阅读(1522) (13)

通过new创建合约

2022-05-12 11:16:52 更新

一个合约可以使用new关键字创建其他合约。在编译创建合约时,必须知道正在创建的合约的完整代码,因此递归创建依赖项是不可能的。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract D {
    uint public x;
    constructor(uint a) payable {
        x = a;
    }
}

contract C {
    D d = new D(4); // will be executed as part of C's constructor

    function createD(uint arg) public {
        D newD = new D(arg);
        newD.x();
    }

    function createAndEndowD(uint arg, uint amount) public payable {
        // Send ether along with the creation
        D newD = new D{value: amount}(arg);
        newD.x();
    }
}

D如示例所示,可以在创建使用该选项的实例时发送 Ether value,但无法限制 gas 量。如果创建失败(由于出栈、余额不足或其他问题),则抛出异常。

加盐合约创建 / create2

创建合约时,合约的地址是根据创建合约的地址和随着每次合约创建而增加的计数器计算出来的。

如果您指定选项salt(bytes32 值),则合约创建将使用不同的机制来提供新合约的地址:

它将根据创建合约的地址、给定的盐值、创建合约的(创建)字节码和构造函数参数来计算地址。

特别是,不使用计数器(“nonce”)。这为创建合约提供了更大的灵活性:您可以在创建新合约之前获取它的地址。此外,您也可以依赖此地址,以防创建合约同时创建其他合约。

这里的主要用例是充当链下交互法官的合约,只有在有争议时才需要创建。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract D {
    uint public x;
    constructor(uint a) {
        x = a;
    }
}

contract C {
    function createDSalted(bytes32 salt, uint arg) public {
        // This complicated expression just tells you how the address
        // can be pre-computed. It is just there for illustration.
        // You actually only need ``new D{salt: salt}(arg)``.
        address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked(
            bytes1(0xff),
            address(this),
            salt,
            keccak256(abi.encodePacked(
                type(D).creationCode,
                abi.encode(arg)
            ))
        )))));

        D d = new D{salt: salt}(arg);
        require(address(d) == predictedAddress);
    }
}

警告

盐渍创作有一些特殊性。合约被销毁后,可以在同一地址重新创建。然而,即使创建的字节码是相同的,新创建的合约也有可能拥有不同的部署字节码(这是一个要求,否则地址会改变)。这是因为构造函数可以查询可能在两次创建之间发生变化的外部状态,并在存储之前将其合并到部署的字节码中。