Alert Source Discuss
⚠️ Review Standards Track: ERC

ERC-5247: 智能合约可执行提案接口

用于创建和执行提案的接口。

Authors Zainan Victor Zhou (@xinbenlv)
Created 2022-07-13

摘要

本EIP提出了一种“智能合约可执行提案”的接口:提交到链上、记录在链上并可能在链上执行的提案。此类提案包括一系列关于函数调用的信息,包括目标合约地址、要传输的以太币值、gas 限制和 calldata。

动机

通常有必要将要执行的代码与代码的实际执行分开。

此 EIP 的一个典型用例是在去中心化自治组织 (DAO) 中。提案者将创建一个智能提案并为其辩护。然后,成员将选择是否支持该提案并相应地投票(参见 ERC-1202)。最后,当达成共识时,该提案将被执行。

第二个典型的用例是,可以有一个你信任的人,例如委托人、受托人或律师,或任何双边协作形式,首先编写一个智能提案,以某种方式进行讨论、批准,然后投入执行。

第三个用例是,一个人可以向第二个人提出“要约”,可能带有条件。智能提案可以作为要约提出,如果第二个人选择接受该提案,则可以执行它。

规范

本文档中的关键词“必须”、“禁止”、“必需”、“应该”、“不应该”、“推荐”、“可以”和“可选”应按照 RFC 2119 中的描述进行解释。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

interface IERC5247 {
    event ProposalCreated(
        address indexed proposer,
        uint256 indexed proposalId,
        address[] targets,
        uint256[] values,
        uint256[] gasLimits,
        bytes[] calldatas,
        bytes extraParams
    );

    event ProposalExecuted(
        address indexed executor,
        uint256 indexed proposalId,
        bytes extraParams
    );

    function createProposal(
        uint256 proposalId,
        address[] calldata targets,
        uint256[] calldata values,
        uint256[] calldata gasLimits,
        bytes[] calldata calldatas,
        bytes calldata extraParams
    ) external returns (uint256 registeredProposalId);

    function executeProposal(uint256 proposalId, bytes calldata extraParams) external;
}

理由

  • 最初,此接口是 ERC-1202 的一部分。但是,提案本身可能在投票之外有许多用例。投票可能不需要以任何特定格式进行。因此,我们决定解耦投票界面和提案界面
  • 数组用于 targetvaluecalldata,而不是单个变量,允许提案携带任意长的多个函数调用。
  • registeredProposalIdcreateProposal 中返回,因此该标准可以支持实现以决定他们自己的提案 ID 格式。

测试用例

一个简单的测试用例如下:

        it("Should work for a simple case", async function () {
            const { contract, erc721, owner } = await loadFixture(deployFixture);
            const callData1 = erc721.interface.encodeFunctionData("mint", [owner.address, 1]);
            const callData2 = erc721.interface.encodeFunctionData("mint", [owner.address, 2]);
            await contract.connect(owner)
                .createProposal(
                    0,
                    [erc721.address, erc721.address],
                    [0,0],
                    [0,0],
                    [callData1, callData2],
                    []);
            expect(await erc721.balanceOf(owner.address)).to.equal(0);
            await contract.connect(owner).executeProposal(0, []);
            expect(await erc721.balanceOf(owner.address)).to.equal(2);
        });

有关完整的测试集,请参见 testProposalRegistry.ts

参考实现

可以找到一个简单的参考实现。

    function createProposal(
        uint256 proposalId,
        address[] calldata targets,
        uint256[] calldata values,
        uint256[] calldata gasLimits,
        bytes[] calldata calldatas,
        bytes calldata extraParams
    ) external returns (uint256 registeredProposalId) {
        require(targets.length == values.length, "GeneralForwarder: targets and values length mismatch");
        require(targets.length == gasLimits.length, "GeneralForwarder: targets and gasLimits length mismatch");
        require(targets.length == calldatas.length, "GeneralForwarder: targets and calldatas length mismatch");
        registeredProposalId = proposalCount;
        proposalCount++;

        proposals[registeredProposalId] = Proposal({
            by: msg.sender,
            proposalId: proposalId,
            targets: targets,
            values: values,
            calldatas: calldatas,
            gasLimits: gasLimits
        });
        emit ProposalCreated(msg.sender, proposalId, targets, values, gasLimits, calldatas, extraParams);
        return registeredProposalId;
    }
    function executeProposal(uint256 proposalId, bytes calldata extraParams) external {
        Proposal storage proposal = proposals[proposalId];
        address[] memory targets = proposal.targets;
        string memory errorMessage = "Governor: call reverted without message";
        for (uint256 i = 0; i < targets.length; ++i) {
            (bool success, bytes memory returndata) = proposal.targets[i].call{value: proposal.values[i]}(proposal.calldatas[i]);
            Address.verifyCallResult(success, returndata, errorMessage);
        }
        emit ProposalExecuted(msg.sender, proposalId, extraParams);
    }

有关更多信息,请参见 ProposalRegistry.sol

安全考虑

需要讨论。

版权

通过 CC0 放弃版权和相关权利。

Citation

Please cite this document as:

Zainan Victor Zhou (@xinbenlv), "ERC-5247: 智能合约可执行提案接口 [DRAFT]," Ethereum Improvement Proposals, no. 5247, July 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5247.