以太坊创建合约的方式有2种:
CREATE(0xf0)
opcodeCREATE2(0xf5)
opcode// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;
contract OpCreates {
function opCreate(bytes memory bytecode, uint length) public returns(address) {
address addr;
assembly {
addr := create(0, 0xa0, length)
sstore(0x0, addr)
}
return addr;
}
function opCreate2(bytes memory bytecode, uint length) public returns(address) {
address addr;
assembly {
addr := create2(0, 0xa0, length, 0x2)
sstore(0x0, addr)
}
return addr;
}
function sendValue() public payable {
uint bal;
assembly{
bal := add(bal,callvalue())
sstore(0x1, bal)
}
}
function opCreateValue(bytes memory bytecode, uint length) public payable returns(address) {
address addr;
assembly {
addr := create(500, 0xa0, length)
sstore(0x0, addr)
}
return addr;
}
function opCreate2Value(bytes memory bytecode, uint length) public payable returns(address) {
address addr;
assembly {
addr := create2(300, 0xa0, length, 0x55555)
sstore(0x0, addr)
}
return addr;
}
}
由EOA账号直接创建的合约——合约地址计算规则,根据pyethereum有:
try:
from Crypto.Hash import keccak
sha3_256 = lambda x: keccak.new(digest_bits=256, data=x).digest()
except:
import sha3 as _sha3
sha3_256 = lambda x: _sha3.sha3_256(x).digest()
def mk_contract_address(sender, nonce):
return sha3(rlp.encode([normalize_address(sender), nonce]))[12:]
即采用sender-and-nonce-hash方式来计算合约地址。
以太坊合约的地址是根据creator地址(sender
) 和 该creator已发送的交易数(nonce
)经RLP编码再采用Keccak-256哈希运算 确定性计算而来的:【下述示例应进一步拓展为支持nonce最大值 2 64 2^{64} 264】
// Solidity 0.8及以上
function addressFrom(address _origin, uint _nonce) public pure returns (address) {
bytes memory data;
if (_nonce == 0x00) data = abi.encodePacked(byte(0xd6), byte(0x94), _origin, byte(0x80));
else if (_nonce <= 0x7f) data = abi.encodePacked(byte(0xd6), byte(0x94), _origin, byte(_nonce));
else if (_nonce <= 0xff) data = abi.encodePacked(byte(0xd7), byte(0x94), _origin, byte(0x81), uint8(_nonce));
else if (_nonce <= 0xffff) data = abi.encodePacked(byte(0xd8), byte(0x94), _origin, byte(0x82), uint16(_nonce));
else if (_nonce <= 0xffffff) data = abi.encodePacked(byte(0xd9), byte(0x94), _origin, byte(0x83), uint24(_nonce));
else data = abi.encodePacked(byte(0xda), byte(0x94), _origin, byte(0x84), uint32(_nonce));
return address(keccak256(data));
}
// Solidity 0.6
function addressFrom(address _origin, uint _nonce) public pure returns (address) {
bytes memory data;
if (_nonce == 0x00) data = abi.encodePacked(byte(0xd6), byte(0x94), _origin, byte(0x80));
else if (_nonce <= 0x7f) data = abi.encodePacked(byte(0xd6), byte(0x94), _origin, uint8(_nonce));
else if (_nonce <= 0xff) data = abi.encodePacked(byte(0xd7), byte(0x94), _origin, byte(0x81), uint8(_nonce));
else if (_nonce <= 0xffff) data = abi.encodePacked(byte(0xd8), byte(0x94), _origin, byte(0x82), uint16(_nonce));
else if (_nonce <= 0xffffff) data = abi.encodePacked(byte(0xd9), byte(0x94), _origin, byte(0x83), uint24(_nonce));
else data = abi.encodePacked(byte(0xda), byte(0x94), _origin, byte(0x84), uint32(_nonce));
return address(uint256(keccak256(data)));
}
可借助汇编代码进一步优化以节约gas费:
// Solidity 0.8及以上
function addressFrom(address _origin, uint _nonce) external pure returns (address _address) {
bytes memory data;
if(_nonce == 0x00) data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, bytes1(0x80));
else if(_nonce <= 0x7f) data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, uint8(_nonce));
else if(_nonce <= 0xff) data = abi.encodePacked(bytes1(0xd7), bytes1(0x94), _origin, bytes1(0x81), uint8(_nonce));
else if(_nonce <= 0xffff) data = abi.encodePacked(bytes1(0xd8), bytes1(0x94), _origin, bytes1(0x82), uint16(_nonce));
else if(_nonce <= 0xffffff) data = abi.encodePacked(bytes1(0xd9), bytes1(0x94), _origin, bytes1(0x83), uint24(_nonce));
else data = abi.encodePacked(bytes1(0xda), bytes1(0x94), _origin, bytes1(0x84), uint32(_nonce));
bytes32 hash = keccak256(data);
assembly {
mstore(0, hash)
_address := mload(0)
}
}
// Solidity 0.6
function addressFrom(address _origin, uint _nonce) public pure returns (address _address) {
bytes memory data;
if(_nonce == 0x00) data = abi.encodePacked(byte(0xd6), byte(0x94), _origin, byte(0x80));
else if(_nonce <= 0x7f) data = abi.encodePacked(byte(0xd6), byte(0x94), _origin, uint8(_nonce));
else if(_nonce <= 0xff) data = abi.encodePacked(byte(0xd7), byte(0x94), _origin, byte(0x81), uint8(_nonce));
else if(_nonce <= 0xffff) data = abi.encodePacked(byte(0xd8), byte(0x94), _origin, byte(0x82), uint16(_nonce));
else if(_nonce <= 0xffffff) data = abi.encodePacked(byte(0xd9), byte(0x94), _origin, byte(0x83), uint24(_nonce));
else data = abi.encodePacked(byte(0xda), byte(0x94), _origin, byte(0x84), uint32(_nonce));
bytes32 hash = keccak256(data);
assembly {
mstore(0, hash)
_address := mload(0)
}
}
在EIP-1014:Skinny CREATE2中,额外引入了新的opcode CREATE2(0xf5)
来创建智能合约。CREATE2(0xf5)
与CREATE(0xf0)
的工作模式相同,只是CREATE(0xf0)
计算合约地址采用sender-and-nonce-hash方式,而CREATE2(0xf5)
采用合约地址计算规则为:
keccak256( 0xff ++ senderAddress ++ salt ++ keccak256(init_code))[12:]
[1] How to calculate an Ethereum Contract’s address during its creation using the Solidity language?
[2] How is the address of an Ethereum contract computed?