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

Extend Checkpoints with new sizes and lookup mechanisms #3589

Merged
merged 53 commits into from
Aug 30, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
4176852
move mock
Amxx Jul 28, 2022
63edb74
Expand Checkpoint library
Amxx Jul 28, 2022
da0ea31
rename upperLookupExpEnd → upperLookupRecent
Amxx Jul 28, 2022
ed81388
sanity check when pushing checkpoints
Amxx Jul 28, 2022
b91832d
Skip redundant optimisation in VotesQuorumFraction
Amxx Jul 29, 2022
d2a829c
Merge branch 'master' into feature/checkpoint
Amxx Jul 29, 2022
2a6adb3
reset previous history lookup algorithm and reenable quorum checks
Amxx Jul 29, 2022
28f6e6f
Use unsafeAccess to save gas
Amxx Jul 29, 2022
b7e2f64
Merge branch 'master' into feature/checkpoint
Amxx Jul 29, 2022
616c51b
Regenerate contracts
Amxx Jul 29, 2022
3fb185b
Add changelog entry
Amxx Jul 29, 2022
ce0c7a6
use StorageSlot for unsafeAccess returns
Amxx Jul 29, 2022
33741e7
minor rewrite for readability
Amxx Jul 29, 2022
8425e4a
fix typo
Amxx Jul 29, 2022
74fef95
testing unsafeAccess
Amxx Jul 29, 2022
df3a9b1
fix lint
Amxx Jul 29, 2022
e182730
Update CHANGELOG.md
Amxx Jul 29, 2022
9b1f559
Merge branch 'master' into feature/checkpoint
Amxx Aug 14, 2022
768a681
Apply suggestions from code review
Amxx Aug 17, 2022
8d9b177
Update Arrays.sol
Amxx Aug 17, 2022
8dc9d4f
Run prettier as part of the procedural generation
Amxx Aug 17, 2022
4e6aa05
Reactor checkpoints to keep legacy types
Amxx Aug 17, 2022
fffde15
add a comment about files being procedurally generated
Amxx Aug 17, 2022
ac5466a
add extensions for the comments
Amxx Aug 17, 2022
a9a69cf
lint
Amxx Aug 17, 2022
4654237
shorter message
Amxx Aug 17, 2022
b486b6b
Merge branch 'master' into feature/checkpoint
Amxx Aug 25, 2022
2848c3a
regenerate & codespell
Amxx Aug 25, 2022
4c6c65e
update generation script path resolution
Amxx Aug 25, 2022
068513c
address comment from PR
Amxx Aug 26, 2022
bf12a2b
add more neatspec
Amxx Aug 26, 2022
b1d5a09
add more neatspec
Amxx Aug 26, 2022
543d516
Apply suggestions from code review
Amxx Aug 26, 2022
634e279
regenerate
Amxx Aug 26, 2022
17e1ec7
don't insert newline when no previous version is recorded
Amxx Aug 26, 2022
d1af38d
improve checkpoint opts generation
Amxx Aug 26, 2022
253e12a
codespell
Amxx Aug 26, 2022
1ccff75
test coverage
Amxx Aug 26, 2022
4d1d3da
grammar
frangio Aug 29, 2022
2d03e54
improve tests
Amxx Aug 30, 2022
69ac69e
Merge remote-tracking branch 'amxx/feature/checkpoint' into feature/c…
Amxx Aug 30, 2022
5661e71
Update test/utils/Checkpoints.test.js
Amxx Aug 30, 2022
6cd42ce
Apply suggestions from code review
Amxx Aug 30, 2022
9c84034
fix generation
Amxx Aug 30, 2022
b878b23
Update scripts/generate/templates/Checkpoints.js
Amxx Aug 30, 2022
c832e53
Update scripts/generate/templates/Checkpoints.js
Amxx Aug 30, 2022
a450545
Update scripts/generate/templates/Checkpoints.js
Amxx Aug 30, 2022
96122a3
Update scripts/generate/templates/Checkpoints.js
Amxx Aug 30, 2022
c891f58
fix generation
Amxx Aug 30, 2022
47ed78e
add History.getAtRecentBlock & tests
Amxx Aug 30, 2022
533ccef
wording
Amxx Aug 30, 2022
6caea6a
Merge branch 'master' into feature/checkpoint
Amxx Aug 30, 2022
22491c0
wrap docs
frangio Aug 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ 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;
}

Expand Down Expand Up @@ -107,7 +107,7 @@ 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)})
Checkpoints.Checkpoint224({_key: 0, _value: SafeCast.toUint224(oldQuorumNumerator)})
);
}

Expand Down
27 changes: 0 additions & 27 deletions contracts/mocks/CheckpointsImpl.sol

This file was deleted.

87 changes: 87 additions & 0 deletions contracts/mocks/CheckpointsMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../utils/Checkpoints.sol";

contract CheckpointsMock {
using Checkpoints for Checkpoints.History;

Checkpoints.History private _totalCheckpoints;

function latest() public view returns (uint256) {
return _totalCheckpoints.latest();
}

function push(uint256 value) public returns (uint256, uint256) {
return _totalCheckpoints.push(value);
}

function getAtBlock(uint256 blockNumber) public view returns (uint256) {
return _totalCheckpoints.getAtBlock(blockNumber);
}

function length() public view returns (uint256) {
return _totalCheckpoints._checkpoints.length;
}
}

contract Checkpoints224Mock {
using Checkpoints for Checkpoints.Checkpoint224[];

Checkpoints.Checkpoint224[] private _totalCheckpoints;

function latest() public view returns (uint224) {
return _totalCheckpoints.latest();
}

function push(uint32 key, uint224 value) public returns (uint224, uint224) {
return _totalCheckpoints.push(key, value);
}

function lowerLookup(uint32 key) public view returns (uint224) {
return _totalCheckpoints.lowerLookup(key);
}

function upperLookup(uint32 key) public view returns (uint224) {
return _totalCheckpoints.upperLookup(key);
}

function upperLookupExpEnd(uint32 key) public view returns (uint224) {
return _totalCheckpoints.upperLookupExpEnd(key);
}

function length() public view returns (uint256) {
return _totalCheckpoints.length;
}
}

contract Checkpoints160Mock {
using Checkpoints for Checkpoints.Checkpoint160[];

Checkpoints.Checkpoint160[] private _totalCheckpoints;

function latest() public view returns (uint160) {
return _totalCheckpoints.latest();
}

function push(uint96 key, uint160 value) public returns (uint160, uint160) {
return _totalCheckpoints.push(key, value);
}

function lowerLookup(uint96 key) public view returns (uint160) {
return _totalCheckpoints.lowerLookup(key);
}

function upperLookup(uint96 key) public view returns (uint160) {
return _totalCheckpoints.upperLookup(key);
}

function upperLookupExpEnd(uint96 key) public view returns (uint224) {
return _totalCheckpoints.upperLookupExpEnd(key);
}

function length() public view returns (uint256) {
return _totalCheckpoints.length;
}
}
200 changes: 174 additions & 26 deletions contracts/utils/Checkpoints.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/Checkpoints.sol)

pragma solidity ^0.8.0;

import "./math/Math.sol";
Expand All @@ -15,21 +16,187 @@ import "./math/SafeCast.sol";
* _Available since v4.5._
*/
library Checkpoints {
Amxx marked this conversation as resolved.
Show resolved Hide resolved
struct Checkpoint {
Amxx marked this conversation as resolved.
Show resolved Hide resolved
uint32 _blockNumber;
struct Checkpoint224 {
uint32 _key;
uint224 _value;
}

function latest(Checkpoint224[] storage self) internal view returns (uint224) {
uint256 pos = self.length;
return pos == 0 ? 0 : self[pos - 1]._value;
}

function push(
Amxx marked this conversation as resolved.
Show resolved Hide resolved
Checkpoint224[] storage self,
uint32 key,
uint224 value
) internal returns (uint224, uint224) {
uint256 pos = self.length;
uint224 old = latest(self);
if (pos > 0 && self[pos - 1]._key == key) {
self[pos - 1]._value = value;
} else {
self.push(Checkpoint224({_key: key, _value: value}));
}
return (old, value);
}

function lowerLookup(Checkpoint224[] storage self, uint32 key) internal view returns (uint224) {
uint256 length = self.length;
uint256 pos = _lowerDichotomicLookup(self, key, 0, length);
return pos == length ? 0 : self[pos]._value;
}

function upperLookup(Checkpoint224[] storage self, uint32 key) internal view returns (uint224) {
uint256 length = self.length;
uint256 pos = _upperDichotomicLookup(self, key, 0, length);
return pos == 0 ? 0 : self[pos - 1]._value;
}

function upperLookupExpEnd(Checkpoint224[] storage self, uint32 key) internal view returns (uint224) {
uint256 length = self.length;
uint256 offset = 1;

while (offset <= length && self[length - offset]._key > key) {
offset <<= 1;
}

uint256 low = offset < length ? length - offset : 0;
uint256 high = length - (offset >> 1);
uint256 pos = _upperDichotomicLookup(self, key, low, high);

return pos == 0 ? 0 : self[pos - 1]._value;
}

function _upperDichotomicLookup(
Amxx marked this conversation as resolved.
Show resolved Hide resolved
Checkpoint224[] storage self,
uint32 key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (self[mid]._key > key) {
high = mid;
} else {
low = mid + 1;
}
}
return high;
}

function _lowerDichotomicLookup(
Checkpoint224[] storage self,
uint32 key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (self[mid]._key < key) {
low = mid + 1;
} else {
high = mid;
}
}
return high;
}

struct Checkpoint160 {
uint96 _key;
uint160 _value;
}

function latest(Checkpoint160[] storage self) internal view returns (uint160) {
uint256 pos = self.length;
return pos == 0 ? 0 : self[pos - 1]._value;
}

function push(
Checkpoint160[] storage self,
uint96 key,
uint160 value
) internal returns (uint160, uint160) {
uint256 pos = self.length;
uint160 old = latest(self);
if (pos > 0 && self[pos - 1]._key == key) {
self[pos - 1]._value = value;
} else {
self.push(Checkpoint160({_key: key, _value: value}));
}
return (old, value);
}

function lowerLookup(Checkpoint160[] storage self, uint96 key) internal view returns (uint160) {
uint256 length = self.length;
uint256 pos = _lowerDichotomicLookup(self, key, 0, length);
return pos == length ? 0 : self[pos]._value;
}

function upperLookup(Checkpoint160[] storage self, uint96 key) internal view returns (uint160) {
uint256 length = self.length;
uint256 pos = _upperDichotomicLookup(self, key, 0, length);
return pos == 0 ? 0 : self[pos - 1]._value;
}

function upperLookupExpEnd(Checkpoint160[] storage self, uint96 key) internal view returns (uint224) {
uint256 length = self.length;
uint256 offset = 1;

while (offset <= length && self[length - offset]._key > key) {
offset <<= 1;
frangio marked this conversation as resolved.
Show resolved Hide resolved
}

uint256 low = offset < length ? length - offset : 0;
uint256 high = length - (offset >> 1);
uint256 pos = _upperDichotomicLookup(self, key, low, high);

return pos == 0 ? 0 : self[pos - 1]._value;
}

function _upperDichotomicLookup(
Checkpoint160[] storage self,
uint96 key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (self[mid]._key > key) {
high = mid;
} else {
low = mid + 1;
}
}
return high;
}

function _lowerDichotomicLookup(
Checkpoint160[] storage self,
uint96 key,
uint256 low,
uint256 high
) private view returns (uint256) {
while (low < high) {
uint256 mid = Math.average(low, high);
if (self[mid]._key < key) {
low = mid + 1;
} else {
high = mid;
}
}
return high;
}

struct History {
Checkpoint[] _checkpoints;
Checkpoint224[] _checkpoints;
}

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

/**
Expand All @@ -39,17 +206,7 @@ library Checkpoints {
function getAtBlock(History storage self, uint256 blockNumber) internal view returns (uint256) {
require(blockNumber < block.number, "Checkpoints: block not yet mined");

uint256 high = self._checkpoints.length;
uint256 low = 0;
while (low < high) {
uint256 mid = Math.average(low, high);
if (self._checkpoints[mid]._blockNumber > blockNumber) {
high = mid;
} else {
low = mid + 1;
}
}
return high == 0 ? 0 : self._checkpoints[high - 1]._value;
return upperLookupExpEnd(self._checkpoints, SafeCast.toUint32(blockNumber));
}

/**
Expand All @@ -58,16 +215,7 @@ library Checkpoints {
* Returns previous value and new value.
*/
function push(History storage self, uint256 value) internal returns (uint256, uint256) {
uint256 pos = self._checkpoints.length;
uint256 old = latest(self);
if (pos > 0 && self._checkpoints[pos - 1]._blockNumber == block.number) {
self._checkpoints[pos - 1]._value = SafeCast.toUint224(value);
} else {
self._checkpoints.push(
Checkpoint({_blockNumber: SafeCast.toUint32(block.number), _value: SafeCast.toUint224(value)})
);
}
return (old, value);
return push(self._checkpoints, SafeCast.toUint32(block.number), SafeCast.toUint224(value));
}

/**
Expand Down
2 changes: 2 additions & 0 deletions scripts/generate/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ function getVersion (path) {
}

for (const [ file, template ] of Object.entries({
'utils/Checkpoints.sol': './templates/Checkpoints',
'mocks/CheckpointsMock.sol': './templates/CheckpointsMock',
'utils/math/SafeCast.sol': './templates/SafeCast',
'mocks/SafeCastMock.sol': './templates/SafeCastMock',
})) {
Expand Down