赞
踩
部署智能合约是开发中必不可少的一个环节,常规的方式是借助像Hardhat这样的工具,通过编写ts部署脚本来实现。但在实际业务中,经常会遇到通过在合约中部署子合约的情况。比如添加Token流动池。这类需求在设计在上需要通过工厂合约来创建部署子合约来实现,它看起来就像是从一个模具厂生产的模具一样,只是每个模具的编号(子合约地址)不同。子合约的业务逻辑不是本次介绍的重点,我们主要关注在合约中部署合约的两种方式:
先确认子合约的内容,一个构造函数和简单的函数:
- contract Son {
-
- address token1;
- address token2;
-
- constructor(address _token1,address _token2) {
- token1 = _token1;
- token2 = _token2;
- }
-
- function addLiquidity() external pure returns(uint256){
- return 1;
- }
- }
create的部署方式是通过new关键字来实现,所部署合约的地址是通过哈希计算得来:
nonce
keccak256(rlp.encode(deployingAddress, nonce))
nonce每次获取的不一样,因此每次部署的合约地址不同。在solidity最新的版本中,只需通过内置的关键字new即可:
X x = new X()
- //新版本的create部署方式
- //0x4D1435CA4761C96ea6156AFf63A9F0D8D00D10c1
- //0xFF8C88bA69cbfba47BfC1ff84aB2cfA6a64B8429
- function create(address token1,address token2) external returns(address){
- Son son = new Son(token1,token2);
- return address(son);
- }
remix运行结果:
合约地址:0x5C9eb5D6a6C2c1B3EFc52255C0b356f116f6f66D,我们用相同的参数token1、token2,再次部署的结果:
合约地址: 0xb8f43EC36718ecCb339B75B727736ba14F174d77,可以看到与第一次的地址不同,create部署的好处是每次的部署地址不同,不用担心合约地址被占用导致部署失败的问题,缺点是无法通过计算得到合约地址,不适合一些需要提前知道合约地址的场景。
合约地址计算的方式:
salt
(32 字节字符串).keccak256(0xff ++ deployingAddr ++ salt ++ keccak256(bytecode))
相较于create,多了参数salt。
- //新版本的create2部署方式
- function create2(address token1,address token2) external returns(address){
- bytes32 salt = keccak256(abi.encodePacked(token1, token2));//盐值,可以是一个数字、一个字符串等,一般是随机数
- Son son = new Son{salt: salt}(token1,token2);
- return address(son);
- }
salt的获取规则是计算token1和token2的hash,也可以根据实际的业务进行调整。部署代码:
X x = new X{salt: salt}()
token1和token2仍然使用create中的参数值,remix运行结果:
合约地址为:0x8D0Cd60156182DF2263a41960c250Bd921047Bc3 ,与create部署的合约地址不同,因为底层的计算方式不同。现在我们用相同的参数再次部署:
已经报错,因为相同的salt计算的合约地址是相同的,而合约地址必须未被使用过。
我们用旧版本的create2来试试看
- //老版本的create2部署方式,通过solidity汇编语法实现
- function deployAssembly(address token1,address token2) external returns(address addr){
- bytes memory bytecode = getBytecode(token1, token2);
- bytes32 salt = keccak256(abi.encodePacked(token1, token2));//盐值,可以是一个数字、一个字符串等,一般是随机数
-
- assembly {
- //param1:发送给新合约的wei数(msg.value)
- //param2:存储位置(从32开始)和需要的长度(bytecode)
- //param3:存储在memory中
- //param4:随机值
- addr := create2(0,add(bytecode, 32),mload(bytecode),salt)
-
- if iszero(extcodesize(addr)) {
- revert(0, 0)
- }
- }
- }
-
-
- function getBytecode(address token1,address token2) internal pure returns(bytes memory bytecode){
- bytecode = type(Son).creationCode;//获取合约字节码,也可通过编译合约获取
- bytecode = abi.encodePacked(bytecode, abi.encode(token1, token2));
- }
需要提前通过creationCode()函数获取部署子合约的bytecode。部署结果:
可以看到结果仍然是失败,因为与上述计算的合约地址相同,只是实现方式不同。我们通过计算合约地址的函数来验证是否与部署成功的地址一致:
可以看到计算结果是:0x8D0Cd60156182DF2263a41960c250Bd921047Bc3,与create2函数返回的合约地址相同。
相较于create,create2提供了更多的灵活性,使得开发者可以预先计算合约地址,从而更好地管理合约地址的分配。同时,create2还可以用于实现一些更高级的功能,例如合约工厂、二级合约等。
需要注意的是,使用create2创建合约时,由于需要提供一个预计地址,因此需要确保该地址没有被使用过,否则可能会导致创建失败。此外,使用create2创建合约时,需要确保预计地址的计算规则是确定的,这样可以确保预计地址与实际地址的一致性。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。