-
Notifications
You must be signed in to change notification settings - Fork 12k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a governance extension that implements super quorum #5492
base: master
Are you sure you want to change the base?
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
'openzeppelin-solidity': minor | ||
--- | ||
|
||
- `GovernorSuperQuorum`: Add a governance extension to support a super quorum. Proposals that meet the super quorum can be executed earlier than the proposal deadline. | ||
- `GovernorVotesSuperQuorumFraction`: Add a governance extension to enable super quorum with fractional voting. | ||
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -7,12 +7,13 @@ import {SignatureChecker} from "../../utils/cryptography/SignatureChecker.sol"; | |||||
import {SafeCast} from "../../utils/math/SafeCast.sol"; | ||||||
import {VotesExtended} from "../utils/VotesExtended.sol"; | ||||||
import {GovernorVotes} from "./GovernorVotes.sol"; | ||||||
import {IGovernorCounting} from "./IGovernorCounting.sol"; | ||||||
|
||||||
/** | ||||||
* @dev Extension of {Governor} which enables delegators to override the vote of their delegates. This module requires a | ||||||
* token that inherits {VotesExtended}. | ||||||
*/ | ||||||
abstract contract GovernorCountingOverridable is GovernorVotes { | ||||||
abstract contract GovernorCountingOverridable is GovernorVotes, IGovernorCounting { | ||||||
bytes32 public constant OVERRIDE_BALLOT_TYPEHASH = | ||||||
keccak256("OverrideBallot(uint256 proposalId,uint8 support,address voter,uint256 nonce,string reason)"); | ||||||
|
||||||
|
@@ -78,7 +79,7 @@ abstract contract GovernorCountingOverridable is GovernorVotes { | |||||
*/ | ||||||
function proposalVotes( | ||||||
uint256 proposalId | ||||||
) public view virtual returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) { | ||||||
) public view virtual override returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one will keep compiling since overriding interface functions is not required since Solidity 0.8.14.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressing in bb9a989 |
||||||
uint256[3] storage votes = _proposalVotes[proposalId].votes; | ||||||
return (votes[uint8(VoteType.Against)], votes[uint8(VoteType.For)], votes[uint8(VoteType.Abstain)]); | ||||||
} | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -4,11 +4,12 @@ | |||||
pragma solidity ^0.8.20; | ||||||
|
||||||
import {Governor} from "../Governor.sol"; | ||||||
import {IGovernorCounting} from "./IGovernorCounting.sol"; | ||||||
|
||||||
/** | ||||||
* @dev Extension of {Governor} for simple, 3 options, vote counting. | ||||||
*/ | ||||||
abstract contract GovernorCountingSimple is Governor { | ||||||
abstract contract GovernorCountingSimple is Governor, IGovernorCounting { | ||||||
/** | ||||||
* @dev Supported vote types. Matches Governor Bravo ordering. | ||||||
*/ | ||||||
|
@@ -47,7 +48,7 @@ abstract contract GovernorCountingSimple is Governor { | |||||
*/ | ||||||
function proposalVotes( | ||||||
uint256 proposalId | ||||||
) public view virtual returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) { | ||||||
) public view virtual override returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressing in bb9a989 |
||||||
ProposalVote storage proposalVote = _proposalVotes[proposalId]; | ||||||
return (proposalVote.againstVotes, proposalVote.forVotes, proposalVote.abstainVotes); | ||||||
} | ||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,49 @@ | ||||||||||||||||
// SPDX-License-Identifier: MIT | ||||||||||||||||
pragma solidity ^0.8.20; | ||||||||||||||||
|
||||||||||||||||
import {Governor} from "../Governor.sol"; | ||||||||||||||||
import {IGovernorCounting} from "./IGovernorCounting.sol"; | ||||||||||||||||
import {SafeCast} from "../../utils/math/SafeCast.sol"; | ||||||||||||||||
import {Checkpoints} from "../../utils/structs/Checkpoints.sol"; | ||||||||||||||||
|
||||||||||||||||
/** | ||||||||||||||||
* @dev Extension of {Governor} with a super quorum. Proposals that meet the super quorum can be executed | ||||||||||||||||
* earlier than the proposal deadline. Counting modules that want to use this extension must implement | ||||||||||||||||
* {IGovernorCounting}. | ||||||||||||||||
* | ||||||||||||||||
* NOTE: It's up to developers to implement `superQuorum` and validate it against `quorum`. | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd move this note to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressing in bb9a989 |
||||||||||||||||
*/ | ||||||||||||||||
abstract contract GovernorSuperQuorum is Governor { | ||||||||||||||||
/** | ||||||||||||||||
* @dev Minimum number of cast votes required for a proposal to reach super quorum. Only FOR votes are counted towards | ||||||||||||||||
* the super quorum. Once the super quorum is reached, an active proposal can proceed to the next state without waiting | ||||||||||||||||
* for the proposal deadline. | ||||||||||||||||
* | ||||||||||||||||
* NOTE: The `timepoint` parameter corresponds to the snapshot used for counting vote. This allows to scale the | ||||||||||||||||
* quorum depending on values such as the totalSupply of a token at this timepoint (see {ERC20Votes}). | ||||||||||||||||
*/ | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Following my previous comment, I think this is clearer in regards to what the developer must care and what the consequences are
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressing in bb9a989 |
||||||||||||||||
function superQuorum(uint256 timepoint) public view virtual returns (uint256); | ||||||||||||||||
|
||||||||||||||||
/** | ||||||||||||||||
* @dev Overridden version of the {Governor-state} function that checks if the proposal has reached the super quorum. | ||||||||||||||||
* | ||||||||||||||||
* NOTE: If the proposal reaches super quorum but {_voteSucceeded} returns false, eg, assuming the super quorum has been set | ||||||||||||||||
* low enough that both FOR and AGAINST votes have exceeded it and AGAINST votes exceed FOR votes, the proposal continues to | ||||||||||||||||
* be active until {_voteSucceeded} returns true or the proposal deadline is reached. This means that with a low super quorum | ||||||||||||||||
* it is also possible that a vote can succeed prematurely before enough AGAINST voters have a chance to vote. Hence, it is | ||||||||||||||||
* recommended to set a high enough super quorum to avoid these types of scenarios. | ||||||||||||||||
*/ | ||||||||||||||||
function state(uint256 proposalId) public view virtual override returns (ProposalState) { | ||||||||||||||||
ProposalState currentState = super.state(proposalId); | ||||||||||||||||
if (currentState != ProposalState.Active) return currentState; | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel this line is forcing to replicate the logic of calculating
Suggested change
This way, the logic from Governor is completely reused. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You still need to calculate all states in the overridden version. The way this is currently coded:
Not sure I understand your suggestion but from the looks of it I would still have to do this: if (currentState == ProposalState.Active) {
(, uint256 forVotes, ) = IGovernorCounting(address(this)).proposalVotes(proposalId);
if (forVotes < superQuorum(proposalSnapshot(proposalId)) || !_voteSucceeded(proposalId)) {
return currentState;
} else if (proposalEta(proposalId) == 0) {
return ProposalState.Succeeded;
} else {
return ProposalState.Queued;
}
} |
||||||||||||||||
|
||||||||||||||||
(, uint256 forVotes, ) = IGovernorCounting(address(this)).proposalVotes(proposalId); | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand this function is external in the interface, but the intuition of calling Given the compiler doesn't give an error when you mark as I'd use the
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you clarify what do you mean that you would use the |
||||||||||||||||
if (forVotes < superQuorum(proposalSnapshot(proposalId)) || !_voteSucceeded(proposalId)) { | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't be?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This clause reads as "if FOR votes haven't reached super quorum OR the vote is not yet successful, then the proposal is active". In other words, both conditions need to be false in order for the proposal not to be active anymore: "FOR votes have reached super quorum AND the vote is successful". So no, the conditional should stay as is. |
||||||||||||||||
return ProposalState.Active; | ||||||||||||||||
} else if (proposalEta(proposalId) == 0) { | ||||||||||||||||
return ProposalState.Succeeded; | ||||||||||||||||
} else { | ||||||||||||||||
return ProposalState.Queued; | ||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import {Governor} from "../Governor.sol"; | ||
import {GovernorSuperQuorum} from "./GovernorSuperQuorum.sol"; | ||
import {GovernorVotesQuorumFraction} from "./GovernorVotesQuorumFraction.sol"; | ||
import {SafeCast} from "../../utils/math/SafeCast.sol"; | ||
import {Checkpoints} from "../../utils/structs/Checkpoints.sol"; | ||
|
||
/** | ||
* @dev Extension of {GovernorVotesQuorumFraction} with a super quorum expressed as a | ||
* fraction of the total supply. Proposals that meet the super quorum can be executed | ||
* earlier than the proposal deadline. | ||
*/ | ||
abstract contract GovernorVotesSuperQuorumFraction is GovernorVotesQuorumFraction, GovernorSuperQuorum { | ||
using Checkpoints for Checkpoints.Trace208; | ||
|
||
Checkpoints.Trace208 private _superQuorumNumeratorHistory; | ||
|
||
event SuperQuorumNumeratorUpdated(uint256 oldSuperQuorumNumerator, uint256 newSuperQuorumNumerator); | ||
|
||
/** | ||
* @dev The super quorum set is not valid as it exceeds the quorum denominator. | ||
*/ | ||
error GovernorInvalidSuperQuorumFraction(uint256 superQuorumNumerator, uint256 denominator); | ||
|
||
/** | ||
* @dev The super quorum set is not valid as it is smaller or equal to the quorum. | ||
*/ | ||
error GovernorInvalidSuperQuorumTooSmall(uint256 superQuorumNumerator, uint256 quorumNumerator); | ||
|
||
/** | ||
* @dev The quorum set is not valid as it exceeds the super quorum. | ||
*/ | ||
error GovernorInvalidQuorumTooLarge(uint256 quorumNumerator, uint256 superQuorumNumerator); | ||
|
||
/** | ||
* @dev Initialize super quorum as a fraction of the token's total supply. | ||
* | ||
* The super quorum is specified as a fraction of the token's total supply and has to | ||
* be greater than the quorum. | ||
*/ | ||
constructor(uint256 superQuorumNumeratorValue) { | ||
_updateSuperQuorumNumerator(superQuorumNumeratorValue); | ||
} | ||
|
||
/** | ||
* @dev Returns the current super quorum numerator. | ||
*/ | ||
function superQuorumNumerator() public view virtual returns (uint256) { | ||
return _superQuorumNumeratorHistory.latest(); | ||
} | ||
|
||
/** | ||
* @dev Returns the super quorum numerator at a specific timepoint. | ||
*/ | ||
function superQuorumNumerator(uint256 timepoint) public view virtual returns (uint256) { | ||
return _numerator(_superQuorumNumeratorHistory, timepoint); | ||
} | ||
|
||
/** | ||
* @dev Returns the super quorum for a timepoint, in terms of number of votes: `supply * numerator / denominator`. | ||
*/ | ||
function superQuorum(uint256 timepoint) public view virtual override returns (uint256) { | ||
return (token().getPastTotalSupply(timepoint) * superQuorumNumerator(timepoint)) / quorumDenominator(); | ||
} | ||
|
||
/** | ||
* @dev Changes the super quorum numerator. | ||
* | ||
* Emits a {SuperQuorumNumeratorUpdated} event. | ||
* | ||
* Requirements: | ||
* | ||
* - Must be called through a governance proposal. | ||
* - New super quorum numerator must be smaller or equal to the denominator. | ||
* - New super quorum numerator must be bigger than the quorum numerator. | ||
*/ | ||
function updateSuperQuorumNumerator(uint256 newSuperQuorumNumerator) external virtual onlyGovernance { | ||
_updateSuperQuorumNumerator(newSuperQuorumNumerator); | ||
} | ||
|
||
/** | ||
* @dev Changes the super quorum numerator. | ||
* | ||
* Emits a {SuperQuorumNumeratorUpdated} event. | ||
* | ||
* Requirements: | ||
* | ||
* - New super quorum numerator must be smaller or equal to the denominator. | ||
* - New super quorum numerator must be bigger than the quorum numerator. | ||
*/ | ||
function _updateSuperQuorumNumerator(uint256 newSuperQuorumNumerator) internal virtual { | ||
uint256 denominator = quorumDenominator(); | ||
if (newSuperQuorumNumerator > denominator) { | ||
revert GovernorInvalidSuperQuorumFraction(newSuperQuorumNumerator, denominator); | ||
} | ||
uint256 quorumNumerator = quorumNumerator(); | ||
if (newSuperQuorumNumerator <= quorumNumerator) { | ||
revert GovernorInvalidSuperQuorumTooSmall(newSuperQuorumNumerator, quorumNumerator); | ||
} | ||
|
||
uint256 oldSuperQuorumNumerator = _superQuorumNumeratorHistory.latest(); | ||
_superQuorumNumeratorHistory.push(clock(), SafeCast.toUint208(newSuperQuorumNumerator)); | ||
|
||
emit SuperQuorumNumeratorUpdated(oldSuperQuorumNumerator, newSuperQuorumNumerator); | ||
} | ||
|
||
/** | ||
* @dev Overrides {GovernorVotesQuorumFraction._updateQuorumNumerator} to ensure the super | ||
* quorum numerator is bigger than the quorum numerator. | ||
*/ | ||
function _updateQuorumNumerator(uint256 newQuorumNumerator) internal virtual override { | ||
uint256 superQuorumNumerator_ = superQuorumNumerator(); | ||
// Ignoring a super quorum of 0 as it is the initial value when the contract is deployed. | ||
if (superQuorumNumerator_ != 0 && newQuorumNumerator >= superQuorumNumerator_) { | ||
revert GovernorInvalidQuorumTooLarge(newQuorumNumerator, superQuorumNumerator_); | ||
} | ||
super._updateQuorumNumerator(newQuorumNumerator); | ||
} | ||
|
||
/** | ||
* @dev Defined to resolve conflicting state() function inheritance | ||
*/ | ||
function state( | ||
uint256 proposalId | ||
) public view virtual override(Governor, GovernorSuperQuorum) returns (ProposalState) { | ||
return super.state(proposalId); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.20; | ||
|
||
/** | ||
* @dev Interface to count votes for the following counting modules: | ||
* - GovernorCountingFractional | ||
* - GovernorCountingSimple | ||
* - GovernorCountingOverridable | ||
* | ||
* This interface is used by GovernorSuperQuorum to get counted votes from the above counting modules. | ||
* Other counting modules that need to work with GovernorSuperQuorum MUST implement this interface. | ||
*/ | ||
interface IGovernorCounting { | ||
/** | ||
* @dev Accessor to the internal vote counts. | ||
*/ | ||
function proposalVotes( | ||
uint256 proposalId | ||
) external view returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's split this one into two changesets and remove the
-
at the beginning, each changeset gets formatted once we release a new versionThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressing in bb9a989