Skip to content
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

Nextv5.0 checkpoint history removal #4170

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions contracts/governance/extensions/GovernorVotesQuorumFraction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import "../../utils/math/SafeCast.sol";
* _Available since v4.3._
*/
abstract contract GovernorVotesQuorumFraction is GovernorVotes {
using Checkpoints for Checkpoints.History;
using Checkpoints for Checkpoints.Trace224;

uint256 private _quorumNumerator; // DEPRECATED
Checkpoints.History private _quorumNumeratorHistory;
Checkpoints.Trace224 private _quorumNumeratorHistory;

event QuorumNumeratorUpdated(uint256 oldQuorumNumerator, uint256 newQuorumNumerator);

Expand Down Expand Up @@ -50,13 +50,14 @@ abstract contract GovernorVotesQuorumFraction is GovernorVotes {
}

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

// Otherwise, do the binary search
return _quorumNumeratorHistory.getAtBlock(blockNumber);
uint32 key = SafeCast.toUint32(blockNumber);
return _quorumNumeratorHistory.upperLookup(key);
}

/**
Expand Down Expand Up @@ -106,13 +107,11 @@ abstract contract GovernorVotesQuorumFraction is GovernorVotes {

// 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.Checkpoint({_blockNumber: 0, _value: SafeCast.toUint224(oldQuorumNumerator)})
);
_quorumNumeratorHistory.push(0, SafeCast.toUint224(oldQuorumNumerator));
}

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

emit QuorumNumeratorUpdated(oldQuorumNumerator, newQuorumNumerator);
}
Expand Down
36 changes: 25 additions & 11 deletions contracts/governance/utils/Votes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ import "../../utils/math/SafeCast.sol";
* _Available since v4.5._
*/
abstract contract Votes is IVotes, Context, EIP712, Nonces {
using Checkpoints for Checkpoints.History;
using Checkpoints for Checkpoints.Trace224;

bytes32 private constant _DELEGATION_TYPEHASH =
keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");

mapping(address => address) private _delegation;
mapping(address => Checkpoints.History) private _delegateCheckpoints;
Checkpoints.History private _totalCheckpoints;
mapping(address => Checkpoints.Trace224) private _delegateCheckpoints;
Checkpoints.Trace224 private _totalCheckpoints;

/**
* @dev Returns the current amount of votes that `account` has.
Expand All @@ -54,7 +54,9 @@ abstract contract Votes is IVotes, Context, EIP712, Nonces {
* - `blockNumber` must have been already mined
*/
function getPastVotes(address account, uint256 blockNumber) public view virtual override returns (uint256) {
return _delegateCheckpoints[account].getAtProbablyRecentBlock(blockNumber);
require(blockNumber < block.number, "Votes: block not yet mined");
uint32 key = SafeCast.toUint32(blockNumber);
return _delegateCheckpoints[account].upperLookup(key);
}

/**
Expand All @@ -69,7 +71,9 @@ abstract contract Votes is IVotes, Context, EIP712, Nonces {
* - `blockNumber` must have been already mined
*/
function getPastTotalSupply(uint256 blockNumber) public view virtual override returns (uint256) {
return _totalCheckpoints.getAtProbablyRecentBlock(blockNumber);
require(blockNumber < block.number, "Votes: block not yet mined");
uint32 key = SafeCast.toUint32(blockNumber);
return _totalCheckpoints.upperLookup(key);
}

/**
Expand Down Expand Up @@ -135,10 +139,12 @@ abstract contract Votes is IVotes, Context, EIP712, Nonces {
*/
function _transferVotingUnits(address from, address to, uint256 amount) internal virtual {
if (from == address(0)) {
_totalCheckpoints.push(_add, amount);
uint224 latest = _totalCheckpoints.latest();
_totalCheckpoints.push(SafeCast.toUint32(block.number), latest + SafeCast.toUint224(amount));
}
if (to == address(0)) {
_totalCheckpoints.push(_subtract, amount);
uint224 latest = _totalCheckpoints.latest();
_totalCheckpoints.push(SafeCast.toUint32(block.number), latest - SafeCast.toUint224(amount));
}
_moveDelegateVotes(delegates(from), delegates(to), amount);
}
Expand All @@ -149,11 +155,19 @@ abstract contract Votes is IVotes, Context, EIP712, Nonces {
function _moveDelegateVotes(address from, address to, uint256 amount) private {
if (from != to && amount > 0) {
if (from != address(0)) {
(uint256 oldValue, uint256 newValue) = _delegateCheckpoints[from].push(_subtract, amount);
uint224 latest = _delegateCheckpoints[from].latest();
(uint256 oldValue, uint256 newValue) = _delegateCheckpoints[from].push(
SafeCast.toUint32(block.number),
latest - SafeCast.toUint224(amount)
);
emit DelegateVotesChanged(from, oldValue, newValue);
}
if (to != address(0)) {
(uint256 oldValue, uint256 newValue) = _delegateCheckpoints[to].push(_add, amount);
uint224 latest = _delegateCheckpoints[to].latest();
(uint256 oldValue, uint256 newValue) = _delegateCheckpoints[to].push(
SafeCast.toUint32(block.number),
latest + SafeCast.toUint224(amount)
);
emit DelegateVotesChanged(to, oldValue, newValue);
}
}
Expand All @@ -169,8 +183,8 @@ abstract contract Votes is IVotes, Context, EIP712, Nonces {
/**
* @dev Get the `pos`-th checkpoint for `account`.
*/
function _checkpoints(address account, uint32 pos) internal view virtual returns (Checkpoints.Checkpoint memory) {
return _delegateCheckpoints[account].getAtPosition(pos);
function _checkpoints(address account, uint32 pos) internal view virtual returns (Checkpoints.Checkpoint224 memory) {
return _delegateCheckpoints[account]._checkpoints[pos];
}

function _add(uint256 a, uint256 b) private pure returns (uint256) {
Expand Down
2 changes: 1 addition & 1 deletion contracts/token/ERC20/extensions/ERC20Votes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ abstract contract ERC20Votes is ERC20, Votes {
/**
* @dev Get the `pos`-th checkpoint for `account`.
*/
function checkpoints(address account, uint32 pos) public view virtual returns (Checkpoints.Checkpoint memory) {
function checkpoints(address account, uint32 pos) public view virtual returns (Checkpoints.Checkpoint224 memory) {
return _checkpoints(account, pos);
}

Expand Down
196 changes: 0 additions & 196 deletions contracts/utils/Checkpoints.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,202 +17,6 @@ import "./math/SafeCast.sol";
* _Available since v4.5._
*/
library Checkpoints {
struct History {
Checkpoint[] _checkpoints;
}

struct Checkpoint {
uint32 _blockNumber;
uint224 _value;
}

/**
* @dev Returns the value at a given block number. If a checkpoint is not available at that block, the closest one
* before it is returned, or zero otherwise. Because the number returned corresponds to that at the end of the
* block, the requested block number must be in the past, excluding the current block.
*/
function getAtBlock(History storage self, uint256 blockNumber) internal view returns (uint256) {
require(blockNumber < block.number, "Checkpoints: block not yet mined");
uint32 key = SafeCast.toUint32(blockNumber);

uint256 len = self._checkpoints.length;
uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len);
return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
}

/**
* @dev Returns the value at a given block number. If a checkpoint is not available at that block, the closest one
* before it is returned, or zero otherwise. Similar to {upperLookup} but optimized for the case when the searched
* checkpoint is probably "recent", defined as being among the last sqrt(N) checkpoints where N is the number of
* checkpoints.
*/
function getAtProbablyRecentBlock(History storage self, uint256 blockNumber) internal view returns (uint256) {
require(blockNumber < block.number, "Checkpoints: block not yet mined");
uint32 key = SafeCast.toUint32(blockNumber);

uint256 len = self._checkpoints.length;

uint256 low = 0;
uint256 high = len;

if (len > 5) {
uint256 mid = len - Math.sqrt(len);
if (key < _unsafeAccess(self._checkpoints, mid)._blockNumber) {
high = mid;
} else {
low = mid + 1;
}
}

uint256 pos = _upperBinaryLookup(self._checkpoints, key, low, high);

return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
}

/**
* @dev Returns checkpoint at given position.
*/
function getAtPosition(History storage self, uint32 pos) internal view returns (Checkpoint memory) {
return self._checkpoints[pos];
}

/**
* @dev Pushes a value onto a History so that it is stored as the checkpoint for the current block.
*
* Returns previous value and new value.
*/
function push(History storage self, uint256 value) internal returns (uint256, uint256) {
return _insert(self._checkpoints, SafeCast.toUint32(block.number), SafeCast.toUint224(value));
}

/**
* @dev Pushes a value onto a History, by updating the latest value using binary operation `op`. The new value will
* be set to `op(latest, delta)`.
*
* Returns previous value and new value.
*/
function push(
History storage self,
function(uint256, uint256) view returns (uint256) op,
uint256 delta
) internal returns (uint256, uint256) {
return push(self, op(latest(self), delta));
}

/**
* @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints.
*/
function latest(History storage self) internal view returns (uint224) {
uint256 pos = self._checkpoints.length;
return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value;
}

/**
* @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value
* in the most recent checkpoint.
*/
function latestCheckpoint(
History storage self
) internal view returns (bool exists, uint32 _blockNumber, uint224 _value) {
uint256 pos = self._checkpoints.length;
if (pos == 0) {
return (false, 0, 0);
} else {
Checkpoint memory ckpt = _unsafeAccess(self._checkpoints, pos - 1);
return (true, ckpt._blockNumber, ckpt._value);
}
}

/**
* @dev Returns the number of checkpoint.
*/
function length(History storage self) internal view returns (uint256) {
return self._checkpoints.length;
}

/**
* @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint,
* or by updating the last one.
*/
function _insert(Checkpoint[] storage self, uint32 key, uint224 value) private returns (uint224, uint224) {
uint256 pos = self.length;

if (pos > 0) {
// Copying to memory is important here.
Checkpoint memory last = _unsafeAccess(self, pos - 1);

// Checkpoint keys must be non-decreasing.
require(last._blockNumber <= key, "Checkpoint: decreasing keys");

// Update or push new checkpoint
if (last._blockNumber == key) {
_unsafeAccess(self, pos - 1)._value = value;
} else {
self.push(Checkpoint({_blockNumber: key, _value: value}));
}
return (last._value, value);
} else {
self.push(Checkpoint({_blockNumber: key, _value: value}));
return (0, value);
}
}

/**
* @dev Return the index of the oldest checkpoint whose key is greater than the search key, or `high` if there is none.
* `low` and `high` define a section where to do the search, with inclusive `low` and exclusive `high`.
*
* WARNING: `high` should not be greater than the array's length.
*/
function _upperBinaryLookup(
Checkpoint[] storage self,
uint32 key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (_unsafeAccess(self, mid)._blockNumber > key) {
high = mid;
} else {
low = mid + 1;
}
}
return high;
}

/**
* @dev Return the index of the oldest checkpoint whose key is greater or equal than the search key, or `high` if there is none.
* `low` and `high` define a section where to do the search, with inclusive `low` and exclusive `high`.
*
* WARNING: `high` should not be greater than the array's length.
*/
function _lowerBinaryLookup(
Checkpoint[] storage self,
uint32 key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (_unsafeAccess(self, mid)._blockNumber < key) {
low = mid + 1;
} else {
high = mid;
}
}
return high;
}

/**
* @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds.
*/
function _unsafeAccess(Checkpoint[] storage self, uint256 pos) private pure returns (Checkpoint storage result) {
assembly {
mstore(0, self.slot)
result.slot := add(keccak256(0, 0x20), pos)
}
}

struct Trace224 {
Checkpoint224[] _checkpoints;
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"clean": "hardhat clean && rimraf build contracts/build",
"prepare": "scripts/prepare.sh",
"prepack": "scripts/prepack.sh",
"generate": "scripts/generate/run.js",
"generate": "node scripts/generate/run.js",
"release": "scripts/release/release.sh",
"version": "scripts/release/version.sh",
"test": "hardhat test",
Expand Down
Loading