在外部创建合约

Create a contract externally

据我所知,使用 new 运算符在合约内部创建合约会将新创建合约的字节码附加到创建合约,这有效地扩展了原始合约。因此,合同大小可能会超过部署中的 24KB 限制。

我想知道是否有一种合约创建模式,新合约与原始合约分离,并且仍然可以以现有方式访问。谢谢

下面是在合约中发起新合约的例子。查看字节码了解我的意思。

// SPDX-License-Identifier: MIT

pragma solidity 0.8.4;

import "./1_Storage.sol";

contract Test {
    
    Storage stg;
    constructor() {
        stg = new Storage();
    }
    
    function getter() public view returns(uint256) {
        return stg.retrieve();
    }
    
    function setter(uint256 val) public {
        stg.store(val);
    }
}

部署后,字节码是不可变的(selfdestructcreate2 操作码的组合除外,它们允许在同一地址上销毁和重新部署新的字节码)。

stg = new Storage();Storage 的实例部署到新地址,returns 部署 stg 中的实例。 它不扩展已经部署的字节码。

event LogAddress(address _address);
    
Storage stg;
constructor() {
    stg = new Storage();
    emit LogAddress(address(stg));
}

returns from(发射 Test 合约)和 _address(部署 Storage 合约)

的不同值
[{
    "from": "0x7EF2e0048f5bAeDe046f6BF797943daF4ED8CB47",
    "topic": "0xb123f68b8ba02b447d91a6629e121111b7dd6061ff418a60139c8bf00522a284",
    "event": "LogAddress",
    "args": {
        "0": "0xD9eC9E840Bb5Df076DBbb488d01485058f421e58",
        "_address": "0xD9eC9E840Bb5Df076DBbb488d01485058f421e58"
    }
}]

由于 import "./1_Storage.sol"; 语句,Test 合约地址的字节码也包含 Storage 合约定义。但是如果你 运行 stg = new Storage(); 10 次,原始字节码不会改变 - 它只会将 Storage 部署到 10 个不同的地址。

您可以通过将构造函数外部的 stg = new Storage(); 移动到单独的函数来测试它。你会看到执行单独的函数后,Test地址上的字节码不会改变。


编辑:如果你想减小“基础”工厂合约的大小,你可以只部署代理到你的实现中。实现仍然需要部署在某个地方,但它可以在你的合同之外。

在地址 0x123

上实施
pragma solidity ^0.8;

contract Storage
    bool initialized;
    uint256 value;

    constructor() {
        initialized = true;
    }

    function init() external {
        require (!initialized, 'Already initialized');
        initialized = true;
    }

    function retrieve() external returns (uint256) {
        return value;
    }

    function setter(uint256 val) external {
        value = val;
    }
}

工厂地址 0x456

pragma solidity ^0.8;

interface Storage {
    // only need the `init()` definition, other functions are not used here
    function init() external;

    // mind that this is just an interface, the implementation is on address 0x123
}

contract Factory {
    function createStorage() external returns (address target) {
        // 0x123 is the implementation address
        address target = createMinimalProxy(address(0x123));
        // init function instead of constructor (because the contract has already been deployed), you can pass arguments if you need
        Storage(target).init();
    }

    function createMinimalProxy(address _implementation) internal returns (address result) {
        bytes20 implementationBytes = bytes20(_implementation);
        assembly {
            let clone := mload(0x40)
            mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
            mstore(add(clone, 0x14), implementationBytes)
            mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
            result := create(0, clone, 0x37)
        }
    }
}

这个例子使用了EIP-1667 minimal proxy(它实际上是在createMinimalProxy()函数中内置的45个字节)。

每次执行 createMinimalProxy() 时,它都会将最小代理(45 字节)部署到新地址和 returns 新地址。

由于 delegatecall 指令(在代理合约中),当用户调用(或在其上执行函数)代理时,调用使用实现的字节码 - 但存储代理的。 所以多个代理可以指向相同的实现,它们都使用单独的存储。

例如,您可以在 this article 中找到另一个代码片段。