-
Notifications
You must be signed in to change notification settings - Fork 11.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
- Loading branch information
Showing
39 changed files
with
5,302 additions
and
377 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,357 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import "../utils/cryptography/ECDSA.sol"; | ||
import "../utils/cryptography/draft-EIP712.sol"; | ||
import "../utils/introspection/ERC165.sol"; | ||
import "../utils/math/SafeCast.sol"; | ||
import "../utils/Address.sol"; | ||
import "../utils/Context.sol"; | ||
import "../utils/Timers.sol"; | ||
import "./IGovernor.sol"; | ||
|
||
/** | ||
* @dev Core of the governance system, designed to be extended though various modules. | ||
* | ||
* This contract is abstract and requiers several function to be implemented in various modules: | ||
* | ||
* - A counting module must implement {quorum}, {_quorumReached}, {_voteSucceeded} and {_countVote} | ||
* - A voting module must implement {getVotes} | ||
* - Additionanly, the {votingPeriod} must also be implemented | ||
* | ||
* _Available since v4.3._ | ||
*/ | ||
abstract contract Governor is Context, ERC165, EIP712, IGovernor { | ||
using SafeCast for uint256; | ||
using Timers for Timers.BlockNumber; | ||
|
||
bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,uint8 support)"); | ||
|
||
struct ProposalCore { | ||
Timers.BlockNumber voteStart; | ||
Timers.BlockNumber voteEnd; | ||
bool executed; | ||
bool canceled; | ||
} | ||
|
||
string private _name; | ||
|
||
mapping(uint256 => ProposalCore) private _proposals; | ||
|
||
/** | ||
* @dev Restrict access to governor executing address. Some module might override the _executor function to make | ||
* sure this modifier is consistant with the execution model. | ||
*/ | ||
modifier onlyGovernance() { | ||
require(_msgSender() == _executor(), "Governor: onlyGovernance"); | ||
_; | ||
} | ||
|
||
/** | ||
* @dev Sets the value for {name} and {version} | ||
*/ | ||
constructor(string memory name_) EIP712(name_, version()) { | ||
_name = name_; | ||
} | ||
|
||
/** | ||
* @dev See {IERC165-supportsInterface}. | ||
*/ | ||
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) { | ||
return interfaceId == type(IGovernor).interfaceId || super.supportsInterface(interfaceId); | ||
} | ||
|
||
/** | ||
* @dev See {IGovernor-name}. | ||
*/ | ||
function name() public view virtual override returns (string memory) { | ||
return _name; | ||
} | ||
|
||
/** | ||
* @dev See {IGovernor-version}. | ||
*/ | ||
function version() public view virtual override returns (string memory) { | ||
return "1"; | ||
} | ||
|
||
/** | ||
* @dev See {IGovernor-hashProposal}. | ||
* | ||
* The proposal id is produced by hashing the RLC encoded `targets` array, the `values` array, the `calldatas` array | ||
* and the descriptionHash (bytes32 which itself is the keccak256 hash of the description string). This proposal id | ||
* can be produced from the proposal data which is part of the {ProposalCreated} event. It can even be computed in | ||
* advance, before the proposal is submitted. | ||
* | ||
* Note that the chainId and the governor address are not part of the proposal id computation. Consequently, the | ||
* same proposal (with same operation and same description) will have the same id if submitted on multiple governors | ||
* accross multiple networks. This also means that in order to execute the same operation twice (on the same | ||
* governor) the proposer will have to change the description in order to avoid proposal id conflicts. | ||
*/ | ||
function hashProposal( | ||
address[] memory targets, | ||
uint256[] memory values, | ||
bytes[] memory calldatas, | ||
bytes32 descriptionHash | ||
) public pure virtual override returns (uint256) { | ||
return uint256(keccak256(abi.encode(targets, values, calldatas, descriptionHash))); | ||
} | ||
|
||
/** | ||
* @dev See {IGovernor-state}. | ||
*/ | ||
function state(uint256 proposalId) public view virtual override returns (ProposalState) { | ||
ProposalCore memory proposal = _proposals[proposalId]; | ||
|
||
if (proposal.executed) { | ||
return ProposalState.Executed; | ||
} else if (proposal.canceled) { | ||
return ProposalState.Canceled; | ||
} else if (proposal.voteStart.isPending()) { | ||
return ProposalState.Pending; | ||
} else if (proposal.voteEnd.isPending()) { | ||
return ProposalState.Active; | ||
} else if (proposal.voteEnd.isExpired()) { | ||
return | ||
_quorumReached(proposalId) && _voteSucceeded(proposalId) | ||
? ProposalState.Succeeded | ||
: ProposalState.Defeated; | ||
} else { | ||
revert("Governor: unknown proposal id"); | ||
} | ||
} | ||
|
||
/** | ||
* @dev See {IGovernor-proposalSnapshot}. | ||
*/ | ||
function proposalSnapshot(uint256 proposalId) public view virtual override returns (uint256) { | ||
return _proposals[proposalId].voteStart.getDeadline(); | ||
} | ||
|
||
/** | ||
* @dev See {IGovernor-proposalDeadline}. | ||
*/ | ||
function proposalDeadline(uint256 proposalId) public view virtual override returns (uint256) { | ||
return _proposals[proposalId].voteEnd.getDeadline(); | ||
} | ||
|
||
/** | ||
* @dev See {IGovernor-votingDelay} | ||
*/ | ||
function votingDelay() public view virtual override returns (uint256); | ||
|
||
/** | ||
* @dev See {IGovernor-votingPeriod} | ||
*/ | ||
function votingPeriod() public view virtual override returns (uint256); | ||
|
||
/** | ||
* @dev See {IGovernor-quorum} | ||
*/ | ||
function quorum(uint256 blockNumber) public view virtual override returns (uint256); | ||
|
||
/** | ||
* @dev See {IGovernor-getVotes} | ||
*/ | ||
function getVotes(address account, uint256 blockNumber) public view virtual override returns (uint256); | ||
|
||
/** | ||
* @dev Amount of votes already casted passes the threshold limit. | ||
*/ | ||
function _quorumReached(uint256 proposalId) internal view virtual returns (bool); | ||
|
||
/** | ||
* @dev Is the proposal successful or not. | ||
*/ | ||
function _voteSucceeded(uint256 proposalId) internal view virtual returns (bool); | ||
|
||
/** | ||
* @dev Register a vote with a given support and voting weight. | ||
* | ||
* Note: Support is generic and can represent various things depending on the voting system used. | ||
*/ | ||
function _countVote( | ||
uint256 proposalId, | ||
address account, | ||
uint8 support, | ||
uint256 weight | ||
) internal virtual; | ||
|
||
/** | ||
* @dev See {IGovernor-propose}. | ||
*/ | ||
function propose( | ||
address[] memory targets, | ||
uint256[] memory values, | ||
bytes[] memory calldatas, | ||
string memory description | ||
) public virtual override returns (uint256) { | ||
uint256 proposalId = hashProposal(targets, values, calldatas, keccak256(bytes(description))); | ||
|
||
require(targets.length == values.length, "Governor: invalid proposal length"); | ||
require(targets.length == calldatas.length, "Governor: invalid proposal length"); | ||
require(targets.length > 0, "Governor: empty proposal"); | ||
|
||
ProposalCore storage proposal = _proposals[proposalId]; | ||
require(proposal.voteStart.isUnset(), "Governor: proposal already exists"); | ||
|
||
uint64 snapshot = block.number.toUint64() + votingDelay().toUint64(); | ||
uint64 deadline = snapshot + votingPeriod().toUint64(); | ||
|
||
proposal.voteStart.setDeadline(snapshot); | ||
proposal.voteEnd.setDeadline(deadline); | ||
|
||
emit ProposalCreated( | ||
proposalId, | ||
_msgSender(), | ||
targets, | ||
values, | ||
new string[](targets.length), | ||
calldatas, | ||
snapshot, | ||
deadline, | ||
description | ||
); | ||
|
||
return proposalId; | ||
} | ||
|
||
/** | ||
* @dev See {IGovernor-execute}. | ||
*/ | ||
function execute( | ||
address[] memory targets, | ||
uint256[] memory values, | ||
bytes[] memory calldatas, | ||
bytes32 descriptionHash | ||
) public payable virtual override returns (uint256) { | ||
uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); | ||
|
||
ProposalState status = state(proposalId); | ||
require( | ||
status == ProposalState.Succeeded || status == ProposalState.Queued, | ||
"Governor: proposal not successful" | ||
); | ||
_proposals[proposalId].executed = true; | ||
|
||
emit ProposalExecuted(proposalId); | ||
|
||
_execute(proposalId, targets, values, calldatas, descriptionHash); | ||
|
||
return proposalId; | ||
} | ||
|
||
/** | ||
* @dev Internal execution mechanism. Can be overriden to implement different execution mechanism | ||
*/ | ||
function _execute( | ||
uint256, /* proposalId */ | ||
address[] memory targets, | ||
uint256[] memory values, | ||
bytes[] memory calldatas, | ||
bytes32 /*descriptionHash*/ | ||
) internal virtual { | ||
string memory errorMessage = "Governor: call reverted without message"; | ||
for (uint256 i = 0; i < targets.length; ++i) { | ||
(bool success, bytes memory returndata) = targets[i].call{value: values[i]}(calldatas[i]); | ||
Address.verifyCallResult(success, returndata, errorMessage); | ||
} | ||
} | ||
|
||
/** | ||
* @dev Internal cancel mechanism: locks up the proposal timer, preventing it from being re-submitted. Marks it as | ||
* canceled to allow distinguishing it from executed proposals. | ||
* | ||
* Emits a {IGovernor-ProposalCanceled} event. | ||
*/ | ||
function _cancel( | ||
address[] memory targets, | ||
uint256[] memory values, | ||
bytes[] memory calldatas, | ||
bytes32 descriptionHash | ||
) internal virtual returns (uint256) { | ||
uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); | ||
ProposalState status = state(proposalId); | ||
|
||
require( | ||
status != ProposalState.Canceled && status != ProposalState.Expired && status != ProposalState.Executed, | ||
"Governor: proposal not active" | ||
); | ||
_proposals[proposalId].canceled = true; | ||
|
||
emit ProposalCanceled(proposalId); | ||
|
||
return proposalId; | ||
} | ||
|
||
/** | ||
* @dev See {IGovernor-castVote}. | ||
*/ | ||
function castVote(uint256 proposalId, uint8 support) public virtual override returns (uint256) { | ||
address voter = _msgSender(); | ||
return _castVote(proposalId, voter, support, ""); | ||
} | ||
|
||
/** | ||
* @dev See {IGovernor-castVoteWithReason}. | ||
*/ | ||
function castVoteWithReason( | ||
uint256 proposalId, | ||
uint8 support, | ||
string calldata reason | ||
) public virtual override returns (uint256) { | ||
address voter = _msgSender(); | ||
return _castVote(proposalId, voter, support, reason); | ||
} | ||
|
||
/** | ||
* @dev See {IGovernor-castVoteBySig}. | ||
*/ | ||
function castVoteBySig( | ||
uint256 proposalId, | ||
uint8 support, | ||
uint8 v, | ||
bytes32 r, | ||
bytes32 s | ||
) public virtual override returns (uint256) { | ||
address voter = ECDSA.recover( | ||
_hashTypedDataV4(keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support))), | ||
v, | ||
r, | ||
s | ||
); | ||
return _castVote(proposalId, voter, support, ""); | ||
} | ||
|
||
/** | ||
* @dev Internal vote casting mechanism: Check that the vote is pending, that it has not been casted yet, retrieve | ||
* voting weight using {IGovernor-getVotes} and call the {_countVote} internal function. | ||
* | ||
* Emits a {IGovernor-VoteCast} event. | ||
*/ | ||
function _castVote( | ||
uint256 proposalId, | ||
address account, | ||
uint8 support, | ||
string memory reason | ||
) internal virtual returns (uint256) { | ||
ProposalCore storage proposal = _proposals[proposalId]; | ||
require(state(proposalId) == ProposalState.Active, "Governor: vote not currently active"); | ||
|
||
uint256 weight = getVotes(account, proposal.voteStart.getDeadline()); | ||
_countVote(proposalId, account, support, weight); | ||
|
||
emit VoteCast(account, proposalId, support, weight, reason); | ||
|
||
return weight; | ||
} | ||
|
||
/** | ||
* @dev Address through which the governor executes action. Will be overloaded by module that execute actions | ||
* through another contract such as a timelock. | ||
*/ | ||
function _executor() internal view virtual returns (address) { | ||
return address(this); | ||
} | ||
} |
Oops, something went wrong.