Skip to content

Commit

Permalink
Add Governor contracts (#2672)
Browse files Browse the repository at this point in the history
Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
  • Loading branch information
Amxx and frangio authored Jul 16, 2021
1 parent f88e555 commit 6c1a634
Show file tree
Hide file tree
Showing 39 changed files with 5,302 additions and 377 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

* `ERC2771Context`: use private variable from storage to store the forwarder address. Fixes issues where `_msgSender()` was not callable from constructors. ([#2754](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2754))
* `EnumerableSet`: add `values()` functions that returns an array containing all values in a single call. ([#2768](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2768))
* `Governor`: added a modular system of `Governor` contracts based on `GovernorAlpha` and `GovernorBravo`. ([#2672](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2672))

## 4.2.0 (2021-06-30)

Expand All @@ -18,7 +19,7 @@
* `ERC1155Supply`: add a new `ERC1155` extension that keeps track of the totalSupply of each tokenId. ([#2593](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2593))
* `BitMaps`: add a new `BitMaps` library that provides a storage efficient datastructure for `uint256` to `bool` mapping with contiguous keys. ([#2710](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2710))

### Breaking Changes
### Breaking Changes

* `ERC20FlashMint` is no longer a Draft ERC. ([#2673](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2673)))

Expand Down
357 changes: 357 additions & 0 deletions contracts/governance/Governor.sol
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);
}
}
Loading

0 comments on commit 6c1a634

Please sign in to comment.