以太坊合约地址计算

1. 引言

前序博客有:

以太坊合约实现中,为实现可升级性、地址一致性、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. 由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)采用合约地址计算规则为:

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值