--- eip: 5247 title: Smart Contract Executable Proposal Interface description: An interface to create and execute proposals. author: Zainan Victor Zhou (@xinbenlv) discussions-to: https://ethereum-magicians.org/t/erc-5247-executable-proposal-standard/9938 status: Draft type: Standards Track category: ERC created: 2022-07-13 --- ## Abstract This EIP presents an interface for "smart contract executable proposals": proposals that are submitted to, recorded on, and possibly executed on-chain. Such proposals include a series of information about function calls including the target contract address, ether value to be transmitted, gas limits and calldatas. ## Motivation It is oftentimes necessary to separate the code that is to be executed from the actual execution of the code. A typical use case for this EIP is in a Decentralized Autonomous Organization (DAO). A proposer will create a smart proposal and advocate for it. Members will then choose whether or not to endorse the proposal and vote accordingly (see [EIP-1202](./eip-1202.md)). Finallym when consensus has been formed, the proposal is executed. A second typical use-case is that one could have someone who they trust, such as a delegator, trustee, or an attorney-in-fact, or any bilateral collaboration format, where a smart proposal will be first composed, discussed, approved in some way, and then put into execution. A third use-case is that a person could make an "offer" to a second person, potentially with conditions. The smart proposal can be presented as an offer and the second person can execute it if they choose to accept this proposal. ## Specification The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. ```solidity // 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; } ``` ## Rationale * Originally, this interface was part of part of [EIP-1202](./eip-1202.md). However, the proposal itself can potentially have many use cases outside of voting. It is possible that voting may not need to be upon a proposal in any particular format. Hence, we decide to *decouple the voting interface and proposal interface*. * Arrays were used for `target`s, `value`s, `calldata`s instead of single variables, allowing a proposal to carry arbitrarily long multiple functional calls. * `registeredProposalId` is returned in `createProposal` so the standard can support implementation to decide their own format of proposal id. ## Test Cases A simple test case can be found as ```ts 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); }); ``` See [testProposalRegistry.ts](../assets/eip-5247/testProposalRegistry.ts) for the whole testset. ## Reference Implementation A simple reference implementation can be found. ```solidity 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); } ``` See [ProposalRegistry.sol](../assets/eip-5247/ProposalRegistry.sol) for more information. ## Security Considerations Needs discussion. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).