1. 引言
前序博客有:
- 以太坊proxy合约
以太坊合约实现中,为实现可升级性、地址一致性、state和data 与 合约逻辑 分离,proxy承担了重要的角色。在proxy合约中,合约部署主要采用过CREATE(0xf0)
和CREATE2(0xf5)
opcode。
以太坊创建合约的方式有2种:
- 1)由EOA账号直接创建合约
- 2)由其它智能合约创建智能合约
- 2.1)通过
CREATE(0xf0)
opcode:合约地址由keccak256(sender, nonce)
确定。CREATE
方式简单,但存在不同网络重放问题,详细见Wintermute hack on Optimism攻击。 - 2.2)通过
CREATE2(0xf5)
opcode:合约地址由keccak256(0xff, sender, salt, keccak256(bytecode))
确定。引入0xff
是为了避免CREATE2
创建的合约地址与CREATE
的发生碰撞。因为0xff
byte,作为RLP编码的starting byte时,对应的数据结构长度将为PB(petabytes)级,这在以太坊合约及数据结构中是不可能达到的。
借助CREATE2
,可能可通过Metamorphosis Smart Contract Pattern,将不同的bytecode部署到同一地址。详细参看Metamorphosis Smart Contracts using CREATE2。
为避免不同链之间的重放,推荐使用基于old salt和unique data(如inputs或unique data hash)来派生new salt:bytes20 newSalt = bytes20(keccak256(abi.encodePacked(_initializerData, _salt)));
。
// 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; } }
- 2.1)通过
2. 由EOA账号直接创建的合约——合约地址计算
由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)
}
}
3. 由其它智能合约创建合约——合约地址计算
在EIP-1014:Skinny CREATE2中,额外引入了新的opcode CREATE2(0xf5)
来创建智能合约。CREATE2(0xf5)
与CREATE(0xf0)
的工作模式相同,只是CREATE(0xf0)
计算合约地址采用sender-and-nonce-hash方式,而CREATE2(0xf5)
采用合约地址计算规则为:文章来源:https://uudwc.com/A/jrg6Y
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?
[3] 2023年4月博客 Deep Dive into Smart Contract Proxies: Variants, CREATE vs. CREATE2, and Security Considerations文章来源地址https://uudwc.com/A/jrg6Y