计算合约地址
本章讲解在 Solidity
中,新部署的智能合约的地址如何计算。
在以太坊中,合约地址的生成规则是由合约的创建方式、创建者地址,以及随机数等多个因素共同确定的。
不同的合约创建方式,生成合约地址的规则也不相同。
1. 通过外部账户创建合约
使用外部账户EOA
在以太坊上,创建一个智能合约,那么合约地址的生成规则为:
将创建者地址与 nonce
值一起进行 rlp
编码,再进行 keccak256
哈希计算,最后取其后 20 个字节。
这是在以太坊客户端中,使用了 go
语言生成合约地址的代码:
func CreateAddress(b common.Address, nonce uint64) common.Address { data, _ := rlp.EncodeToBytes([]interface{}{b, nonce}) return common.BytesToAddress(Keccak256(data)[12:]) }
我们也可以使用 js
语言,提前计算将要创建的智能合约的地址:
const { ethers } = require('ethers') const ownerAddress = '0x7Cc9108d881a0141e5eF3326Cc5B5f15621b6998'; const ownerNonce = 464; const address = ethers.utils.getContractAddress({ from: ownerAddress, nonce: ownerNonce, }); console.log(address);
其中,creatorAddress
是创建合约的外部账户的地址。
creatorNonce
是创建者的 nonce
值,它表示了创建者发出的交易数量,通常是递增的。
2. 合约内创建 CREATE 方式
在智能合约内,可以使用 CREATE
方式来创建一个合约。它与使用外部账户EOA
创建智能合约的地址生成规则一样。
在 Solidity 中使用 new 创建合约,或者通过内联汇编的 create 指令创建合约。合约地址由EVM 生成并返回。
具体规则与外部账户创建相同,由创建者的地址和 nonce 计算而成。
func CreateAddress(b common.Address, nonce uint64) common.Address { data, _ := rlp.EncodeToBytes([]interface{}{b, nonce}) return common.BytesToAddress(Keccak256(data)[12:]) }
3. 合约内创建 CREATE2 方式
CREATE2
与 CREATE
相比,在创建合约时,为部署者提供了更多的控制权来决定合约的地址,常用于合约工厂、可升级合约等场景中。
使用 CREATE2
创建合约时,计算公式为:
具体规则为:
address = keccak256(0xFF, creatorAddress, salt, initCodeHash)。
生成的地址由4个部分决定:
- 0xFF
一个固定常数,避免和create
创建的合约地址冲突。 - creatorAddress
合约创建者地址。 - salt
合约创建者给定的数值,在哈希函数充当盐值。 - initCodeHash
待部署合约的字节码的哈希值。
下面合约中的 predictCreate2Address
函数用于计算新创建合约的地址:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // 被创建合约 contract Callee { string value1; string value2; // 构造函数有两个参数 constructor(string memory _value1,string memory _value2) { value1 = _value1; value2 = _value2; } } // 合约创建者 contract ContractCreator { // 计算合约地址 function predictCreate2Address(string memory value1,string memory value2) external view returns (address) { // 生成盐值 bytes32 salt = keccak256(abi.encodePacked(value1,value2)); // 将合约字节码和参数 value1, value2 打包编码 bytes memory bytecode = abi.encodePacked( type(Callee).creationCode, abi.encode(value1, value2) ); // 计算新合约所有参数的 hash 值 bytes32 hash = keccak256(abi.encodePacked( bytes1(0xff), address(this), salt, keccak256(bytecode) )); // 截取 hash 值右边的160位,作为新合约地址 address predictedAddress = address(uint160(uint(hash))); return predictedAddress; } }