CREATE,CREATE2,CREATE3区别,里面有一些实验可以参考
CREATE 这里先给出Mytoken和创建Mytoken的工厂合约 (这里是用remix来实验的) // SPDX-License-Identifier: MIT pragma solidity ^0.8.20;
contract MyToken { address public factory; // 工厂合约地址 address public underlyingToken; // 该合约管理的底层代币地址
constructor() payable {
factory = msg.sender;
}
// 将参数命名为 _token,使用下划线前缀是 Solidity 的通用惯例
function initialize(address _token) external {
require(msg.sender == factory, "FORBIDDEN: Only factory can initialize");
underlyingToken = _token;
}
} // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "./MyToken.sol";
contract TokenFactory { // 通过代币地址查找其对应的合约地址 mapping(address => address) public getContractByToken; // 保存所有部署出来的合约地址列表 address[] public allContracts;
function create(address _token) external returns (address instanceAddr) {
require(_token != address(0), "INVALID_ADDRESS");
require(getContractByToken[_token] == address(0), "CONTRACT_EXISTS");
// 创建新合约
MyToken instance = new MyToken();
// 调用初始化
instance.initialize(_token);
instanceAddr = address(instance);
// 更新记录
allContracts.push(instanceAddr);
getContractByToken[_token] = instanceAddr;
return instanceAddr;
}
function getDeployedContract(address _token) external view returns (address) {
return getContractByToken[_token];
}
// 这里的 _instance 是指 MyToken 的地址
function getFactoryOf(address _instance) external view returns (address) {
return MyToken(_instance).factory();
}
} 实验开始
在终端输入一下代码,可以得到当前工厂合约的nonce是多少。 每创建一个新的Mytoken也就是说每调用一次create,就会+1 await web3.eth.getTransactionCount("factory_CONTRACT_ADDRESS") 而这个nonce跟生成Mytoken的地址是有巨大关系的,所以我们进到下面的evm的源码里面看一下,这是使用CREATE来创建合约的源码 // core/vm/evm.go // Create creates a new contract using code as deployment code. func (evm EVM) Create(caller common.Address, code []byte, gas uint64, value uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { contractAddr = crypto.CreateAddress(caller, evm.StateDB.GetNonce(caller)) return evm.create(caller, code, gas, value, contractAddr, CREATE) } 可以很明显的看到合约的地址跟caller和这个caller的nonce,evm.StateDB.GetNonce(caller)有关系,下面就是create完一个合约后nonce会增加的实现 // core/vm/evm.go // create creates a new contract using code as deployment code. func (evm EVM) create(...){ //... nonce := evm.StateDB.GetNonce(caller) if nonce+1 < nonce { return nil, common.Address{}, gas, ErrNonceUintOverflow } evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator) //... } PS:如果合约创建失败回滚之后这个nonce还是会增加,原因如下: // core/vm/evm.go func (evm EVM) create(...){ //... evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator) //...
// Create a new account on the state only if the object was not present. // It might be possible the contract code is deployed to a pre-existent // account with non-zero balance. snapshot := evm.StateDB.Snapshot() if !evm.StateDB.Exist(address) { evm.StateDB.CreateAccount(address) } //... ret, err = evm.initNewContract(contract, address) if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { contract.UseGas(contract.Gas, evm.Config.Tracer, tracing.GasChangeCallFailedExecution) } } } 因为snapshot储存是在nonce增加之后,然后revert是到snapshot的地方,所以nonce还是增加了 CREATE2
contract MyToken2 { address public factory; address public underlyingToken;
// constructor需要接收参数,才能在 new MyToken2{salt: salt}(_token) 中传
constructor(address _token) payable {
factory = msg.sender;
underlyingToken = _token;
}
} // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "./MyToken2.sol";
// 使用create2创建MyToken2 contract TokenFactory2 {
function createMytokenSalted(bytes32 salt, address _token) public returns (address) {
// bytecodeHash 必须包含构造参数的编码
bytes32 bytecodeHash = keccak256(
abi.encodePacked(
type(MyToken2).creationCode, //这是MyToken2的基础代码
abi.encode(_token) // constructor参数在这里
)
);
// 预测地址
address predictedAddress = address(uint160(uint(keccak256(
abi.encodePacked(
bytes1(0xff),
address(this),
salt,
bytecodeHash
)
))));
// 使用CREATE2
MyToken2 mytoken2 = new MyToken2{salt: salt}(_token);
require(address(mytoken2) == predictedAddress, "Address mismatch");
return address(mytoken2);
}
}
MyToken2 mytoken2 = new MyToken2{salt: salt}(_token); 这里使用的是CREATE2来进行创建合约,这个{salt: salt}是用来告诉编译器使用 CREATE2 ,而不是默认的 CREATE 会做下面几件事
// 调用 evm.Create2的地方 res, addr, returnGas, suberr := evm.Create2(scope.Contract.Address(), input, gas, &endowment, &salt) // ... } 然后正式进入CREATE2阶段 // core/vm/evm.go // The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:] // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. func (evm EVM) Create2(caller common.Address, code []byte, gas uint64, endowment uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { inithash := crypto.HashData(evm.hasher, code) contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:]) return evm.create(caller, code, gas, endowment, contractAddr, CREATE2) }
// crypto/crypto.go // CreateAddress2 creates an ethereum address given the address bytes, initial // contract code hash and a salt. func CreateAddress2(b common.Address, salt [32]byte, inithash []byte) common.Address { return common.BytesToAddress(Keccak256([]byte{0xff}, b.Bytes(), salt[:], inithash)[12:]) } 通过common.BytesToAddress调用来计算地址,下面这个是公示,这个对应EIP-1014的公式 address = keccak256(0xff || caller || salt || keccak256(initcode))[12:] 从这个公式可以看出来CREATE2创建的地址是跟nonce无关的。 下面是用CREATE2来实现的工厂合约创建实验结果 参数:
在orderly中是使用CREATE3来创建地址的 address contractAddress = CREATE3.deployDeterministic(creationCode, salt); 根据上面的步骤 首先要用CREATE2部署一个proxy:let proxy := create2(0, 0x10, 0x10, salt) 然后用这个proxy来调用CREATE部署目标合约,下面这行是先预测地址 deployed := keccak256(0x1e, 0x17) 然后会通过proxy来真正调用CREATE来进行合约的创建 call(gas(), proxy, value, add(initCode, 0x20), mload(initCode), 0x00, 0x00) 调用之后机会EVM 开始执行 proxy 合约的字节码 uint256 private constant _PROXY_INITCODE = 0x67363d3d37363d34f03d5260086018f3; 这个字节码的解释在这边,可以看到f0就是CREATE操作 [图片] 调用之后就会执行CREATE,读取内存中的 initCode 计算地址 keccak256(RLP(proxy, nonce=1))[12:] 然后部署合约到该地址并返回新合约地址 参考文献: https://github.com/ethereum/go-ethereum https://www.jianshu.com/p/f319c78e9714 https://blog.csdn.net/RuRu_Bai/article/details/136820594 https://blog.csdn.net/ak19920601/article/details/135065045https://ithelp.ithome.com.tw/articles/10287334 https://learnblockchain.cn/article/9696 https://learnblockchain.cn/article/14032
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!