创建一个合约工厂来克隆Solidity智能合约

1,284 阅读5分钟

简介

在区块链开发中,将数据集开采到区块链中是一个相当昂贵的过程,因为每开采一个新的数据到下一个区块都要收取费用。在区块链上部署智能合约的结果是将合约的数据挖掘到下一个区块中,这将花费一些气体费用,如果在以太坊区块链上部署,则会以以太币收取。

本文将实际演示如何使用工厂模式以正确的方式部署你的智能合约的多个实例。此外,我们还将讨论工厂模式,它的好处,以及何时使用它。

先决条件

要跟上这篇文章,你应该有关于用Solidity开发智能合约的知识。

以下内容将在本文中涵盖。

工厂模式

工厂设计模式是一种著名的编程模式。其概念很简单:你没有直接创建对象的实例,而是有一个单一的对象(工厂)来为你做这件事。

这与Solidity是一样的,因为智能合约是对象。在Solidity中,一个工厂是一个合约,它将部署其他合约的多个实例。

我们有时需要创建不同类型的对象,但在运行时执行代码之前,我们不知道要实例化什么样的对象。在这种情况下,工厂技术就派上了用场。

一般来说,一个基本的工厂合约应该能够部署同一个合约的多个实例,将这些创建的实例存储在区块链上,并在需要时检索它们。您可能想为管理已部署的合约添加更多的功能,比如检索合约的特定实例,禁用合约的实例,等等。

在 Solidity 中使用工厂模式的好处

以下是在 Solidity 中使用工厂模式的好处。

  1. 以高气质效率部署多个合同
  2. 追踪所有部署的合同
  3. 在多个合同的部署中节省汽油

何时使用工厂模式

工厂模式在以下情况下是有效的。

  • 当我们需要在运行时快速产生一个智能合约的多个实例时
  • 当我们要处理大量的、具有相同功能的合约时

Solidity中工厂模式的类型

我们将讨论在Solidity 智能合约中通常实现的两类工厂模式。这些模式包括。

  • 普通工厂模式--这种工厂模式部署了其他合约的多个实例,没有任何优化,以节省每次部署的气体。
  • 克隆工厂模式--这种工厂模式部署其他合约的多个实例,强调优化,以在每次部署中节省气体。

我们的第一个 Solidity 智能合约

我们将创建一个简单的智能合约,它将被工厂合约用来部署多个实例。

// SPDX-License-Identifier: MIT
pragma solidity >0.4.23 <0.9.0;

contract Foundation {
    string public name;
    address public _owner;

    constructor(
        string memory _name,
        address _owner
    ) public {
        name = _name;
        _owner = msg.sender;
    }

}

因为Ethereum是一个开源项目,第一行显示合约的开源许可。第二行指定了执行该合约所需的Solidity版本。

之后,我们建立了Foundation 合同,它类似于其他面向对象编程语言中的类。这里的构造函数用作为参数提供的值来初始化合同的状态变量。当我们创建合同的一个实例时,constructor 函数被调用。

编写我们的第一个工厂合同

Foundation合约目前还没有被创建的方法。因此,我们要做一个工厂合同,用正常的工厂模式来创建Foundation合同的各个实例。

下面是一个正常的工厂合同应该有的样子。

// SPDX-License-Identifier: MIT
pragma solidity >0.4.23 <0.9.0;
import "./Foundation.sol";
contract FoundationFactory {
    Foundation[] private _foundations;
    function createFoundation(
        string memory name
    ) public {
        Foundation foundation = new Foundation(
            name,
            msg.sender
        );
        _foundations.push(foundation);
    }
    function allFoundations(uint256 limit, uint256 offset)
        public
        view
        returns (Foundation[] memory coll)
    {
        return coll;
    }
}

在这里,这段代码导入了Foundation 合同,我们想要创建多个实例。_foundations 变量持有创建的Foundation 合同的实例。

createFoundation 函数部署了Foundation 合约的实例,并将其存储在区块链中,而allFoundations 函数则检索了存储在区块链中的Foundation 合约的所有实例。

普通工厂模式的一个缺点

CREATE操作码的气体成本目前为32,000 Gwei。每次部署Foundation 合约的实例时,都要收取32,000 Gwei的气体费用。

普通工厂模式的主要缺点是气体成本高。而这正是克隆工厂模式的用武之地。

克隆工厂模式。部署我们Solidity智能合约的多个实例的正确模式

为什么采用克隆工厂模式?

因为我们部署的是同一个合约,合约的每个实例都会有相同的字节码。所以,为每个部署反复存储所有字节码,会促进字节码的气体成本浪费。

解决这个问题的机制是只部署一个Foundation 合同的实例,并让所有其他Foundation 合同的实例作为代理,将调用委托给Foundation 合同的第一个实例,并允许函数在代理合同的上下文中运行。有了这个,Foundation 合同的每个实例将有自己的状态,并简单地使用由我们建立的Foundation 合同的实例作为一个库。

Peter Murray,Nate Welch,Joe Messerman创建的eip-1167提供了这种机制。根据它的文档,"这个标准指定了一个最小的字节码实现,它将所有的调用委托给一个已知的、固定的地址,以一种不可改变的方式简单而廉价地克隆合约功能。"

克隆工厂合约eip-1167标准的一个参考实现。

使用克隆工厂模式

为了实现克隆工厂合约,你必须安装克隆工厂包,如下所示。

npm install @optionality.io/clone-factory

现在我们可以在我们的FoundationFactory 上实现克隆工厂合约,如下所示

// SPDX-License-Identifier: MIT
pragma solidity >0.4.23 <0.9.0;
import "./Foundation.sol";
import "@optionality.io/clone-factory/contracts/CloneFactory.sol";
import "zeppelin-solidity/contracts/ownership/Ownable.sol";


contract FoundationFactory is Ownable, CloneFactory {

  address public libraryAddress;

  event FoundationCreated(address newFoundation);

  function FoundationFactory(address _libraryAddress) public {
    libraryAddress = _libraryAddress;
  }

  function setLibraryAddress(address _libraryAddress) public onlyOwner {
    libraryAddress = _libraryAddress;
  }

  function createFoundation(string _name) public onlyOwner {
    address clone = createClone(libraryAddress);
    Foundation(clone).init(_name);
    FoundationCreated(clone);
  }
}

在这里,我们从OpenZeppelin库中导入了Ownable 合同,以便利用它的onlyOwner 修改器,因为它是经过审计的,与自己编写的合同相比更加安全。

通过上面的片段,我们有了一个合同,它将以较低的气体成本委托所有调用合同libraryAddress

虽然这是一个更好的机制,但你应该注意以下几点。

  • 确保你的主合同不是自毁的,因为它将导致所有克隆体停止工作,从而冻结它们的状态和平衡
  • 确保你的主合同在你的构造函数中被预初始化,因为主合同构造函数唯一被调用的时间是在主合同的创建期间

部署您的克隆工厂合同

现在,如果你是用Truffle开发的,你可以通过创建一个新的迁移文件来部署工厂合同,该文件如下。

const FoundationFactoryContract = artifacts.require("FoundationFactory");
module.exports = function(deployer) {
 deployer.deploy(FoundationFactoryContract);
}

如果你用Hardhat开发,ethers.getContractFactory 方法已经覆盖了你,所以不需要自己创建工厂合同。

你可以去部署Foundation 合约,如下所示。

const hre = require("hardhat");
async function main() {
  const Foundation= await hre.ethers.getContractFactory("Foundation");
  const foundation= await NFTMarket.deploy();
  await foundation.deployed();
  console.log("Foundation contract deployed to: ", foundation.address);
}

普通工厂和克隆工厂模式的比较

为了考察CloneFactory 和普通工厂之间的气体费用差异,我们将把每个合同部署到测试网,接着执行每个合同的createFoundation 函数,然后在资源管理器中检查交易哈希值,以了解使用了多少气体。下面是执行createFoundation 函数时收取的气体费用。

克隆工厂
气体费用:
基本。12.959896517 Gwei

普通工厂
气费:
基准。25.529794514 Gwei

结语

在这篇文章中,我们已经探讨了工厂模式,它的好处,它的类型,以及它何时最适合我们的智能合约。同时,我们已经确定,由于其气体成本效率,克隆工厂模式是部署我们Solidity智能合约的多个实例的正确模式。

希望你能发现这篇文章的信息量和帮助。你可以在TwitterLinkedIn上关注我,在那里我每天分享关于web3和web开发的技巧。

The postCreating a contract factory to clone Solidity smart contractsappeared first onLogRocket Blog.