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 ERC-1202). 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.
// SPDX-License-Identifier: MIT
pragmasolidity^0.8.17;interfaceIERC5247{eventProposalCreated(addressindexedproposer,uint256indexedproposalId,address[]targets,uint256[]values,uint256[]gasLimits,bytes[]calldatas,bytesextraParams);eventProposalExecuted(addressindexedexecutor,uint256indexedproposalId,bytesextraParams);functioncreateProposal(uint256proposalId,address[]calldatatargets,uint256[]calldatavalues,uint256[]calldatagasLimits,bytes[]calldatacalldatas,bytescalldataextraParams)externalreturns(uint256registeredProposalId);functionexecuteProposal(uint256proposalId,bytescalldataextraParams)external;}
Rationale
Originally, this interface was part of part of ERC-1202. 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 targets, values, calldatas 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
it("Should work for a simple case",asyncfunction(){const{contract,erc721,owner}=awaitloadFixture(deployFixture);constcallData1=erc721.interface.encodeFunctionData("mint",[owner.address,1]);constcallData2=erc721.interface.encodeFunctionData("mint",[owner.address,2]);awaitcontract.connect(owner).createProposal(0,[erc721.address,erc721.address],[0,0],[0,0],[callData1,callData2],[]);expect(awaiterc721.balanceOf(owner.address)).to.equal(0);awaitcontract.connect(owner).executeProposal(0,[]);expect(awaiterc721.balanceOf(owner.address)).to.equal(2);});
functioncreateProposal(uint256proposalId,address[]calldatatargets,uint256[]calldatavalues,uint256[]calldatagasLimits,bytes[]calldatacalldatas,bytescalldataextraParams)externalreturns(uint256registeredProposalId){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});emitProposalCreated(msg.sender,proposalId,targets,values,gasLimits,calldatas,extraParams);returnregisteredProposalId;}functionexecuteProposal(uint256proposalId,bytescalldataextraParams)external{Proposalstorageproposal=proposals[proposalId];address[]memorytargets=proposal.targets;stringmemoryerrorMessage="Governor: call reverted without message";for(uint256i=0;i<targets.length;++i){(boolsuccess,bytesmemoryreturndata)=proposal.targets[i].call{value:proposal.values[i]}(proposal.calldatas[i]);Address.verifyCallResult(success,returndata,errorMessage);}emitProposalExecuted(msg.sender,proposalId,extraParams);}