合约创建指南: create、create2 和 create3 的区别及应用场景
以太坊开发者使用称为操作码的特殊指令在与以太坊虚拟机(EVM)兼容的区块链上部署合约。在这些指令中:
前两个是实际的操作码,而 “create3” 是一个类似功能的有用库。本指南将为你快速概述每个操作码的功能及其重要性。
create
操作码是最常用的合约创建操作码。当合约从脚本或其他开发环境中部署时,create 操作码是 EVM 执行的低级指令,用于部署和生成合约地址。
0xFF
。通过组合这些参数,CREATE2 为新合约计算出一个确定性的地址,即使区块链演变,该地址也保持不变。
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
)
)
);
}
}
当目标是在多个区块链上部署合约到相同地址时,影响部署地址的因素较少,使得实现这一目标更容易。因此,在这种情况下,CREATE3 比 CREATE2 更好用。
“Create”、“create2”和“create3”是任何从事以太坊合约工作的人必备的工具。每个工具提供不同的合约部署方式,“create”简单直接,“create2”提供可预测的合约地址,而“create3”通过库方法提供多链兼容性。有关详细信息,请查看:
有关以太坊开发和见解的更多信息,请访问 smart-contracts-developer.com 和solichain.com。在 Twitter、LinkedIn和 GitHub 上与我联系。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!