forked from DecentralizedClimateFoundation/DCIPs
148 lines
6.2 KiB
Markdown
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).
|