合约创建指南: create、create2 和 create3

合约创建指南: create、create2 和 create3 的区别及应用场景

以太坊开发者使用称为操作码的特殊指令在与以太坊虚拟机(EVM)兼容的区块链上部署合约。在这些指令中:

  • “CREATE”
  • “CREATE2”
  • “CREATE3”

前两个是实际的操作码,而 “create3” 是一个类似功能的有用库。本指南将为你快速概述每个操作码的功能及其重要性。

利用“create”操作码

create 操作码是最常用的合约创建操作码。当合约从脚本或其他开发环境中部署时,create 操作码是 EVM 执行的低级指令,用于部署和生成合约地址。

用例:

  1. 在合约中动态创建新合约。
  2. 对于部署多个合约更具成本效益。
  3. 当地址可预测性不是必需时。

利用“create2”操作码

  1. 固定前缀,总是0xFF
  2. 发送者的地址确保合约与特定创建者绑定。
  3. 选择的盐值为合约地址增加唯一性。
  4. 字节码包含要部署的新合约的代码。

通过组合这些参数,CREATE2 为新合约计算出一个确定性的地址,即使区块链演变,该地址也保持不变。

用例:

  1. 确保新合约的确定性和可预测的地址。
  2. 在状态通道、合约钱包和链下交互中有用。
  3. 通过在部署前检查现有合约提供额外的安全层。

利用“create3” 库

CREATE3 类似于 CREATE2,但在地址推导公式中不包括合约 initCode。它可用于生成不与特定合约代码绑定的确定性合约地址。

CREATE3 是一种结合使用 CREATE 和 CREATE2 的方法,使字节码不再影响部署地址。— CREATE3 比 CREATE 或 CREATE2 更昂贵(固定额外成本约为 55k gas)。

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

/**
  @title A library for deploying contracts EIP-3171 style.
  @author Agustin Aguilar <aa@horizon.io>
*/
library Create3 {
  error ErrorCreatingProxy();
  error ErrorCreatingContract();
  error TargetAlreadyExists();

  /**
    @notice The bytecode for a contract that proxies the creation of another contract
    @dev If this code is deployed using CREATE2 it can be used to decouple `creationCode` from the child contract address

  0x67363d3d37363d34f03d5260086018f3:
      0x00  0x67  0x67XXXXXXXXXXXXXXXX  PUSH8 bytecode  0x363d3d37363d34f0
      0x01  0x3d  0x3d                  RETURNDATASIZE  0 0x363d3d37363d34f0
      0x02  0x52  0x52                  MSTORE
      0x03  0x60  0x6008                PUSH1 08        8
      0x04  0x60  0x6018                PUSH1 18        24 8
      0x05  0xf3  0xf3                  RETURN

  0x363d3d37363d34f0:
      0x00  0x36  0x36                  CALLDATASIZE    cds
      0x01  0x3d  0x3d                  RETURNDATASIZE  0 cds
      0x02  0x3d  0x3d                  RETURNDATASIZE  0 0 cds
      0x03  0x37  0x37                  CALLDATACOPY
      0x04  0x36  0x36                  CALLDATASIZE    cds
      0x05  0x3d  0x3d                  RETURNDATASIZE  0 cds
      0x06  0x34  0x34                  CALLVALUE       val 0 cds
      0x07  0xf0  0xf0                  CREATE          addr
  */

  bytes internal constant PROXY_CHILD_BYTECODE = hex"67_36_3d_3d_37_36_3d_34_f0_3d_52_60_08_60_18_f3";

  //                        KECCAK256_PROXY_CHILD_BYTECODE = keccak256(PROXY_CHILD_BYTECODE);
  bytes32 internal constant KECCAK256_PROXY_CHILD_BYTECODE = 0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f;

  /**
    @notice Returns the size of the code on a given address
    @param _addr Address that may or may not contain code
    @return size of the code on the given `_addr`
  */
  function codeSize(address _addr) internal view returns (uint256 size) {
    assembly { size := extcodesize(_addr) }
  }

  /**
    @notice Creates a new contract with given `_creationCode` and `_salt`
    @param _salt Salt of the contract creation, resulting address will be derivated from this value only
    @param _creationCode Creation code (constructor) of the contract to be deployed, this value doesn't affect the resulting address
    @return addr of the deployed contract, reverts on error
  */
  function create3(bytes32 _salt, bytes memory _creationCode) internal returns (address addr) {
    return create3(_salt, _creationCode, 0);
  }

  /**
    @notice Creates a new contract with given `_creationCode` and `_salt`
    @param _salt Salt of the contract creation, resulting address will be derivated from this value only
    @param _creationCode Creation code (constructor) of the contract to be deployed, this value doesn't affect the resulting address
    @param _value In WEI of ETH to be forwarded to child contract
    @return addr of the deployed contract, reverts on error
  */
  function create3(bytes32 _salt, bytes memory _creationCode, uint256 _value) internal returns (address addr) {
    // Creation code
    bytes memory creationCode = PROXY_CHILD_BYTECODE;

    // Get target final address
    addr = addressOf(_salt);
    if (codeSize(addr) != 0) revert TargetAlreadyExists();

    // Create CREATE2 proxy
    address proxy; assembly { proxy := create2(0, add(creationCode, 32), mload(creationCode), _salt)}
    if (proxy == address(0)) revert ErrorCreatingProxy();

    // Call proxy with final init code
    (bool success,) = proxy.call{ value: _value }(_creationCode);
    if (!success || codeSize(addr) == 0) revert ErrorCreatingContract();
  }

  /**
    @notice Computes the resulting address of a contract deployed using address(this) and the given `_salt`
    @param _salt Salt of the contract creation, resulting address will be derivated from this value only
    @return addr of the deployed contract, reverts on error

    @dev The address creation formula is: keccak256(rlp([keccak256(0xff ++ address(this) ++ _salt ++ keccak256(childBytecode))[12:], 0x01]))
  */
  function addressOf(bytes32 _salt) internal view returns (address) {
    address proxy = address(
      uint160(
        uint256(
          keccak256(
            abi.encodePacked(
              hex'ff',
              address(this),
              _salt,
              KECCAK256_PROXY_CHILD_BYTECODE
            )
          )
        )
      )
    );

    return address(
      uint160(
        uint256(
          keccak256(
            abi.encodePacked(
              hex"d6_94",
              proxy,
              hex"01"
            )
          )
        )
      )
    );
  }
}

contract Child {
  function hola() external view returns (string memory) {
    return "mundo";
  }
}

contract Deployer {
  function deployChild() external {
    Create3.create3(keccak256(bytes("<my salt>")), type(Child).creationCode);
  }
}

contract Child2 {
  uint256 meaningOfLife;
  address owner;

  constructor(uint256 _meaning, address _owner) {
    meaningOfLife = _meaning;
    owner = _owner;
  }
}

contract Deployer2 {
  function deployChild() external {
    Create3.create3(
      keccak256(bytes("<my salt>")), 
      abi.encodePacked(
        type(Child).creationCode,
        abi.encode(
          42,
          msg.sender
        )
      )
    );
  }
}

特点:

  1. 基于msg.sender + salt的确定性合约地址。
  2. 相同的合约地址适用于不同的 EVM 网络。
  3. 支持任何兼容 EVM 的链,支持 CREATE2。
  4. 可支付的合约创建(转发到子合约)— 支持构造函数。

用例:

当目标是在多个区块链上部署合约到相同地址时,影响部署地址的因素较少,使得实现这一目标更容易。因此,在这种情况下,CREATE3 比 CREATE2 更好用。

结论

“Create”、“create2”和“create3”是任何从事以太坊合约工作的人必备的工具。每个工具提供不同的合约部署方式,“create”简单直接,“create2”提供可预测的合约地址,而“create3”通过库方法提供多链兼容性。有关详细信息,请查看:

有关以太坊开发和见解的更多信息,请访问 smart-contracts-developer.comsolichain.com。在 TwitterLinkedInGitHub 上与我联系。

我是 AI 翻译官,为大家转译优秀英文文章,如有翻译不通的地方,在这里修改,还请包涵~

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

0 条评论

请先 登录 后评论
Solichain
Solichain
We make Web3 simple.