diff --git a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol index 8efefce3959..0c4aea4e083 100644 --- a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol +++ b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol @@ -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); @@ -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); } /** @@ -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); } diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index ccac72bce2a..a6657b361eb 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -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. @@ -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); } /** @@ -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); } /** @@ -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); } @@ -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); } } @@ -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) { diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index 225d08c443e..fe25b9bb506 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -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); } diff --git a/contracts/utils/Checkpoints.sol b/contracts/utils/Checkpoints.sol index 76203edd2ce..ada1bbedcb7 100644 --- a/contracts/utils/Checkpoints.sol +++ b/contracts/utils/Checkpoints.sol @@ -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; } diff --git a/package.json b/package.json index 72978ffec05..5b0eb34fe95 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index c4673d245f9..bb65b653a5f 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -62,84 +62,7 @@ function upperLookup(${opts.historyTypeName} storage self, ${opts.keyTypeName} k uint256 pos = _upperBinaryLookup(self.${opts.checkpointFieldName}, key, 0, len); return pos == 0 ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1).${opts.valueFieldName}; } -`; - -const legacyOperations = opts => `\ -/** - * @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(${opts.historyTypeName} 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.${opts.checkpointFieldName}.length; - uint256 pos = _upperBinaryLookup(self.${opts.checkpointFieldName}, key, 0, len); - return pos == 0 ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1).${opts.valueFieldName}; -} -/** - * @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(${opts.historyTypeName} 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.${opts.checkpointFieldName}.length; - - uint256 low = 0; - uint256 high = len; - - if (len > 5) { - uint256 mid = len - Math.sqrt(len); - if (key < _unsafeAccess(self.${opts.checkpointFieldName}, mid)._blockNumber) { - high = mid; - } else { - low = mid + 1; - } - } - - uint256 pos = _upperBinaryLookup(self.${opts.checkpointFieldName}, key, low, high); - - return pos == 0 ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1).${opts.valueFieldName}; -} - -/** - * @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(${opts.historyTypeName} storage self, uint256 value) internal returns (uint256, uint256) { - return _insert(self.${opts.checkpointFieldName}, 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( - ${opts.historyTypeName} storage self, - function(uint256, uint256) view returns (uint256) op, - uint256 delta -) internal returns (uint256, uint256) { - return push(self, op(latest(self), delta)); -} -`; - -const common = opts => `\ /** * @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints. */ @@ -278,29 +201,19 @@ const defaultOpts = size => ({ keyTypeName: `uint${256 - size}`, keyFieldName: '_key', valueTypeName: `uint${size}`, + valueTypeNameCap: `Uint${size}`, valueFieldName: '_value', }); const OPTS = VALUE_SIZES.map(size => defaultOpts(size)); -const LEGACY_OPTS = { - ...defaultOpts(224), - historyTypeName: 'History', - checkpointTypeName: 'Checkpoint', - keyFieldName: '_blockNumber', -}; - // GENERATE module.exports = format( header.trimEnd(), 'library Checkpoints {', [ - // Legacy types & functions - types(LEGACY_OPTS), - legacyOperations(LEGACY_OPTS), - common(LEGACY_OPTS), // New flavors - ...OPTS.flatMap(opts => [types(opts), operations(opts), common(opts)]), + ...OPTS.flatMap(opts => [types(opts), operations(opts)]), ], '}', ); diff --git a/test/governance/utils/Votes.behavior.js b/test/governance/utils/Votes.behavior.js index 7cc8f133eb9..70de6e540d2 100644 --- a/test/governance/utils/Votes.behavior.js +++ b/test/governance/utils/Votes.behavior.js @@ -281,7 +281,7 @@ function shouldBehaveLikeVotes(accounts, tokens, fungible = true) { expect(await this.votes.getPastTotalSupply(blockNumber + 9)).to.be.bignumber.equal(weight[2]); expect(await this.votes.getPastTotalSupply(blockNumber + 10)).to.be.bignumber.equal(weight[2]); expect(await this.votes.getPastTotalSupply(blockNumber + 11)).to.be.bignumber.equal('0'); - await expectRevert(this.votes.getPastTotalSupply(blockNumber + 12), 'Checkpoints: block not yet mined'); + await expectRevert(this.votes.getPastTotalSupply(blockNumber + 12), 'Votes: block not yet mined'); }); }); diff --git a/test/governance/utils/Votes.test.js b/test/governance/utils/Votes.test.js index c0c574df4e0..e27a8ccf1c0 100644 --- a/test/governance/utils/Votes.test.js +++ b/test/governance/utils/Votes.test.js @@ -37,7 +37,7 @@ contract('Votes', function (accounts) { it('reverts if block number >= current block', async function () { await expectRevert( this.votes.getPastTotalSupply(this.txs.at(-1).receipt.blockNumber + 1), - 'Checkpoints: block not yet mined', + 'Votes: block not yet mined', ); }); diff --git a/test/token/ERC20/extensions/ERC20Votes.test.js b/test/token/ERC20/extensions/ERC20Votes.test.js index 8d010ec6bc9..d3e237283ab 100644 --- a/test/token/ERC20/extensions/ERC20Votes.test.js +++ b/test/token/ERC20/extensions/ERC20Votes.test.js @@ -422,7 +422,7 @@ contract('ERC20Votes', function (accounts) { describe('getPastVotes', function () { it('reverts if block number >= current block', async function () { - await expectRevert(this.token.getPastVotes(other1, 5e10), 'Checkpoints: block not yet mined'); + await expectRevert(this.token.getPastVotes(other1, 5e10), 'Votes: block not yet mined'); }); it('returns 0 if there are no checkpoints', async function () { @@ -503,7 +503,7 @@ contract('ERC20Votes', function (accounts) { }); it('reverts if block number >= current block', async function () { - await expectRevert(this.token.getPastTotalSupply(5e10), 'Checkpoints: block not yet mined'); + await expectRevert(this.token.getPastTotalSupply(5e10), 'Votes: block not yet mined'); }); it('returns 0 if there are no checkpoints', async function () { diff --git a/test/token/ERC20/extensions/ERC20VotesComp.test.js b/test/token/ERC20/extensions/ERC20VotesComp.test.js index ef4a22be225..c7ef1cad19a 100644 --- a/test/token/ERC20/extensions/ERC20VotesComp.test.js +++ b/test/token/ERC20/extensions/ERC20VotesComp.test.js @@ -389,7 +389,7 @@ contract('ERC20VotesComp', function (accounts) { describe('getPriorVotes', function () { it('reverts if block number >= current block', async function () { - await expectRevert(this.token.getPriorVotes(other1, 5e10), 'Checkpoints: block not yet mined'); + await expectRevert(this.token.getPriorVotes(other1, 5e10), 'Votes: block not yet mined'); }); it('returns 0 if there are no checkpoints', async function () { @@ -470,7 +470,7 @@ contract('ERC20VotesComp', function (accounts) { }); it('reverts if block number >= current block', async function () { - await expectRevert(this.token.getPastTotalSupply(5e10), 'Checkpoints: block not yet mined'); + await expectRevert(this.token.getPastTotalSupply(5e10), 'Votes: block not yet mined'); }); it('returns 0 if there are no checkpoints', async function () { diff --git a/test/utils/Checkpoints.test.js b/test/utils/Checkpoints.test.js index d43d469cc17..47b5153410d 100644 --- a/test/utils/Checkpoints.test.js +++ b/test/utils/Checkpoints.test.js @@ -1,9 +1,7 @@ -const { expectRevert, time } = require('@openzeppelin/test-helpers'); +const { expectRevert } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); -const { batchInBlock } = require('../helpers/txpool'); - const $Checkpoints = artifacts.require('$Checkpoints'); const first = array => (array.length ? array[0] : undefined); @@ -14,103 +12,6 @@ contract('Checkpoints', function () { this.mock = await $Checkpoints.new(); }); - describe('History checkpoints', function () { - const latest = (self, ...args) => self.methods['$latest_Checkpoints_History(uint256)'](0, ...args); - const latestCheckpoint = (self, ...args) => - self.methods['$latestCheckpoint_Checkpoints_History(uint256)'](0, ...args); - const push = (self, ...args) => self.methods['$push(uint256,uint256)'](0, ...args); - const getAtBlock = (self, ...args) => self.methods['$getAtBlock(uint256,uint256)'](0, ...args); - const getAtRecentBlock = (self, ...args) => self.methods['$getAtProbablyRecentBlock(uint256,uint256)'](0, ...args); - const getLength = (self, ...args) => self.methods['$length_Checkpoints_History(uint256)'](0, ...args); - - describe('without checkpoints', function () { - it('returns zero as latest value', async function () { - expect(await latest(this.mock)).to.be.bignumber.equal('0'); - - const ckpt = await latestCheckpoint(this.mock); - expect(ckpt[0]).to.be.equal(false); - expect(ckpt[1]).to.be.bignumber.equal('0'); - expect(ckpt[2]).to.be.bignumber.equal('0'); - }); - - it('returns zero as past value', async function () { - await time.advanceBlock(); - expect(await getAtBlock(this.mock, (await web3.eth.getBlockNumber()) - 1)).to.be.bignumber.equal('0'); - expect(await getAtRecentBlock(this.mock, (await web3.eth.getBlockNumber()) - 1)).to.be.bignumber.equal('0'); - }); - }); - - describe('with checkpoints', function () { - beforeEach('pushing checkpoints', async function () { - this.tx1 = await push(this.mock, 1); - this.tx2 = await push(this.mock, 2); - await time.advanceBlock(); - this.tx3 = await push(this.mock, 3); - await time.advanceBlock(); - await time.advanceBlock(); - }); - - it('returns latest value', async function () { - expect(await latest(this.mock)).to.be.bignumber.equal('3'); - - const ckpt = await latestCheckpoint(this.mock); - expect(ckpt[0]).to.be.equal(true); - expect(ckpt[1]).to.be.bignumber.equal(web3.utils.toBN(this.tx3.receipt.blockNumber)); - expect(ckpt[2]).to.be.bignumber.equal(web3.utils.toBN('3')); - }); - - for (const getAtBlockVariant of [getAtBlock, getAtRecentBlock]) { - describe(`lookup: ${getAtBlockVariant}`, function () { - it('returns past values', async function () { - expect(await getAtBlockVariant(this.mock, this.tx1.receipt.blockNumber - 1)).to.be.bignumber.equal('0'); - expect(await getAtBlockVariant(this.mock, this.tx1.receipt.blockNumber)).to.be.bignumber.equal('1'); - expect(await getAtBlockVariant(this.mock, this.tx2.receipt.blockNumber)).to.be.bignumber.equal('2'); - // Block with no new checkpoints - expect(await getAtBlockVariant(this.mock, this.tx2.receipt.blockNumber + 1)).to.be.bignumber.equal('2'); - expect(await getAtBlockVariant(this.mock, this.tx3.receipt.blockNumber)).to.be.bignumber.equal('3'); - expect(await getAtBlockVariant(this.mock, this.tx3.receipt.blockNumber + 1)).to.be.bignumber.equal('3'); - }); - it('reverts if block number >= current block', async function () { - await expectRevert( - getAtBlockVariant(this.mock, await web3.eth.getBlockNumber()), - 'Checkpoints: block not yet mined', - ); - - await expectRevert( - getAtBlockVariant(this.mock, (await web3.eth.getBlockNumber()) + 1), - 'Checkpoints: block not yet mined', - ); - }); - }); - } - - it('multiple checkpoints in the same block', async function () { - const lengthBefore = await getLength(this.mock); - - await batchInBlock([ - () => push(this.mock, 8, { gas: 100000 }), - () => push(this.mock, 9, { gas: 100000 }), - () => push(this.mock, 10, { gas: 100000 }), - ]); - - expect(await getLength(this.mock)).to.be.bignumber.equal(lengthBefore.addn(1)); - expect(await latest(this.mock)).to.be.bignumber.equal('10'); - }); - - it('more than 5 checkpoints', async function () { - for (let i = 4; i <= 6; i++) { - await push(this.mock, i); - } - expect(await getLength(this.mock)).to.be.bignumber.equal('6'); - const block = await web3.eth.getBlockNumber(); - // recent - expect(await getAtRecentBlock(this.mock, block - 1)).to.be.bignumber.equal('5'); - // non-recent - expect(await getAtRecentBlock(this.mock, block - 9)).to.be.bignumber.equal('0'); - }); - }); - }); - for (const length of [160, 224]) { describe(`Trace${length}`, function () { const latest = (self, ...args) => self.methods[`$latest_Checkpoints_Trace${length}(uint256)`](0, ...args);