Create2操作码使我们在智能合约部署在以太坊网络之前就能预测合约的地址。Uniswap创建Pair合约用的就是Create2而不是Create。
Create是如何计算地址
智能合约可以由其他合约和普通账户利用Create操作码创建。 在这两种情况下,新合约的地址都以相同的方式计算:创建者的地址(通常为部署的钱包地址或者合约地址)和nonce
(该地址发送交易的总数,对于合约账户是创建的合约总数,每创建一个合约nonce+1))的哈希。
新地址 = hash(创建者地址, nonce)
创建者地址不会变,但nonce
可能会随时间而改变,因此用Create创建的合约地址不好预测。
Create2是如何计算地址
Create2的目的是为了让合约地址独立于未来的事件。不管未来区块链上发生了什么,你都可以把合约部署在事先计算好的地址上。用Create2创建的合约地址由4个部分决定:
0xFF:一个常数,避免和Create冲突
创建者地址
salt:一个创建者给定的数值
待部署合约的字节吗(bytecode)
新地址 = hash("0xFF",创建者地址, salt, bytecode)
Create2确保,如果创建者使用 Create2和提供的salt
部署给定的合约bytecode
,它将存储在新地址中。
Pair
contract Pair{address public factory; // 工厂合约地址address public token0; // 代币1address public token1; // 代币2constructor() payable {factory = msg.sender;}// called once by the factory at time of deploymentfunction initialize(address _token0, address _token1) external {require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient checktoken0 = _token0;token1 = _token1;}}
Pair合约很简单,包含三个状态变量:factory,token()和token1。
构造函数constructor在部署时将factory赋值为工厂合约地址。initialize函数会在Pair合约创建的时候被工厂合约调用一次,将token()和token1更新为币对中两种代币的地址。
PairFactory2
contract PairFactory2{mapping(address => mapping(address => address)) public getPair; // 通过两个代币地址查Pair地址address[] public allPairs; // 保存所有Pair地址function createPair2(address tokenA, address tokenB) external returns (address pairAddr) {require(tokenA != tokenB, 'IDENTICAL_ADDRESSES'); //避免tokenA和tokenB相同产生的冲突// 计算用tokenA和tokenB地址计算salt(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); //将tokenA和tokenB按大小排序bytes32 salt = keccak256(abi.encodePacked(token0, token1));// 用create2部署新合约Pair pair = new Pair{salt: salt}(); // 调用新合约的initialize方法pair.initialize(tokenA, tokenB);// 更新地址mappairAddr = address(pair);allPairs.push(pairAddr);getPair[tokenA][tokenB] = pairAddr;getPair[tokenB][tokenA] = pairAddr;}
工厂合约(PairFactory2
)有两个状态变量getPair
是两个代币地址到币对地址的map
,方便根据代币找到币对地址;allPairs
是币对地址的数组,存储了所有币对地址。
PairFactory2
合约只有一个createPair2
函数,使用Create2根据输入的两个代币地址tokenA
和tokenB
来创建新的Pair
合约。其中
Pair pair = new Pair{salt: salt}();
就是利用Create2
创建合约的代码,非常简单,而salt
为token1
和token2
的hash
:
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
事先先计算Pair的地址
// 提前计算pair合约地址function calculateAddr(address tokenA, address tokenB) public view returns(address predictedAddress){require(tokenA != tokenB, 'IDENTICAL_ADDRESSES'); //避免tokenA和tokenB相同产生的冲突// 计算用tokenA和tokenB地址计算salt(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); //将tokenA和tokenB按大小排序bytes32 salt = keccak256(abi.encodePacked(token0, token1));// 计算合约地址方法 hash()predictedAddress = address(uint160(uint(keccak256(abi.encodePacked(bytes1(0xff),address(this),salt,keccak256(type(Pair).creationCode))))));}
我们写了一个calculateAddr
函数来事先计算tokenA
和tokenB
将会生成的Pair
地址。通过它,我们可以验证我们事先计算的地址和实际地址是否相同。
大家可以部署好PairFactory2
合约,然后用下面两个地址作为参数调用createPair2
,看看创建的币对地址是什么,是否与事先计算的地址一样:
WBNB地址: 0x2c44b726ADF1963cA47Af88B284C06f30380fC78地址:0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c