Skip to content

Commit

Permalink
Add Governor module for governance-settable parameters (#2904)
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 committed Oct 19, 2021
1 parent 0db97c9 commit b12af48
Show file tree
Hide file tree
Showing 19 changed files with 647 additions and 131 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -8,6 +8,7 @@
* `EIP712`: cache `address(this)` to immutable storage to avoid potential issues if a vanilla contract is used in a delegatecall context. ([#2852](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/#2852))
* Add internal `_setApprovalForAll` to `ERC721` and `ERC1155`. ([#2834](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2834))
* `Governor`: shift vote start and end by one block to better match Compound's GovernorBravo and prevent voting at the Governor level if the voting snapshot is not ready. ([#2892](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/#2892))
* `GovernorSettings`: a new governor module that manages voting settings updatable through governance actions. ([#2904](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/#2904))
* `PaymentSplitter`: now supports ERC20 assets in addition to Ether. ([#2858](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/#2858))
* `ECDSA`: add a variant of `toEthSignedMessageHash` for arbitrary length message hashing. ([#2865](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/#2865))
* `MerkleProof`: add a `processProof` function that returns the rebuilt root hash given a leaf and a proof. ([#2841](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2841))
Expand Down
12 changes: 12 additions & 0 deletions contracts/governance/Governor.sol
Expand Up @@ -143,6 +143,13 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor {
return _proposals[proposalId].voteEnd.getDeadline();
}

/**
* @dev Part of the Governor Bravo's interface: _"The number of votes required in order for a voter to become a proposer"_.
*/
function proposalThreshold() public view virtual returns (uint256) {
return 0;
}

/**
* @dev Amount of votes already cast passes the threshold limit.
*/
Expand Down Expand Up @@ -174,6 +181,11 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor {
bytes[] memory calldatas,
string memory description
) public virtual override returns (uint256) {
require(
getVotes(msg.sender, block.number - 1) >= proposalThreshold(),
"GovernorCompatibilityBravo: proposer votes below proposal threshold"
);

uint256 proposalId = hashProposal(targets, values, calldatas, keccak256(bytes(description)));

require(targets.length == values.length, "Governor: invalid proposal length");
Expand Down
8 changes: 6 additions & 2 deletions contracts/governance/README.adoc
Expand Up @@ -40,7 +40,7 @@ Other extensions can customize the behavior or interface in multiple ways.

* {GovernorCompatibilityBravo}: Extends the interface to be fully `GovernorBravo`-compatible. Note that events are compatible regardless of whether this extension is included or not.

* {GovernorProposalThreshold}: Restricts proposals to delegates with a minimum voting power.
* {GovernorSettings}: Manages some of the settings (voting delay, voting period duration, and proposal threshold) in a way that can be updated through a governance proposal, without requiering an upgrade.

In addition to modules and extensions, the core contract requires a few virtual functions to be implemented to your particular specifications:

Expand Down Expand Up @@ -72,10 +72,14 @@ NOTE: Functions of the `Governor` contract do not include access control. If you

{{GovernorTimelockCompound}}

{{GovernorProposalThreshold}}
{{GovernorSettings}}

{{GovernorCompatibilityBravo}}

=== Deprecated

{{GovernorProposalThreshold}}

== Timelock

In a governance system, the {TimelockController} contract is in carge of introducing a delay between a proposal and its execution. It can be used with or without a {Governor}.
Expand Down
20 changes: 2 additions & 18 deletions contracts/governance/compatibility/GovernorCompatibilityBravo.sol
Expand Up @@ -5,7 +5,6 @@ pragma solidity ^0.8.0;
import "../../utils/Counters.sol";
import "../../utils/math/SafeCast.sol";
import "../extensions/IGovernorTimelock.sol";
import "../extensions/GovernorProposalThreshold.sol";
import "../Governor.sol";
import "./IGovernorCompatibilityBravo.sol";

Expand All @@ -19,12 +18,7 @@ import "./IGovernorCompatibilityBravo.sol";
*
* _Available since v4.3._
*/
abstract contract GovernorCompatibilityBravo is
IGovernorTimelock,
IGovernorCompatibilityBravo,
Governor,
GovernorProposalThreshold
{
abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorCompatibilityBravo, Governor {
using Counters for Counters.Counter;
using Timers for Timers.BlockNumber;

Expand Down Expand Up @@ -63,7 +57,7 @@ abstract contract GovernorCompatibilityBravo is
uint256[] memory values,
bytes[] memory calldatas,
string memory description
) public virtual override(IGovernor, Governor, GovernorProposalThreshold) returns (uint256) {
) public virtual override(IGovernor, Governor) returns (uint256) {
_storeProposal(_msgSender(), targets, values, new string[](calldatas.length), calldatas, description);
return super.propose(targets, values, calldatas, description);
}
Expand Down Expand Up @@ -169,16 +163,6 @@ abstract contract GovernorCompatibilityBravo is
}

// ==================================================== Views =====================================================
/**
* @dev Part of the Governor Bravo's interface: _"The number of votes required in order for a voter to become a proposer"_.
*/
function proposalThreshold()
public
view
virtual
override(IGovernorCompatibilityBravo, GovernorProposalThreshold)
returns (uint256);

/**
* @dev See {IGovernorCompatibilityBravo-proposals}.
*/
Expand Down
Expand Up @@ -110,9 +110,4 @@ abstract contract IGovernorCompatibilityBravo is IGovernor {
* @dev Part of the Governor Bravo's interface: _"Gets the receipt for a voter on a given proposal"_.
*/
function getReceipt(uint256 proposalId, address voter) public view virtual returns (Receipt memory);

/**
* @dev Part of the Governor Bravo's interface: _"The number of votes required in order for a voter to become a proposer"_.
*/
function proposalThreshold() public view virtual returns (uint256);
}
14 changes: 1 addition & 13 deletions contracts/governance/extensions/GovernorProposalThreshold.sol
Expand Up @@ -8,27 +8,15 @@ import "../Governor.sol";
* @dev Extension of {Governor} for proposal restriction to token holders with a minimum balance.
*
* _Available since v4.3._
* _Deprecated since v4.4._
*/
abstract contract GovernorProposalThreshold is Governor {
/**
* @dev See {IGovernor-propose}.
*/
function propose(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
string memory description
) public virtual override returns (uint256) {
require(
getVotes(msg.sender, block.number - 1) >= proposalThreshold(),
"GovernorCompatibilityBravo: proposer votes below proposal threshold"
);

return super.propose(targets, values, calldatas, description);
}

/**
* @dev Part of the Governor Bravo's interface: _"The number of votes required in order for a voter to become a proposer"_.
*/
function proposalThreshold() public view virtual returns (uint256);
}
113 changes: 113 additions & 0 deletions contracts/governance/extensions/GovernorSettings.sol
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../Governor.sol";

/**
* @dev Extension of {Governor} for settings updatable through governance.
*
* _Available since v4.4._
*/
abstract contract GovernorSettings is Governor {
uint256 private _votingDelay;
uint256 private _votingPeriod;
uint256 private _proposalThreshold;

event VotingDelaySet(uint256 oldVotingDelay, uint256 newVotingDelay);
event VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod);
event ProposalThresholdSet(uint256 oldProposalThreshold, uint256 newProposalThreshold);

/**
* @dev Initialize the governance parameters.
*/
constructor(
uint256 initialVotingDelay,
uint256 initialVotingPeriod,
uint256 initialProposalThreshold
) {
_setVotingDelay(initialVotingDelay);
_setVotingPeriod(initialVotingPeriod);
_setProposalThreshold(initialProposalThreshold);
}

/**
* @dev See {IGovernor-votingDelay}.
*/
function votingDelay() public view virtual override returns (uint256) {
return _votingDelay;
}

/**
* @dev See {IGovernor-votingPeriod}.
*/
function votingPeriod() public view virtual override returns (uint256) {
return _votingPeriod;
}

/**
* @dev See {Governor-proposalThreshold}.
*/
function proposalThreshold() public view virtual override returns (uint256) {
return _proposalThreshold;
}

/**
* @dev Update the voting delay. This operation can only be performed through a governance proposal.
*
* Emits a {VotingDelaySet} event.
*/
function setVotingDelay(uint256 newVotingDelay) public onlyGovernance {
_setVotingDelay(newVotingDelay);
}

/**
* @dev Update the voting period. This operation can only be performed through a governance proposal.
*
* Emits a {VotingPeriodSet} event.
*/
function setVotingPeriod(uint256 newVotingPeriod) public onlyGovernance {
_setVotingPeriod(newVotingPeriod);
}

/**
* @dev Update the proposal threshold. This operation can only be performed through a governance proposal.
*
* Emits a {ProposalThresholdSet} event.
*/
function setProposalThreshold(uint256 newProposalThreshold) public onlyGovernance {
_setProposalThreshold(newProposalThreshold);
}

/**
* @dev Internal setter for the the voting delay.
*
* Emits a {VotingDelaySet} event.
*/
function _setVotingDelay(uint256 newVotingDelay) internal virtual {
emit VotingDelaySet(_votingDelay, newVotingDelay);
_votingDelay = newVotingDelay;
}

/**
* @dev Internal setter for the the voting period.
*
* Emits a {VotingPeriodSet} event.
*/
function _setVotingPeriod(uint256 newVotingPeriod) internal virtual {
// voting period must be at least one block long
require(newVotingPeriod > 0, "GovernorSettings: voting period too low");
emit VotingPeriodSet(_votingPeriod, newVotingPeriod);
_votingPeriod = newVotingPeriod;
}

/**
* @dev Internal setter for the the proposal threshold.
*
* Emits a {ProposalThresholdSet} event.
*/
function _setProposalThreshold(uint256 newProposalThreshold) internal virtual {
emit ProposalThresholdSet(_proposalThreshold, newProposalThreshold);
_proposalThreshold = newProposalThreshold;
}
}
28 changes: 8 additions & 20 deletions contracts/mocks/GovernorCompMock.sol
Expand Up @@ -2,34 +2,22 @@

pragma solidity ^0.8.0;

import "../governance/Governor.sol";
import "../governance/extensions/GovernorCountingSimple.sol";
import "../governance/extensions/GovernorVotesComp.sol";

contract GovernorCompMock is Governor, GovernorVotesComp, GovernorCountingSimple {
uint256 immutable _votingDelay;
uint256 immutable _votingPeriod;

constructor(
string memory name_,
ERC20VotesComp token_,
uint256 votingDelay_,
uint256 votingPeriod_
) Governor(name_) GovernorVotesComp(token_) {
_votingDelay = votingDelay_;
_votingPeriod = votingPeriod_;
}
contract GovernorCompMock is GovernorVotesComp, GovernorCountingSimple {
constructor(string memory name_, ERC20VotesComp token_) Governor(name_) GovernorVotesComp(token_) {}

function votingDelay() public view override returns (uint256) {
return _votingDelay;
function quorum(uint256) public pure override returns (uint256) {
return 0;
}

function votingPeriod() public view override returns (uint256) {
return _votingPeriod;
function votingDelay() public pure override returns (uint256) {
return 4;
}

function quorum(uint256) public pure override returns (uint256) {
return 0;
function votingPeriod() public pure override returns (uint256) {
return 16;
}

function cancel(
Expand Down
41 changes: 18 additions & 23 deletions contracts/mocks/GovernorCompatibilityBravoMock.sol
Expand Up @@ -3,26 +3,29 @@
pragma solidity ^0.8.0;

import "../governance/compatibility/GovernorCompatibilityBravo.sol";
import "../governance/extensions/GovernorVotesComp.sol";
import "../governance/extensions/GovernorTimelockCompound.sol";
import "../governance/extensions/GovernorSettings.sol";
import "../governance/extensions/GovernorVotesComp.sol";

contract GovernorCompatibilityBravoMock is GovernorCompatibilityBravo, GovernorTimelockCompound, GovernorVotesComp {
uint256 immutable _votingDelay;
uint256 immutable _votingPeriod;
uint256 immutable _proposalThreshold;

contract GovernorCompatibilityBravoMock is
GovernorCompatibilityBravo,
GovernorSettings,
GovernorTimelockCompound,
GovernorVotesComp
{
constructor(
string memory name_,
ERC20VotesComp token_,
uint256 votingDelay_,
uint256 votingPeriod_,
uint256 proposalThreshold_,
ICompoundTimelock timelock_
) Governor(name_) GovernorVotesComp(token_) GovernorTimelockCompound(timelock_) {
_votingDelay = votingDelay_;
_votingPeriod = votingPeriod_;
_proposalThreshold = proposalThreshold_;
}
)
Governor(name_)
GovernorTimelockCompound(timelock_)
GovernorSettings(votingDelay_, votingPeriod_, proposalThreshold_)
GovernorVotesComp(token_)
{}

function supportsInterface(bytes4 interfaceId)
public
Expand All @@ -34,18 +37,6 @@ contract GovernorCompatibilityBravoMock is GovernorCompatibilityBravo, GovernorT
return super.supportsInterface(interfaceId);
}

function votingDelay() public view override returns (uint256) {
return _votingDelay;
}

function votingPeriod() public view override returns (uint256) {
return _votingPeriod;
}

function proposalThreshold() public view virtual override returns (uint256) {
return _proposalThreshold;
}

function quorum(uint256) public pure override returns (uint256) {
return 0;
}
Expand All @@ -70,6 +61,10 @@ contract GovernorCompatibilityBravoMock is GovernorCompatibilityBravo, GovernorT
return super.proposalEta(proposalId);
}

function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) {
return super.proposalThreshold();
}

function propose(
address[] memory targets,
uint256[] memory values,
Expand Down

0 comments on commit b12af48

Please sign in to comment.