create2
是 EVM 中的一個 OpCode,用於 Deploy 一個合約中的子合約,且其地址由 Deployer 決定而非任何區塊鏈上的 State,這代表使用 create2
創造出來的合約地址是可以預先計算出來的。
地址計算公式為:
keccak256( 0xff ++ address ++ salt ++ keccak256(init_code))[12:]
0xff
: 255,為一個常數address
: 20 bytes,此地只是 deploy New Contract 的 Deployersalt
: 32 bytes,User 提供的一個 input,通常為一個隨機數init_code
: 為一組創建合約用的 bytecode,constructor
和其參數都會被包含在 init_code
裡面,下文有範例可看在 Solidity 中使用 create2
有幾種方法:
new
contract我們可以從 Precompute Contract Address with Create2 查看實例:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Factory {
// Returns the address of the newly deployed contract
function deploy(
address _owner,
uint _foo,
bytes32 _salt
) public payable returns (address) {
// This syntax is a newer way to invoke create2 without assembly, you just need to pass salt
// https://docs.soliditylang.org/en/latest/control-structures.html#salted-contract-creations-create2
return address(new TestContract{salt: _salt}(_owner, _foo));
}
}
// This is the older way of doing it using assembly
contract FactoryAssembly {
event Deployed(address addr, uint salt);
// 1. Get bytecode of contract to be deployed
// NOTE: _owner and _foo are arguments of the TestContract's constructor
function getBytecode(address _owner, uint _foo) public pure returns (bytes memory) {
bytes memory bytecode = type(TestContract).creationCode;
return abi.encodePacked(bytecode, abi.encode(_owner, _foo));
}
// 2. Compute the address of the contract to be deployed
// NOTE: _salt is a random number used to create an address
function getAddress(bytes memory bytecode, uint _salt)
public
view
returns (address)
{
bytes32 hash = keccak256(
abi.encodePacked(bytes1(0xff), address(this), _salt, keccak256(bytecode))
);
// NOTE: cast last 20 bytes of hash to address
return address(uint160(uint(hash)));
}
// 3. Deploy the contract
// NOTE:
// Check the event log Deployed which contains the address of the deployed TestContract.
// The address in the log should equal the address computed from above.
function deploy(bytes memory bytecode, uint _salt) public payable {
address addr;
/*
NOTE: How to call create2
create2(v, p, n, s)
create new contract with code at memory p to p + n
and send v wei
and return the new address
where new address = first 20 bytes of keccak256(0xff + address(this) + s + keccak256(mem[p…(p+n)))
s = big-endian 256-bit value
*/
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)
}
}
emit Deployed(addr, _salt);
}
}
contract TestContract {
address public owner;
uint public foo;
constructor(address _owner, uint _foo) payable {
owner = _owner;
foo = _foo;
}
function getBalance() public view returns (uint) {
return address(this).balance;
}
}
上述 addr := create2(callvalue(), add(bytecode, 0x20), mload(bytecode), salt)
中:
add(bytecode, 0x20)
用來載入初始的 codemload(bytecode)
用來載入初始 code 的 sizecallvalue()
可以為 0bytecode
為 constructor 和其參數一起編譯後產生的 bytecode計算合約地址的實例:
function calCreate2Address(creatorAddress, salt, bytecode){
shaContent = '0xff'
+ creatorAddress.slice(2)
+ web3.eth.abi.encodeParameter('uint256',salt).slice(2).toString()
+ web3.utils.sha3(bytecode).slice(2).toString();
addr = "0x" + web3.utils.sha3(shaContent).slice(-40);
return addr;
}
new
以下內容參考自官方文件: Solidity - Creating Contracts via new
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract D {
uint public x;
constructor(uint a) payable {
x = a;
}
}
contract C {
D d = new D(4); // will be executed as part of C's constructor
function createD(uint arg) public {
D newD = new D(arg);
newD.x();
}
function createAndEndowD(uint arg, uint amount) public payable {
// Send ether along with the creation
D newD = new D{value: amount}(arg);
newD.x();
}
}
從以上的例子我們可以知道如果我們要使用 salt
(a bytes32 value) 這個參數,就會是另外一個創建合約的機制,且這個過程不會讓 contract nonce 上升。 如果我們不斷從一個一樣的 sender 送出 create2,且參數都是一樣的 init_code 和 salt 就能夠生產合約到一樣的 address 上。如果合約擁有 selfdestruct 並且搭配其他鏈上狀態(區塊資訊或者其他合約的 body),就可以在同一個地址上不斷更換合約程式碼。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract D {
uint public x;
constructor(uint a) {
x = a;
}
}
contract C {
function createDSalted(bytes32 salt, uint arg) public {
// This complicated expression just tells you how the address
// can be pre-computed. It is just there for illustration.
// You actually only need ``new D{salt: salt}(arg)``.
address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked(
bytes1(0xff),
address(this),
salt,
keccak256(abi.encodePacked(
type(D).creationCode,
arg
))
)))));
D d = new D{salt: salt}(arg);
require(address(d) == predictedAddress);
}
}
selfdesturct()
存在,那就有可能把合約銷毀並且重新佈署一次合約在相同地址上。create2
。create
opcode 是使用呼叫者合約的 nonce counter 還有其地址來算出目標合約的地址,無論合約創建成功與否 nonce 都會增加,這代表舊的 nonce 都不能再被用於交易。因此在 create2
之後有心人士可以找出一個加密參數,以此來故意去碰撞產生相同的佈署地址。特別拉 Nonce 出來講,在 geth 中 CREATE 和 CREATE2 都是執行同一個 [create](https://github.com/ethereum/go-ethereum/blob/master/core/vm/evm.go#L408-L482)
,在 EIP158 和EIP-161: State trie clearing (invariant-preserving alternative) 規定了 contract nonce 是從 1 開始。
if evm.chainRules.IsEIP158 {
evm.StateDB.SetNonce(address, 1)
}
...
ret, err := run(evm, contract, nil, false)
關於 contract nonce 在未來 nonce 的主題篇幅會講述。
最後歡迎大家拍打餵食大學生
0x2b83c71A59b926137D3E1f37EF20394d0495d72d