DCIPs/EIPS/eip-5247.md

148 lines
6.2 KiB
Markdown

---
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).