CREATE2 是一个可以在合约中创建合约的操作码。我们先来举个例子看看它能干什么:
这段代码是 Uniswap v2-core 里面的工厂合约代码,使用 create2 操作码创建了 pair 合约,返回值是 pair 的地址,这样就可以逻辑中直接使用其地址进行接下来的操作。
那么 create2 到底是怎么使用呢,根据官方 EIP 文档,create2 一共接收四个参数,分别是:
这里要注意的是第一个参数如果大于 0 的话,需要待部署合约的构造方法带有 payable。随机数盐是由用户自定,须为 bytes32 格式,例如在上面 Uniswap 的例子中,salt 为:
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
create2 还有一个优点,相较于以前的 new 创建合约方法,可以在合约未创建之前就能够计算出合约的地址。我们之前在使用 new 创建新合约的时候,必须在取到合约对象之后,再取其 address 才能获取地址。而使用 create2,就可以这样提前计算出地址(参考):
// salt 为部署合约时使用的随机数盐
// bytecode 为合约的字节码哈希(keccak256)
// deployer 为部署合约的地址(在A合约中部署B合约,则此处为A)
function computeAddress(
bytes32 salt,
bytes32 bytecodeHash,
address deployer
) internal pure returns (address) {
bytes32 _data = keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, bytecodeHash));
return address(uint160(uint256(_data)));
}
能够提前计算出合约地址这一点,就会给我们许多想象力。比如我们可以让合约地址变成靓号(例如前缀地址是 0x000,0x666,0x888),原因就是 salt 是我们自己定义的,那么就可以像 POW 挖矿那样,不断找寻随机数,以达到目的。这里我们就不再对这个话题过多深入,感兴趣的同学可以看看 这里 或者 这里。
接下来我们就尝试一下使用 create2 部署合约,方便的是,OpenZeppelin 库中就有现成的模版可以供我们使用,先来看看它是怎么实现的:
注意到注释中对于参数的要求:
逻辑还是比较简单的,我们在使用的时候直接套用模版就行,来实操一下:
pragma solidity 0.8.10;
import "@openzeppelin/contracts/utils/Create2.sol";
contract Factory {
event Deployed(address addr);
// 计算合约地址的方法
function getAddress() public view returns (address) {
return
Create2.computeAddress(
keccak256("Here is salt"),
keccak256(
abi.encodePacked(type(Template).creationCode, abi.encode(3))
)
);
}
// 部署合约
function deploy() public {
address addr = Create2.deploy(
0,
keccak256("Here is salt"),
abi.encodePacked(type(Template).creationCode, abi.encode(3))
);
emit Deployed(addr);
}
}
contract Template {
uint256 public a;
constructor(uint256 _a) {
a = _a;
}
}
我们使用 keccak256("Here is salt") 为盐,当然这里可以使用任何 bytes32 类型的数据。
有一点需要注意的是,对于构造函数有参数的情况,需要将参数编码并拼接在合约字节码后面作为完整的字节码传入:
abi.encodePacked(type(Template).creationCode, abi.encode(3))
我们部署一下,先调用 getAddress 计算合约地址:
然后再调用 deploy 部署合约,在事件中查看部署的地址为:
验证地址确实相同。
这里还有一点需要说一下的是,如果要在 EtherScan 中上传代码,是需要将上面的所有合约,也就是 Factory 和 Template,包括 import 的合约都需要上传,仅仅上传 Template 是无法成功的,这里当时卡了我很长时间,最后试了试全部粘贴才能成功。
由于对于相同的合约、参数、盐,create2 计算所得的合约地址都是相同的,因此我们就可以通过 create2 与 selfdestruct 相结合,在同一个地址上多次部署合约。我在这篇文章中有详细介绍。
本文只介绍了 create2 的使用方法,其实它的应用场景还有很多,比如:
感兴趣的同学可以多看看学习学习。