Solidity中通过工厂合约创建合约原理详解

  • Louis
  • 更新于 2024-07-28 11:34
  • 阅读 753

在Solidity中,工厂合约是一种设计模式,用于创建和管理多个实例合约。通过一个工厂合约,你可以集中管理合约的创建逻辑,方便地部署多个合约实例,跟踪它们的地址,并对它们进行管理。工厂合约模式在开发去中心化应用(DApps)时非常有用,尤其是在需要频繁创建和销毁合约实例的场景下。

基本概念

在Solidity中,工厂合约是一种设计模式,用于创建和管理多个实例合约。通过一个工厂合约,你可以集中管理合约的创建逻辑,方便地部署多个合约实例,跟踪它们的地址,并对它们进行管理。工厂合约模式在开发去中心化应用(DApps)时非常有用,尤其是在需要频繁创建和销毁合约实例的场景下。

工厂合约代码示例

下面是一个生产级别的Solidity工厂合约示例。

Step 1: 定义子合约

首先,我们定义一个简单的子合约,它包含两个地址类型的变量和一个构造函数,两个变量都是在构造函数中进行初始化赋值的。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

contract Account {
    address public bank;
    address public owner;

    constructor(address _owner) payable {
        bank = msg.sender;
        owner = _owner;
    }
}

可以注意到,构造函数添加了payable关键字,是为了接收主币,如果你对payable关键字还不太了解,可以参考我写的这篇文章:Solidity中的payable关键字

Step 2: 定义工厂合约

然后,我们定义工厂合约,用于创建和管理子合约实例,我们通过工厂合约部署子合约相当于在工厂生产产品一样。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

contract Account {
    address public bank;
    address public owner;

    constructor(address _owner) payable {
        bank = msg.sender;
        owner = _owner;
    }
}

// 定义工厂合约
contract AccountFactory {
    Account[] public accounts;
    event ChildContractCreated(address childContractAddress);

    function createAccount(address _owner) external payable  {
      Account account = new Account{value: 111}(_owner);
      accounts.push(account);
      emit ChildContractCreated(address(account));
    }

    function getChildContracts() public view returns (Account[] memory) {
        return accounts;
    }
}

详细解释

  1. 子合约 Account:

    • bank 是创建该合约的地址(即工厂合约的地址)。
    • owner 是合约的拥有者,由创建合约时传入的 _owner 地址确定。
    • 构造函数 constructor(address _owner) payable 用于初始化合约的 bankowner 地址,同时允许在创建时接收以太币。
  2. 工厂合约 AccountFactory:

    • accounts 是一个 Account 类型的数组,用于存储所有创建的 Account 合约实例。
    • ChildContractCreated 是一个事件,当一个新的 Account 合约实例被创建时,会触发该事件。
    • createAccount(address _owner) external payable 函数用于创建新的 Account 实例,并将其地址存储在 accounts 数组中,同时触发 ChildContractCreated 事件。注意,value: 111 传递了 111 wei 给新创建的 Account 实例。
    • getChildContracts() 是一个公有函数,返回 accounts 数组,方便查看所有创建的 Account 实例。

实现原理

当我们在 Solidity 中使用 new 操作符来部署新合约时,底层实现实际上是调用了 EVM 的 CREATE 操作码。new 操作符在高层次上提供了一个简洁的接口来部署新合约,但在底层,它依赖于 EVM 提供的 CREATE 操作码来完成实际的部署工作。

CREATE操作码是以太坊虚拟机(EVM)中的一条指令,用于创建新合约,它是智能合约可以使用的一种低级操作。通过CREATE这个操作码创建的新合约,新合约的地址是基于创建者的地址和其nonce计算出来的。

一个具体的例子:

假设我们有一个简单的工厂合约,它可以通过CREATE 操作码创建新合约。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SimpleContract {
  uint256 public value;

  constructor(uint256 _value) {
    value = _value;
  }
}

contract Factory {
  event Deployed(address addr);

  function deploy(uint256 _value) public returns (address) {
    // Create the bytecode for the new contract
    bytes memory bytecode = abi.encodePacked(
      type(SimpleContract).creationCode,
      abi.encode(_value)
    );

    address addr;
    assembly {
      // Create the new contract using the CREATE opcode
      addr := create(0, add(bytecode, 0x20), mload(bytecode))
      // Check if the deployment was successful
      if iszero(extcodesize(addr)) {
        revert(0, 0)
      }
    }

    emit Deployed(addr);
    return addr;
  }
}

在上面的代码中,Factory 合约使用 create 操作码部署 SimpleContract 合约。

计算公式:

  • address = keccak256(rlp_encode([sender, nonce]))[12:]

    具体的示例说明:

    1、获取工厂合约的地址和 nonce:

假设工厂合约地址为 0x1234567890123456789012345678901234567890,nonce 为 1。

2、RLP 编码:RLP 编码 [sender, nonce],在这个例子中 RLP 编码结果为:

rlp_encode([0x1234567890123456789012345678901234567890, 1])

3、计算哈希值:对 RLP 编码结果应用 keccak256 哈希函数,得到一个 32 字节的哈希值,例如:

keccak256(rlp_encoded_data) = 0xabc1234567890abcdef1234567890abcdef1234567890abcdef1234567890abc

4、截取后 20 个字节:取哈希值的最后 20 个字节,即为新合约的地址:

new_contract_address = 0x567890abcdef1234567890abcdef1234567890abc

操作步骤

1、我们先要部署工厂合约,部署成功后,拿到工厂合约的地址,并记录下来;

01.png

02.png

部署的工厂合约地址:0xaE036c65C649172b43ef7156b009c6221B596B8b

2、通过工厂合约,创建子合约,创建时候需要传入一些主币

03.png

04.png

3、调用工厂合约的方法,获取部署的子合约的地址

05.png

部署的子合约的地址:0x476915eBDb08CaeeDf98953D7c940146Ff5BAA94

我们可以看到工厂合约的地址余额还剩下 1889 个Wei,我们部署的时候传入了 2000 个Wei,其中有111个Wei发送给了子合约。

4、通过地址加载的方法,将子合约加载出来

06.png

点击 At Address 可以将部署的子合约加载出来;

07.png

bank地址:0xaE036c65C649172b43ef7156b009c6221B596B8b (工厂合约地址)

owner地址:0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 (部署工厂合约的地址)

我们可以看到,子合约中的余额就是我们写死的 111 个Wei

总结

工厂合约模式在Solidity中是一种强大且灵活的设计模式,适用于需要创建和管理多个合约实例的场景。通过工厂合约,可以方便地集中管理子合约的创建逻辑,提高代码的可维护性和可扩展性。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Louis
Louis
web3 developer,技术交流或者有工作机会可加VX: magicalLouis