Skip to content

Commit 67b2572

Browse files
Amxxfrangio
andcommitted
Keep track of historical quorum values (#3561)
Co-authored-by: Francisco Giordano <[email protected]> (cherry picked from commit 8ea1fc8)
1 parent 4337192 commit 67b2572

File tree

2 files changed

+46
-5
lines changed

2 files changed

+46
-5
lines changed

contracts/governance/extensions/GovernorVotesQuorumFraction.sol

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
pragma solidity ^0.8.0;
55

66
import "./GovernorVotes.sol";
7+
import "../../utils/Checkpoints.sol";
8+
import "../../utils/math/SafeCast.sol";
79

810
/**
911
* @dev Extension of {Governor} for voting weight extraction from an {ERC20Votes} token and a quorum expressed as a
@@ -12,7 +14,10 @@ import "./GovernorVotes.sol";
1214
* _Available since v4.3._
1315
*/
1416
abstract contract GovernorVotesQuorumFraction is GovernorVotes {
15-
uint256 private _quorumNumerator;
17+
using Checkpoints for Checkpoints.History;
18+
19+
uint256 private _quorumNumerator; // DEPRECATED
20+
Checkpoints.History private _quorumNumeratorHistory;
1621

1722
event QuorumNumeratorUpdated(uint256 oldQuorumNumerator, uint256 newQuorumNumerator);
1823

@@ -31,7 +36,27 @@ abstract contract GovernorVotesQuorumFraction is GovernorVotes {
3136
* @dev Returns the current quorum numerator. See {quorumDenominator}.
3237
*/
3338
function quorumNumerator() public view virtual returns (uint256) {
34-
return _quorumNumerator;
39+
return _quorumNumeratorHistory._checkpoints.length == 0 ? _quorumNumerator : _quorumNumeratorHistory.latest();
40+
}
41+
42+
/**
43+
* @dev Returns the quorum numerator at a specific block number. See {quorumDenominator}.
44+
*/
45+
function quorumNumerator(uint256 blockNumber) public view virtual returns (uint256) {
46+
// If history is empty, fallback to old storage
47+
uint256 length = _quorumNumeratorHistory._checkpoints.length;
48+
if (length == 0) {
49+
return _quorumNumerator;
50+
}
51+
52+
// Optimistic search, check the latest checkpoint
53+
Checkpoints.Checkpoint memory latest = _quorumNumeratorHistory._checkpoints[length - 1];
54+
if (latest._blockNumber <= blockNumber) {
55+
return latest._value;
56+
}
57+
58+
// Otherwize, do the binary search
59+
return _quorumNumeratorHistory.getAtBlock(blockNumber);
3560
}
3661

3762
/**
@@ -45,7 +70,7 @@ abstract contract GovernorVotesQuorumFraction is GovernorVotes {
4570
* @dev Returns the quorum for a block number, in terms of number of votes: `supply * numerator / denominator`.
4671
*/
4772
function quorum(uint256 blockNumber) public view virtual override returns (uint256) {
48-
return (token.getPastTotalSupply(blockNumber) * quorumNumerator()) / quorumDenominator();
73+
return (token.getPastTotalSupply(blockNumber) * quorumNumerator(blockNumber)) / quorumDenominator();
4974
}
5075

5176
/**
@@ -77,8 +102,17 @@ abstract contract GovernorVotesQuorumFraction is GovernorVotes {
77102
"GovernorVotesQuorumFraction: quorumNumerator over quorumDenominator"
78103
);
79104

80-
uint256 oldQuorumNumerator = _quorumNumerator;
81-
_quorumNumerator = newQuorumNumerator;
105+
uint256 oldQuorumNumerator = quorumNumerator();
106+
107+
// Make sure we keep track of the original numerator in contracts upgraded from a version without checkpoints.
108+
if (oldQuorumNumerator != 0 && _quorumNumeratorHistory._checkpoints.length == 0) {
109+
_quorumNumeratorHistory._checkpoints.push(
110+
Checkpoints.Checkpoint({_blockNumber: 0, _value: SafeCast.toUint224(oldQuorumNumerator)})
111+
);
112+
}
113+
114+
// Set new quorum for future proposals
115+
_quorumNumeratorHistory.push(newQuorumNumerator);
82116

83117
emit QuorumNumeratorUpdated(oldQuorumNumerator, newQuorumNumerator);
84118
}

test/governance/extensions/GovernorWeightQuorumFraction.test.js renamed to test/governance/extensions/GovernorVotesQuorumFraction.test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,13 @@ contract('GovernorVotesQuorumFraction', function (accounts) {
104104

105105
expect(await this.mock.quorumNumerator()).to.be.bignumber.equal(newRatio);
106106
expect(await this.mock.quorumDenominator()).to.be.bignumber.equal('100');
107+
108+
// it takes one block for the new quorum to take effect
109+
expect(await time.latestBlock().then(blockNumber => this.mock.quorum(blockNumber.subn(1))))
110+
.to.be.bignumber.equal(tokenSupply.mul(ratio).divn(100));
111+
112+
await time.advanceBlock();
113+
107114
expect(await time.latestBlock().then(blockNumber => this.mock.quorum(blockNumber.subn(1))))
108115
.to.be.bignumber.equal(tokenSupply.mul(newRatio).divn(100));
109116
});

0 commit comments

Comments
 (0)