@@ -3,8 +3,8 @@ pragma solidity ^0.8.20;
33
44import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol " ;
55import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol " ;
6- import {IFractionalGovernor} from ". /interfaces/IFractionalGovernor.sol " ;
7- import {IVotingToken} from ". /interfaces/IVotingToken.sol " ;
6+ import {IFractionalGovernor} from "src /interfaces/IFractionalGovernor.sol " ;
7+ import {IVotingToken} from "src /interfaces/IVotingToken.sol " ;
88
99/// @notice This is an abstract contract designed to make it easy to build clients
1010/// for governance systems that inherit from GovernorCountingFractional, a.k.a.
@@ -51,8 +51,8 @@ abstract contract FlexVotingClient {
5151
5252 // @dev Trace208 is used instead of Trace224 because the former allocates 48
5353 // bits to its _key. We need at least 48 bits because the _key is going to be
54- // a block number. And EIP-6372 specifies that when block numbers are used for
55- // internal clocks (as they are for ERC20Votes) they need to be uint48s.
54+ // a timepoint. Timepoints in the context of ERC20Votes and ERC721Votes
55+ // conform to the EIP-6372 standard, which specifies they be uint48s.
5656 using Checkpoints for Checkpoints.Trace208;
5757
5858 /// @notice The voting options corresponding to those used in the Governor.
@@ -149,7 +149,7 @@ abstract contract FlexVotingClient {
149149 revert FlexVotingClient__NoVotesExpressed ();
150150 }
151151
152- uint256 _proposalSnapshotBlockNumber = GOVERNOR.proposalSnapshot (proposalId);
152+ uint256 _proposalSnapshot = GOVERNOR.proposalSnapshot (proposalId);
153153
154154 // We use the snapshot of total raw balances to determine the weight with
155155 // which to vote. We do this for two reasons:
@@ -167,12 +167,11 @@ abstract contract FlexVotingClient {
167167 // Using the total raw balance to proportion votes in this way means that in
168168 // many circumstances this function will not cast votes with all of its
169169 // weight.
170- uint256 _totalRawBalanceAtSnapshot = getPastTotalBalance (_proposalSnapshotBlockNumber );
170+ uint256 _totalRawBalanceAtSnapshot = getPastTotalBalance (_proposalSnapshot );
171171
172172 // We need 256 bits because of the multiplication we're about to do.
173- uint256 _votingWeightAtSnapshot = IVotingToken (address (GOVERNOR.token ())).getPastVotes (
174- address (this ), _proposalSnapshotBlockNumber
175- );
173+ uint256 _votingWeightAtSnapshot =
174+ IVotingToken (address (GOVERNOR.token ())).getPastVotes (address (this ), _proposalSnapshot);
176175
177176 // forVotesRaw forVoteWeight
178177 // --------------------- = ------------------
@@ -205,21 +204,52 @@ abstract contract FlexVotingClient {
205204
206205 /// @dev Checkpoints the _user's current raw balance.
207206 function _checkpointRawBalanceOf (address _user ) internal {
208- balanceCheckpoints[_user].push (SafeCast.toUint48 (block .number ), _rawBalanceOf (_user));
207+ balanceCheckpoints[_user].push (IVotingToken (GOVERNOR.token ()).clock (), _rawBalanceOf (_user));
208+ }
209+
210+ /// @dev Checkpoints the total balance after applying `_delta`.
211+ function _checkpointTotalBalance (int256 _delta ) internal {
212+ // The casting in this function is safe since:
213+ // - if oldTotal + delta > int256.max it will panic and revert.
214+ // - if |delta| <= oldTotal
215+ // * there is no risk of wrapping
216+ // - if |delta| > oldTotal
217+ // * uint256(oldTotal + delta) will wrap but the wrapped value will
218+ // necessarily be greater than uint208.max, so SafeCast will revert.
219+ // * the lowest that oldTotal + delta can be is int256.min (when
220+ // oldTotal is 0 and delta is int256.min). The wrapped value of a
221+ // negative signed integer is:
222+ // wrapped(integer) = uint256.max + integer
223+ // Substituting:
224+ // wrapped(int256.min) = uint256.max + int256.min
225+ // But:
226+ // uint256.max + int256.min > uint208.max
227+ // Substituting again:
228+ // wrapped(int256.min) > uint208.max, which will revert when safecast.
229+ uint256 _oldTotal = uint256 (totalBalanceCheckpoints.latest ());
230+ uint256 _newTotal = uint256 (int256 (_oldTotal) + _delta);
231+
232+ totalBalanceCheckpoints.push (
233+ IVotingToken (GOVERNOR.token ()).clock (), SafeCast.toUint208 (_newTotal)
234+ );
209235 }
210236
211- /// @notice Returns the `_user`'s raw balance at `_blockNumber `.
237+ /// @notice Returns the `_user`'s raw balance at `_timepoint `.
212238 /// @param _user The account that's historical raw balance will be looked up.
213- /// @param _blockNumber The block at which to lookup the _user's raw balance.
214- function getPastRawBalance (address _user , uint256 _blockNumber ) public view returns (uint256 ) {
215- uint48 key = SafeCast.toUint48 (_blockNumber);
239+ /// @param _timepoint The timepoint at which to lookup the _user's raw
240+ /// balance, either a block number or a timestamp as determined by
241+ /// {GOVERNOR.token().clock()}.
242+ function getPastRawBalance (address _user , uint256 _timepoint ) public view returns (uint256 ) {
243+ uint48 key = SafeCast.toUint48 (_timepoint);
216244 return balanceCheckpoints[_user].upperLookup (key);
217245 }
218246
219- /// @notice Returns the sum total of raw balances of all users at `_blockNumber`.
220- /// @param _blockNumber The block at which to lookup the total balance.
221- function getPastTotalBalance (uint256 _blockNumber ) public view returns (uint256 ) {
222- uint48 key = SafeCast.toUint48 (_blockNumber);
247+ /// @notice Returns the sum total of raw balances of all users at `_timepoint`.
248+ /// @param _timepoint The timepoint at which to lookup the total balance,
249+ /// either a block number or a timestamp as determined by
250+ /// {GOVERNOR.token().clock()}.
251+ function getPastTotalBalance (uint256 _timepoint ) public view returns (uint256 ) {
252+ uint48 key = SafeCast.toUint48 (_timepoint);
223253 return totalBalanceCheckpoints.upperLookup (key);
224254 }
225255}
0 commit comments