关于create和create2对于create创建的合约地址的生成实践
以测试环境的交易https://goerli.etherscan.io/tx/0xb385c360b009d7c0a312fe9e411e13401a65626898f3b871b0d2f4a23ba14549
举例:
其创建的合约地址是:`0xc65dc0119f3e2802cb29cd64104dcfe0aec658bf`
“`js
% node
> var util = require(‘ethereumjs-util’);
> var buf=[Buffer.from(“91aa5df5b42ef16b658b1d2231d73e442cd9c6ed”, “hex”), 266];
> util.keccak256(util.rlp.encode(buf)).toString(“Hex”).slice(-40);
‘c65dc0119f3e2802cb29cd64104dcfe0aec658bf’
“`
## create2 使用
关于使用create2预知合约地址的资料,参考:https://eips.ethereum.org/EIPS/eip-1014
代码示例参考:https://solidity-by-example.org/app/create2/
关于代码的几点解释如下:
“`
// 这里的bytecode就是合约的creationCode,即可以通过type(C).creationCode得到。这个code里包含构造函数+执行函数以及构造函数的参数。
// 这里的create2函数的调用,中间两个参数:
// add(bytecode, 0x20),表示的是bytecode的偏移量,这里个bytecode可以理解为一个指针。bytecode实际上是deploy的参数,在deploy的调用时,由于bytecode是一个变长字段,所以在编码时,是长度+实际内容的编码方式。左移这里的add(bytecode, 0x20)实际上是绕过了长度字段,指向了实际bytecode的内容
// mload(bytecode),就是从bytecode这个地址,load一个32字节的值,这个值就是bytecode的长度。可以看看上面的说明。
function deploy(bytes memory bytecode, uint _salt) public payable {
assembly {
addr := create2(
callvalue(), // wei sent with current call
// Actual code starts after skipping the first 32 bytes
add(bytecode, 0x20),
mload(bytecode), // Load the size of code contained in the first 32 bytes
_salt // Salt from function arguments
)
if iszero(extcodesize(addr)) {
revert(0, 0)
}
}
}
“`
> 所以,实际上create2是一个通过固定bytecode可以提前算好地址的合约创建。如果bytecode有变化,则创建的合约地址是不一样的。
# create2 升级合约
那么有么有可能通过create2进行合约的升级呢?答案是肯定的
> 参考:https://ethereum-blockchain-developer.com/110-upgrade-smart-contracts/12-metamorphosis-create2/#overwriting-smart-contracts
过程解释如下:

图中的`0xaaf10f42`是`getImplementation()`函数
“`
> web3.utils.sha3(“getImplementation()”).slice(0,10)
‘0xaaf10f42’
“`
也就是,固定代码会调用`getImplementation()`函数获取动态代码段进行部署,绕过`create2`对bytecode的变化校验。
> 需要注意,create2再次部署时,需要目标合约地址是selfdestruct的,也就是目标地址是一个空合约。
> 另外,reinit的合约,在etherscan里是有标记的。如下图:
>
部署合约V1:

销毁合约V1:

重新部署合约V2:

可以看到图中的合约地址都是同一个。
# 解读metamorphic源码
> https://github.com/0age/metamorphic
## ImmutableCreate2Factory.sol
这个合约提供了一个`safeCreate2`的方法。实际上就是用一个`_deployed`变量来记录是否在目标地址部署过,如果部署过,就不让部署了。
“`solidity
// 主要代码:
// 这里可以通过将salt的前20字节设置为跟msg.sender一样,就可以阻止其他人重新部署合约。因为其他人的msg.sender无法使用这个salt,所以部署不了。
modifier containsCaller(bytes32 salt) {
require(
(address(bytes20(salt)) == msg.sender) ||
(bytes20(salt) == bytes20(0)),
“Invalid salt – first 20 bytes of the salt must match calling address.”
);
_;
}
function safeCreate2(
bytes32 salt,
bytes calldata initializationCode
) external payable containsCaller(salt) returns (address deploymentAddress) {
//…
// 获取目标地址,也就是最终部署完后的合约地址
address targetDeploymentAddress = address(….);
//防止二次部署
require(
!_deployed[targetDeploymentAddress],
“Invalid contract creation – contract has already been deployed.”
);
// 使用create2进行部署,调用参数传入的bytecode
assembly { // solhint-disable-line
let encoded_data := add(0x20, initCode) // load initialization code.
let encoded_size := mload(initCode) // load the init code’s length.
deploymentAddress := create2( // call CREATE2 with 4 arguments.
callvalue, // forward any attached value.
encoded_data, // pass in initialization code.
encoded_size, // pass in init code’s length.
salt // pass in the salt value.
)
}
}
//…
_deployed[deploymentAddress] = true;// 设置防止重新部署
“`
## TransientContract.sol
这个合约就只有一个构造函数,调用`create`生成一个合约,然后立马`selfdestruct`掉。是需要和后面`MetamorphicContractFactory.sol`里合约的`deployMetamorphicContractWithConstructor`方法配合使用。
“`solidity
//关键代码:
contract TransientContract {
constructor() public payable {
// 这里是从msg.sender合约调用getInitializationCode()方法,来获取初始化代码。
// 所以调用这个合约的调用合约(msg.sender)一定有一个getInitializationCode()方法
bytes memory initCode = FactoryInterface(msg.sender).getInitializationCode();
//通过create生成一个合约。
assembly {
let encoded_data := add(0x20, initCode) // load initialization code.
let encoded_size := mload(initCode) // load init code’s length.
metamorphicContractAddress := create( // call CREATE with 3 arguments.
callvalue, // forward any supplied endowment.
encoded_data, // pass in initialization code.
encoded_size // pass in init code’s length.
)
} /* solhint-enable no-inline-assembly */
// 将生成的合约selfdestruct。注意,selfdestruct时,会将余额转移到msg.sender
selfdestruct(metamorphicContractAddress);
}
}
“`
> 该合约的具体作用,在讨论`MetamorphicContractFactory.sol`的`deployMetamorphicContractWithConstructor`时再解析
## MetamorphicContractFactory.sol
### 构造函数
先看下合约的构造函数,构造函数会传递进来一个`transientContractInitializationCode`
也就是临时合约的初始化代码,从[链上部署情况](https://etherscan.io/address/0x00000000e82eb0431756271f0d00cfb143685e7b#code)来看,是:
“`
608060408190527f57b9f52300000000000000000000000000000000000000000000000000000000815260609033906357b9f5239060849060009060048186803b1801561004c57600080fd5b505afa158015610060573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561008957600080fd5b8101908080516401000000008111156100a157600080fd5b820160208101848111156100b457600080fd5b81516401000000008111828201871017156100ce57600080fd5b505092919050505090506000816020018251808234f0925050506001600160a01b0381166100fb57600080fd5b806001600160a01b0316fffe
“`
这段编码实际上是什么呢:
实际上就是`TransientContract.sol`合约。可以通过将合约用solidity `0.5.6`版本编译(选择`Enable optimization`)。比如我在remix里编译的:



另外,合约里有个`_metamorphicContractInitializationCode`变量,赋值的是:
`5860208158601c335a63aaf10f428752fa158151803b80938091923cf3`
那么这个是什么呢?转为op code看下,就是上文图中画的。参考[create2 升级合约](https://learnblockchain.cn/article/4915#create2%20%E5%8D%87%E7%BA%A7%E5%90%88%E7%BA%A6)
> OK,我们现在明白了,`MetamorphicContractFactory.sol`合约里的`_transientContractInitializationCode`就是`TransientContract.sol`合约内容。
> `_metamorphicContractInitializationCode`实际上就是调用`getImplementation()`获取地址的执行代码(`EXTCODECOPY`)。
下面我们看下这个合约主要提供的三个创建变质合约方法:
### deployMetamorphicContract
“`
// 函数参数里的两个calldata变量,
// implementationContractInitializationCode – 需要部署的合约字节码
// metamorphicContractInitializationCalldata – 部署完以后,需要执行的初始化函数字节码
// 因为下面的实现上,create2只会拷贝合约执行代码,不会执行初始化。所以需要创建完合约后立马进行初始化。
function deployMetamorphicContract(
bytes32 salt,
bytes calldata implementationContractInitializationCode,
bytes calldata metamorphicContractInitializationCalldata
) external payable containsCaller(salt) returns (
address metamorphicContractAddress
) {
//…
// 这里就是调用create,使用参数传递进来的字节码创建一个合约。这个合约本身会执行初始化构造函数。
assembly {
let encoded_data := add(0x20, implInitCode) // load initialization code.
let encoded_size := mload(implInitCode) // load init code’s length.
implementationContract := create( // call CREATE with 3 arguments.
0, // do not forward any endowment.
encoded_data, // pass in initialization code.
encoded_size // pass in init code’s length.
)
}
//…
// 这里记住了目标部署合约地址对应的合约。所以,在下面create2的时候,使用的是EXTCODECOPY,这个op实际上拷贝的是合约的执行代码(必然没有构造代码)。
_implementations[metamorphicContractAddress] = implementationContract;
// 执行create2,实际上就是从上面create的合约里,拷贝合约执行代码到新的合约里。
// initCode是5860208158601c335a63aaf10f428752fa158151803b80938091923cf3
// 这个上面有说明。
assembly {
let encoded_data := add(0x20, initCode) // load initialization code.
let encoded_size := mload(initCode) // load the init code’s length.
deployedMetamorphicContract := create2( // call CREATE2 with 4 arguments.
0, // do not forward any endowment.
encoded_data, // pass in initialization code.
encoded_size, // pass in init code’s length.
salt // pass in the salt value.
)
}
// 这里就是调用初始化方法,因为create2是没有调用构造函数的(构造函数没有办法拷贝过来)
if (data.length > 0 || msg.value > 0) {
(bool success,) = deployedMetamorphicContract.call.value(msg.value)(data);
require(success, “Failed to initialize the new metamorphic contract.”);
}
}
“`
画个图:

>可以看到实际上create2是从create的那个合约拷贝的执行代码。
### deployMetamorphicContractFromExistingImplementation
这个函数和上面的类似,只是不用调用`create`方法去创建tmp合约了,直接用参数给出的合约地址。
### deployMetamorphicContractWithConstructor
这个函数和上面两个的区别,主要是调用的顺序反掉了,先调用`create2`然后调用`create`。这样可以直接调用构造函数,而不用外部传递过来初始化函数。
来个图:

> 这里为何要在临时合约上调用`selfdestruct`,是为了让临时合约销毁掉,这样下次再调用时,其合约的`nonce`值还是从1开始,那么临时合约通过`create`创建的最终合约地址才不会变(最终合约地址跟`msg.sende` 和`msg.nonce`相关)
>
> 另外还需要注意:如果想更新最终部署的合约,仍旧需要先将先前部署的最终部署合约`selfdestruct`掉,否则`create`方法会报错。
具体实例参考:https://goerli.etherscan.io/tx/0xb29f441f809e67e70e3b9385bfcd8b72a594c032b0fd1683b1771f759e7524fd
另外一个更容易理解的例子:
> 交易1:
> 1) 使用`create2`创建合约`A`(0x43cF39292785Ff959fF35E59dE698697A59E5404)
>
> 2) 在创建的合约`A`中用`create`创建合约`B`(0xD11e08dca07F7AD7acF2412F15CBec1b50390EF6)
> 3) 调用合约`A`的`selfdestruct`方法,销毁合约`A`
> https://goerli.etherscan.io/tx/0xfac1f1821f67d646454e80e7b809898f8fe0282db07a51245c33ab74f7260c63#internal
>
> 交易2:调用合约`B`的`selfdestruct`方法销毁合约
> https://goerli.etherscan.io/tx/0x163c501c3fc45d2e088c6a3af300685f5cd370cb0ba1be22e4d58de601a33bbf
>
> 交易3:重复交易1
> https://goerli.etherscan.io/tx/0x84004b0620cbc9ff0e972a611632b30b9b9abdbdd25b2e1e22af481b370cb805#internal
可以看到`交易1`和`交易3`创建的最终合约地址是一样的,都是`0xD11e08dca07F7AD7acF2412F15CBec1b50390EF6`
这里再重点说下一个函数:
“`solidity
// 这个函数就是保证create地址不变的关键。注意这里transientContractAddress地址不变是由create2来保证,nonce=0x01写死了,也是因为让临时合约selfdestruct来实现的。
function _getMetamorphicContractAddressWithConstructor(
address transientContractAddress
) internal pure returns (address) {
// determine the address of the metamorphic contract.
return address(
uint160( // downcast to match the address type.
uint256( // set to uint to truncate upper digits.
keccak256( // compute CREATE hash via RLP encoding.
abi.encodePacked( // pack all inputs to the hash together.
byte(0xd6), // first RLP byte.
byte(0x94), // second RLP byte.
transientContractAddress, // called by the transient contract.
byte(0x01) // nonce begins at 1 for contracts.
)
)
)
)
);
}
“`
## Metapod.sol
### 构造函数
这里有几个常量解释下
“`
bytes private constant TRANSIENT_CONTRACT_INITIALIZATION_CODE = ( hex”58601c59585992335a6357b9f5235952fa5060403031813d03839281943ef08015602557ff5b80fd”
);
//这个转换成opcode是:
[1] PUSH1 0x1c
[2] MSIZE
[3] PC
[4] MSIZE
[5] SWAP3
[6] CALLER
[7] GAS
[12] PUSH4 0x57b9f523
[13] MSIZE
[14] MSTORE
[15] STATICCALL // 这里是调用caller的getInitializationCode()函数
[16] POP
[18] PUSH1 0x40
[19] ADDRESS
[20] BALANCE
[21] DUP2
[22] RETURNDATASIZE
[23] SUB
[24] DUP4
[25] SWAP3
[26] DUP2
[27] SWAP5
[28] RETURNDATACOPY
[29] CREATE // 调用create部署新合约
[30] DUP1
[31] ISZERO
[33] PUSH1 0x25
[34] JUMPI
[35] SELFDESTRUCT // 将自己kill掉
[36] JUMPDEST
[37] DUP1
[38] REVERT
// 可以看出来,实际上上面代码完成的,就是TransientContract合约构造函数的主要内容。
“`
### deploy
和`deployMetamorphicContractWithConstructor`差不多
不过这里有个校验,需要特别警示:
“`
_verifyPrelude(metamorphicContract, _getPrelude(vaultContract));
“`
这是什么呢?文件头有说明:
>Also, bear in mind that any initialization code provided to the contract must
>contain the proper prelude, or initial sequence, with a length of 44 bytes:
> `0x6e03212eb796dee588acdbbbd777d4e73318602b5773 + vault_address + 0xff5b`
也就是说,我们所有部署的最终合约,都需要在头部写上这44个字节。干嘛用的?
看下对应的opcode就明白了:
“`
[15] PUSH15 0x03212eb796dee588acdbbbd777d4e7
[16] CALLER
[17] XOR // 看看caller是不是0x03212eb796dee588acdbbbd777d4e7
[19] PUSH1 0x2b
[20] JUMPI
[41] PUSH20 0x0000000000000000000000000000000000000000
[42] SELFDESTRUCT // 如果是,就执行selfdestruct
[43] JUMPDEST // 否则就跳过这段代码
“`
这是给合约`0x03212eb796dee588acdbbbd777d4e7`留了一个后门,可以让这个caller去执行`selfdestruct`来销毁合约。
这个地址,就是`Metapod`合约本身。见构造函数:
“`
require(
address(this) == address(0x000000000003212eb796dEE588acdbBbD777D4E7),
“Incorrect deployment address.”
);
“`
这里就有个疑问,合约部署的时候,怎么提前知道的地址?看下他的两个部署地址就知道了:
>https://ropsten.etherscan.io/tx/0xabea820680d4c95b90f8eca3751b653846b2d1ca1ee4db966cfa6641ed345689#internal
>https://etherscan.io/tx/0x96b3b4508c899c773748242cfeedb9ddfc95c2c36e96d3c084ce572a418abe7e#internal
他们都是通过创建的新合约去部署的,那么合约的地址就可以在合约内拿到,nonce肯定是1咯,那就可以算出来了。
比如主网的这个:
“`
> web3.utils.sha3(“0xd69410ca1adca9ff38988d75dd1f6ee19b1a6bfa919701”).slice(-40);
‘00000000002b13cccec913420a21e4d11b2dcd3c’
“`
### destroy
有了上面`deploy`的解释,那这个函数就不难理解了,调用`selfdesctruct`函数销毁部署的合约。
### recover
和`deploy`差不多,只是多了个`_triggerVaultFundsRelease`函数
“`
“`
—-
有问题欢迎留言探讨
关于create和create2
对于create创建的合约地址的生成,可以参考:https://learnblockchain.cn/article/4867#Recovery
以测试环境的交易https://goerli.etherscan.io/tx/0xb385c360b009d7c0a312fe9e411e13401a65626898f3b871b0d2f4a23ba14549 举例: 其创建的合约地址是:0xc65dc0119f3e2802cb29cd64104dcfe0aec658bf
% node
> var util = require('ethereumjs-util');
> var buf=[Buffer.from("91aa5df5b42ef16b658b1d2231d73e442cd9c6ed", "hex"), 266];
> util.keccak256(util.rlp.encode(buf)).toString("Hex").slice(-40);
'c65dc0119f3e2802cb29cd64104dcfe0aec658bf'
create2 使用
关于使用create2预知合约地址的资料,参考:https://eips.ethereum.org/EIPS/eip-1014 代码示例参考:https://solidity-by-example.org/app/create2/ 关于代码的几点解释如下:
// 这里的bytecode就是合约的creationCode,即可以通过type(C).creationCode得到。这个code里包含构造函数+执行函数以及构造函数的参数。
// 这里的create2函数的调用,中间两个参数:
// add(bytecode, 0x20),表示的是bytecode的偏移量,这里个bytecode可以理解为一个指针。bytecode实际上是deploy的参数,在deploy的调用时,由于bytecode是一个变长字段,所以在编码时,是长度+实际内容的编码方式。左移这里的add(bytecode, 0x20)实际上是绕过了长度字段,指向了实际bytecode的内容
// mload(bytecode),就是从bytecode这个地址,load一个32字节的值,这个值就是bytecode的长度。可以看看上面的说明。
function deploy(bytes memory bytecode, uint _salt) public payable {
assembly {
addr := create2(
callvalue(), // wei sent with current call
// Actual code starts after skipping the first 32 bytes
add(bytecode, 0x20),
mload(bytecode), // Load the size of code contained in the first 32 bytes
_salt // Salt from function arguments
)
if iszero(extcodesize(addr)) {
revert(0, 0)
}
}
}
所以,实际上create2是一个通过固定bytecode可以提前算好地址的合约创建。如果bytecode有变化,则创建的合约地址是不一样的。
create2 升级合约
那么有么有可能通过create2进行合约的升级呢?答案是肯定的
参考:https://ethereum-blockchain-developer.com/110-upgrade-smart-contracts/12-metamorphosis-create2/#overwriting-smart-contracts
过程解释如下:
图中的0xaaf10f42
是getImplementation()
函数
> web3.utils.sha3("getImplementation()").slice(0,10)
'0xaaf10f42'
也就是,固定代码会调用getImplementation()
函数获取动态代码段进行部署,绕过create2
对bytecode的变化校验。
需要注意,create2再次部署时,需要目标合约地址是selfdestruct的,也就是目标地址是一个空合约。 另外,reinit的合约,在etherscan里是有标记的。如下图:
部署合约V1:
销毁合约V1:
重新部署合约V2:
可以看到图中的合约地址都是同一个。
解读metamorphic源码
https://github.com/0age/metamorphic
ImmutableCreate2Factory.sol
这个合约提供了一个safeCreate2
的方法。实际上就是用一个_deployed
变量来记录是否在目标地址部署过,如果部署过,就不让部署了。
// 主要代码:
// 这里可以通过将salt的前20字节设置为跟msg.sender一样,就可以阻止其他人重新部署合约。因为其他人的msg.sender无法使用这个salt,所以部署不了。
modifier containsCaller(bytes32 salt) {
require(
(address(bytes20(salt)) == msg.sender) ||
(bytes20(salt) == bytes20(0)),
"Invalid salt - first 20 bytes of the salt must match calling address."
);
_;
}
function safeCreate2(
bytes32 salt,
bytes calldata initializationCode
) external payable containsCaller(salt) returns (address deploymentAddress) {
//...
// 获取目标地址,也就是最终部署完后的合约地址
address targetDeploymentAddress = address(....);
//防止二次部署
require(
!_deployed[targetDeploymentAddress],
"Invalid contract creation - contract has already been deployed."
);
// 使用create2进行部署,调用参数传入的bytecode
assembly { // solhint-disable-line
let encoded_data := add(0x20, initCode) // load initialization code.
let encoded_size := mload(initCode) // load the init code's length.
deploymentAddress := create2( // call CREATE2 with 4 arguments.
callvalue, // forward any attached value.
encoded_data, // pass in initialization code.
encoded_size, // pass in init code's length.
salt // pass in the salt value.
)
}
}
//...
_deployed[deploymentAddress] = true;// 设置防止重新部署
TransientContract.sol
这个合约就只有一个构造函数,调用create
生成一个合约,然后立马selfdestruct
掉。是需要和后面MetamorphicContractFactory.sol
里合约的deployMetamorphicContractWithConstructor
方法配合使用。
//关键代码:
contract TransientContract {
constructor() public payable {
// 这里是从msg.sender合约调用getInitializationCode()方法,来获取初始化代码。
// 所以调用这个合约的调用合约(msg.sender)一定有一个getInitializationCode()方法
bytes memory initCode = FactoryInterface(msg.sender).getInitializationCode();
//通过create生成一个合约。
assembly {
let encoded_data := add(0x20, initCode) // load initialization code.
let encoded_size := mload(initCode) // load init code's length.
metamorphicContractAddress := create( // call CREATE with 3 arguments.
callvalue, // forward any supplied endowment.
encoded_data, // pass in initialization code.
encoded_size // pass in init code's length.
)
} /* solhint-enable no-inline-assembly */
// 将生成的合约selfdestruct。注意,selfdestruct时,会将余额转移到msg.sender
selfdestruct(metamorphicContractAddress);
}
}
该合约的具体作用,在讨论
MetamorphicContractFactory.sol
的deployMetamorphicContractWithConstructor
时再解析
MetamorphicContractFactory.sol
构造函数
先看下合约的构造函数,构造函数会传递进来一个transientContractInitializationCode
也就是临时合约的初始化代码,从链上部署情况来看,是:
608060408190527f57b9f52300000000000000000000000000000000000000000000000000000000815260609033906357b9f5239060849060009060048186803b1801561004c57600080fd5b505afa158015610060573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561008957600080fd5b8101908080516401000000008111156100a157600080fd5b820160208101848111156100b457600080fd5b81516401000000008111828201871017156100ce57600080fd5b505092919050505090506000816020018251808234f0925050506001600160a01b0381166100fb57600080fd5b806001600160a01b0316fffe
这段编码实际上是什么呢: 实际上就是TransientContract.sol
合约。可以通过将合约用solidity 0.5.6
版本编译(选择Enable optimization
)。比如我在remix里编译的:
另外,合约里有个_metamorphicContractInitializationCode
变量,赋值的是: 5860208158601c335a63aaf10f428752fa158151803b80938091923cf3
那么这个是什么呢?转为op code看下,就是上文图中画的。参考create2 升级合约
OK,我们现在明白了,
MetamorphicContractFactory.sol
合约里的_transientContractInitializationCode
就是TransientContract.sol
合约内容。_metamorphicContractInitializationCode
实际上就是调用getImplementation()
获取地址的执行代码(EXTCODECOPY
)。
下面我们看下这个合约主要提供的三个创建变质合约方法:
deployMetamorphicContract
// 函数参数里的两个calldata变量,
// implementationContractInitializationCode - 需要部署的合约字节码
// metamorphicContractInitializationCalldata - 部署完以后,需要执行的初始化函数字节码
// 因为下面的实现上,create2只会拷贝合约执行代码,不会执行初始化。所以需要创建完合约后立马进行初始化。
function deployMetamorphicContract(
bytes32 salt,
bytes calldata implementationContractInitializationCode,
bytes calldata metamorphicContractInitializationCalldata
) external payable containsCaller(salt) returns (
address metamorphicContractAddress
) {
//...
// 这里就是调用create,使用参数传递进来的字节码创建一个合约。这个合约本身会执行初始化构造函数。
assembly {
let encoded_data := add(0x20, implInitCode) // load initialization code.
let encoded_size := mload(implInitCode) // load init code's length.
implementationContract := create( // call CREATE with 3 arguments.
0, // do not forward any endowment.
encoded_data, // pass in initialization code.
encoded_size // pass in init code's length.
)
}
//...
// 这里记住了目标部署合约地址对应的合约。所以,在下面create2的时候,使用的是EXTCODECOPY,这个op实际上拷贝的是合约的执行代码(必然没有构造代码)。
_implementations[metamorphicContractAddress] = implementationContract;
// 执行create2,实际上就是从上面create的合约里,拷贝合约执行代码到新的合约里。
// initCode是5860208158601c335a63aaf10f428752fa158151803b80938091923cf3
// 这个上面有说明。
assembly {
let encoded_data := add(0x20, initCode) // load initialization code.
let encoded_size := mload(initCode) // load the init code's length.
deployedMetamorphicContract := create2( // call CREATE2 with 4 arguments.
0, // do not forward any endowment.
encoded_data, // pass in initialization code.
encoded_size, // pass in init code's length.
salt // pass in the salt value.
)
}
// 这里就是调用初始化方法,因为create2是没有调用构造函数的(构造函数没有办法拷贝过来)
if (data.length > 0 || msg.value > 0) {
(bool success,) = deployedMetamorphicContract.call.value(msg.value)(data);
require(success, "Failed to initialize the new metamorphic contract.");
}
}
画个图:
可以看到实际上create2是从create的那个合约拷贝的执行代码。
deployMetamorphicContractFromExistingImplementation
这个函数和上面的类似,只是不用调用create
方法去创建tmp合约了,直接用参数给出的合约地址。
deployMetamorphicContractWithConstructor
这个函数和上面两个的区别,主要是调用的顺序反掉了,先调用create2
然后调用create
。这样可以直接调用构造函数,而不用外部传递过来初始化函数。 来个图:
这里为何要在临时合约上调用
selfdestruct
,是为了让临时合约销毁掉,这样下次再调用时,其合约的nonce
值还是从1开始,那么临时合约通过create
创建的最终合约地址才不会变(最终合约地址跟msg.sende
和msg.nonce
相关)另外还需要注意:如果想更新最终部署的合约,仍旧需要先将先前部署的最终部署合约
selfdestruct
掉,否则create
方法会报错。
具体实例参考:https://goerli.etherscan.io/tx/0xb29f441f809e67e70e3b9385bfcd8b72a594c032b0fd1683b1771f759e7524fd
另外一个更容易理解的例子:
交易1: 1) 使用
create2
创建合约A
(0x43cF39292785Ff959fF35E59dE698697A59E5404)2) 在创建的合约
A
中用create
创建合约B
(0xD11e08dca07F7AD7acF2412F15CBec1b50390EF6) 3) 调用合约A
的selfdestruct
方法,销毁合约A
https://goerli.etherscan.io/tx/0xfac1f1821f67d646454e80e7b809898f8fe0282db07a51245c33ab74f7260c63#internal交易2:调用合约
B
的selfdestruct
方法销毁合约 https://goerli.etherscan.io/tx/0x163c501c3fc45d2e088c6a3af300685f5cd370cb0ba1be22e4d58de601a33bbf交易3:重复交易1 https://goerli.etherscan.io/tx/0x84004b0620cbc9ff0e972a611632b30b9b9abdbdd25b2e1e22af481b370cb805#internal
可以看到交易1
和交易3
创建的最终合约地址是一样的,都是0xD11e08dca07F7AD7acF2412F15CBec1b50390EF6
这里再重点说下一个函数:
// 这个函数就是保证create地址不变的关键。注意这里transientContractAddress地址不变是由create2来保证,nonce=0x01写死了,也是因为让临时合约selfdestruct来实现的。
function _getMetamorphicContractAddressWithConstructor(
address transientContractAddress
) internal pure returns (address) {
// determine the address of the metamorphic contract.
return address(
uint160( // downcast to match the address type.
uint256( // set to uint to truncate upper digits.
keccak256( // compute CREATE hash via RLP encoding.
abi.encodePacked( // pack all inputs to the hash together.
byte(0xd6), // first RLP byte.
byte(0x94), // second RLP byte.
transientContractAddress, // called by the transient contract.
byte(0x01) // nonce begins at 1 for contracts.
)
)
)
)
);
}
Metapod.sol
构造函数
这里有几个常量解释下
bytes private constant TRANSIENT_CONTRACT_INITIALIZATION_CODE = ( hex"58601c59585992335a6357b9f5235952fa5060403031813d03839281943ef08015602557ff5b80fd"
);
//这个转换成opcode是:
[1] PUSH1 0x1c
[2] MSIZE
[3] PC
[4] MSIZE
[5] SWAP3
[6] CALLER
[7] GAS
[12] PUSH4 0x57b9f523
[13] MSIZE
[14] MSTORE
[15] STATICCALL // 这里是调用caller的getInitializationCode()函数
[16] POP
[18] PUSH1 0x40
[19] ADDRESS
[20] BALANCE
[21] DUP2
[22] RETURNDATASIZE
[23] SUB
[24] DUP4
[25] SWAP3
[26] DUP2
[27] SWAP5
[28] RETURNDATACOPY
[29] CREATE // 调用create部署新合约
[30] DUP1
[31] ISZERO
[33] PUSH1 0x25
[34] JUMPI
[35] SELFDESTRUCT // 将自己kill掉
[36] JUMPDEST
[37] DUP1
[38] REVERT
// 可以看出来,实际上上面代码完成的,就是TransientContract合约构造函数的主要内容。
deploy
和deployMetamorphicContractWithConstructor
差不多 不过这里有个校验,需要特别警示:
_verifyPrelude(metamorphicContract, _getPrelude(vaultContract));
这是什么呢?文件头有说明:
Also, bear in mind that any initialization code provided to the contract must contain the proper prelude, or initial sequence, with a length of 44 bytes:
0x6e03212eb796dee588acdbbbd777d4e73318602b5773 + vault_address + 0xff5b
也就是说,我们所有部署的最终合约,都需要在头部写上这44个字节。干嘛用的? 看下对应的opcode就明白了:
[15] PUSH15 0x03212eb796dee588acdbbbd777d4e7
[16] CALLER
[17] XOR // 看看caller是不是0x03212eb796dee588acdbbbd777d4e7
[19] PUSH1 0x2b
[20] JUMPI
[41] PUSH20 0x0000000000000000000000000000000000000000
[42] SELFDESTRUCT // 如果是,就执行selfdestruct
[43] JUMPDEST // 否则就跳过这段代码
这是给合约0x03212eb796dee588acdbbbd777d4e7
留了一个后门,可以让这个caller去执行selfdestruct
来销毁合约。 这个地址,就是Metapod
合约本身。见构造函数:
require(
address(this) == address(0x000000000003212eb796dEE588acdbBbD777D4E7),
"Incorrect deployment address."
);
这里就有个疑问,合约部署的时候,怎么提前知道的地址?看下他的两个部署地址就知道了:
https://ropsten.etherscan.io/tx/0xabea820680d4c95b90f8eca3751b653846b2d1ca1ee4db966cfa6641ed345689#internal https://etherscan.io/tx/0x96b3b4508c899c773748242cfeedb9ddfc95c2c36e96d3c084ce572a418abe7e#internal
他们都是通过创建的新合约去部署的,那么合约的地址就可以在合约内拿到,nonce肯定是1咯,那就可以算出来了。 比如主网的这个:
> web3.utils.sha3("0xd69410ca1adca9ff38988d75dd1f6ee19b1a6bfa919701").slice(-40);
'00000000002b13cccec913420a21e4d11b2dcd3c'
destroy
有了上面deploy
的解释,那这个函数就不难理解了,调用selfdesctruct
函数销毁部署的合约。
recover
和deploy
差不多,只是多了个_triggerVaultFundsRelease
函数
有问题欢迎留言探讨
本文参与区块链技术网 ,好文好收益,欢迎正在阅读的你也加入。
- 发表于 2022-10-20 21:35
- 阅读 ( 353 )
- 学分 ( 21 )
- 分类:智能合约