Skip to content

Commit

Permalink
Added GovernorVotesCompQuorumFraction for settable quorum
Browse files Browse the repository at this point in the history
  • Loading branch information
jferas committed Nov 17, 2023
1 parent 2e6cb9e commit 6c439e8
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 11 deletions.
1 change: 1 addition & 0 deletions script/DeployInput.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ contract DeployInput {
uint256 constant INITIAL_VOTING_DELAY = 7200; // 24 hours
uint256 constant INITIAL_VOTING_PERIOD = 17_280; // matches existing config
uint256 constant INITIAL_PROPOSAL_THRESHOLD = 1_000_000e18; // matches existing config
uint256 constant INITIAL_QUORUM_PCT = 4;
}
17 changes: 10 additions & 7 deletions src/RadworksGovernor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {IGovernor} from "@openzeppelin/contracts/governance/IGovernor.sol";
import {ICompoundTimelock} from
"@openzeppelin/contracts/governance/extensions/GovernorTimelockCompound.sol";
import {GovernorSettings} from "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";

import {GovernorVotesCompQuorumFraction} from "./lib/GovernorVotesCompQuorumFraction.sol";
import {
GovernorTimelockCompound,
ICompoundTimelock
Expand All @@ -22,7 +22,7 @@ import {GovernorCompatibilityBravo} from
/// @notice The upgraded Radworks Governor: Bravo compatible and extended from OpenZeppelin.
contract RadworksGovernor is
Governor,
GovernorVotesComp,
GovernorVotesCompQuorumFraction,
GovernorTimelockCompound,
GovernorSettings
{
Expand All @@ -47,9 +47,6 @@ contract RadworksGovernor is
/// @notice Human readable name of this Governor.
string private constant GOVERNOR_NAME = "Radworks Governor Bravo";

/// @notice The number of RAD (in "wei") that must participate in a vote to meet quorum threshold.
uint256 private constant QUORUM = 4_000_000e18; // 4,000,000 RAD

/// @param _initialVotingDelay The initial voting delay this Governor will enforce.
/// @param _initialVotingPeriod The initial voting period this Governor will enforce.
/// @param _initialProposalThreshold The initial number of RAD required to submit
Expand All @@ -59,6 +56,7 @@ contract RadworksGovernor is
uint256 _initialVotingPeriod,
uint256 _initialProposalThreshold
)
GovernorVotesCompQuorumFraction(4)
GovernorVotesComp(RAD_TOKEN)
GovernorSettings(_initialVotingDelay, _initialVotingPeriod, _initialProposalThreshold)
GovernorTimelockCompound(TIMELOCK)
Expand Down Expand Up @@ -177,8 +175,13 @@ contract RadworksGovernor is
/// @notice The amount of RAD required to meet the quorum threshold for a proposal
/// as of a given block.
/// @dev Our implementation ignores the block number parameter and returns a constant.
function quorum(uint256) public pure override returns (uint256) {
return QUORUM;
function quorum(uint256 timepoint)
public
view
override(IGovernor, GovernorVotesCompQuorumFraction)
returns (uint256)
{
return GovernorVotesCompQuorumFraction.quorum(timepoint);
}

/// @inheritdoc Governor
Expand Down
125 changes: 125 additions & 0 deletions src/lib/GovernorVotesCompQuorumFraction.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0)
// (governance/extensions/GovernorVotesQuorumFraction.sol)

pragma solidity ^0.8.0;

import {
ERC20VotesComp,
GovernorVotesComp
} from "@openzeppelin/contracts/governance/extensions/GovernorVotesComp.sol";
import "@openzeppelin/contracts/utils/Checkpoints.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";

/**
* @dev Extension of {Governor} for voting weight extraction from an {GovernorVotesComp} token and a
* quorum
* expressed as a fraction of the total supply.
*/
abstract contract GovernorVotesCompQuorumFraction is GovernorVotesComp {
using Checkpoints for Checkpoints.Trace224;

uint256 private _quorumNumerator; // DEPRECATED in favor of _quorumNumeratorHistory

/// @custom:oz-retyped-from Checkpoints.History
Checkpoints.Trace224 private _quorumNumeratorHistory;

event QuorumNumeratorUpdated(uint256 oldQuorumNumerator, uint256 newQuorumNumerator);

/**
* @dev Initialize quorum as a fraction of the token's total supply.
*
* The fraction is specified as `numerator / denominator`. By default the denominator is 100, so
* quorum is pecified as a percent: a numerator of 10 corresponds to quorum being 10% of total
* supply.
* The denominator can be customized by overriding {quorumDenominator}.
*/
constructor(uint256 quorumNumeratorValue) {
_updateQuorumNumerator(quorumNumeratorValue);
}

/**
* @dev Returns the current quorum numerator. See {quorumDenominator}.
*/
function quorumNumerator() public view virtual returns (uint256) {
return _quorumNumeratorHistory._checkpoints.length == 0
? _quorumNumerator
: _quorumNumeratorHistory.latest();
}

/**
* @dev Returns the quorum numerator at a specific timepoint. See {quorumDenominator}.
*/
function quorumNumerator(uint256 timepoint) public view virtual returns (uint256) {
// If history is empty, fallback to old storage
uint256 length = _quorumNumeratorHistory._checkpoints.length;
if (length == 0) return _quorumNumerator;

// Optimistic search, check the latest checkpoint
Checkpoints.Checkpoint224 memory latest = _quorumNumeratorHistory._checkpoints[length - 1];
if (latest._key <= timepoint) return latest._value;

// Otherwise, do the binary search
return _quorumNumeratorHistory.upperLookupRecent(SafeCast.toUint32(timepoint));
}

/**
* @dev Returns the quorum denominator. Defaults to 100, but may be overridden.
*/
function quorumDenominator() public view virtual returns (uint256) {
return 100;
}

/**
* @dev Returns the quorum for a timepoint, in terms of number of votes:
* `supply * numerator / denominator`.
*/
function quorum(uint256 timepoint) public view virtual override returns (uint256) {
return (token.totalSupply() * quorumNumerator(timepoint)) / quorumDenominator();
}

/**
* @dev Changes the quorum numerator.
*
* Emits a {QuorumNumeratorUpdated} event.
*
* Requirements:
*
* - Must be called through a governance proposal.
* - New numerator must be smaller or equal to the denominator.
*/
function updateQuorumNumerator(uint256 newQuorumNumerator) external virtual onlyGovernance {
_updateQuorumNumerator(newQuorumNumerator);
}

/**
* @dev Changes the quorum numerator.
*
* Emits a {QuorumNumeratorUpdated} event.
*
* Requirements:
*
* - New numerator must be smaller or equal to the denominator.
*/
function _updateQuorumNumerator(uint256 newQuorumNumerator) internal virtual {
require(
newQuorumNumerator <= quorumDenominator(),
"GovernorVotesQuorumFraction: quorumNumerator over quorumDenominator"
);

uint256 oldQuorumNumerator = quorumNumerator();

// Make sure we keep track of the original numerator in contracts upgraded from a version
// without checkpoints.
if (oldQuorumNumerator != 0 && _quorumNumeratorHistory._checkpoints.length == 0) {
_quorumNumeratorHistory._checkpoints.push(
Checkpoints.Checkpoint224({_key: 0, _value: SafeCast.toUint224(oldQuorumNumerator)})
);
}

// Set new quorum for future proposals
_quorumNumeratorHistory.push(SafeCast.toUint32(clock()), SafeCast.toUint224(newQuorumNumerator));

emit QuorumNumeratorUpdated(oldQuorumNumerator, newQuorumNumerator);
}
}
2 changes: 0 additions & 2 deletions test/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,4 @@ contract Constants {

// we have not yet deployed the Radworks Bravo Governor
address constant DEPLOYED_BRAVO_GOVERNOR = 0x1111111111111111111111111111111111111111;

uint256 constant QUORUM = 4_000_000e18;
}
8 changes: 6 additions & 2 deletions test/RadworksGovernor.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import {IGovernor} from "@openzeppelin/contracts/governance/IGovernor.sol";
import {IGovernorAlpha} from "src/interfaces/IGovernorAlpha.sol";
import {RadworksGovernorTest} from "test/helpers/RadworksGovernorTest.sol";
import {ProposalTest} from "test/helpers/ProposalTest.sol";
import "../script/DeployInput.sol";

abstract contract Constructor is RadworksGovernorTest {
function testFuzz_CorrectlySetsAllConstructorArgs(uint256 _blockNumber) public {
function test_CorrectlySetsAllConstructorArgs() public {
assertEq(governorBravo.name(), "Radworks Governor Bravo");
assertEq(address(governorBravo.token()), RAD_TOKEN);

Expand All @@ -23,7 +24,10 @@ abstract contract Constructor is RadworksGovernorTest {

assertEq(governorBravo.proposalThreshold(), INITIAL_PROPOSAL_THRESHOLD);

assertEq(governorBravo.quorum(_blockNumber), QUORUM);
assertEq(
governorBravo.quorum(block.number),
(governorBravo.token().totalSupply() * INITIAL_QUORUM_PCT) / 100
);
assertEq(governorBravo.timelock(), TIMELOCK);
assertEq(governorBravo.COUNTING_MODE(), "support=bravo&quorum=bravo");
}
Expand Down

0 comments on commit 6c439e8

Please sign in to comment.