Smart Contract Executable Proposal Interface
ERC-5247 proposes an interface for creating and executing proposals on the blockchain. These proposals can include information about function calls, target contract addresses, ether values, gas limits, and calldatas. The proposal can be submitted, recorded, and possibly executed on-chain. This interface can be useful in decentralized autonomous organizations (DAOs), bilateral collaborations, and making offers with conditions. The proposal is first composed, discussed, and approved before being executed. The proposal can be endorsed and voted on by members before consensus is formed. The proposal interface includes a reference implementation and security considerations that need further discussion.
Video
Original
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 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 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
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
target
s,value
s,calldata
s instead of single variables, allowing a proposal to carry arbitrarily long multiple functional calls. registeredProposalId
is returned increateProposal
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", 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 for the whole testset.
Reference Implementation
A simple reference implementation can be found.
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 for more information.
Security Considerations
Needs discussion.
Copyright
Copyright and related rights waived via CC0.
Adopted by projects
Not miss a beat of EIPs' update?
Subscribe EIPs Fun to receive the latest updates of EIPs Good for Buidlers to follow up.
View all