const { ethers } = require('hardhat'); const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); const { VALUE_SIZES } = require('../../../scripts/generate/templates/Checkpoints.opts'); describe('Checkpoints', function () { for (const length of VALUE_SIZES) { describe(`Trace${length}`, function () { const fixture = async () => { const mock = await ethers.deployContract('$Checkpoints'); const methods = { at: (...args) => mock.getFunction(`$at_Checkpoints_Trace${length}`)(0, ...args), latest: (...args) => mock.getFunction(`$latest_Checkpoints_Trace${length}`)(0, ...args), latestCheckpoint: (...args) => mock.getFunction(`$latestCheckpoint_Checkpoints_Trace${length}`)(0, ...args), length: (...args) => mock.getFunction(`$length_Checkpoints_Trace${length}`)(0, ...args), push: (...args) => mock.getFunction(`$push(uint256,uint${256 - length},uint${length})`)(0, ...args), lowerLookup: (...args) => mock.getFunction(`$lowerLookup(uint256,uint${256 - length})`)(0, ...args), upperLookup: (...args) => mock.getFunction(`$upperLookup(uint256,uint${256 - length})`)(0, ...args), upperLookupRecent: (...args) => mock.getFunction(`$upperLookupRecent(uint256,uint${256 - length})`)(0, ...args), }; return { mock, methods }; }; beforeEach(async function () { Object.assign(this, await loadFixture(fixture)); }); describe('without checkpoints', function () { it('at zero reverts', async function () { // Reverts with array out of bound access, which is unspecified await expect(this.methods.at(0)).to.be.reverted; }); it('returns zero as latest value', async function () { expect(await this.methods.latest()).to.equal(0n); const ckpt = await this.methods.latestCheckpoint(); expect(ckpt[0]).to.be.false; expect(ckpt[1]).to.equal(0n); expect(ckpt[2]).to.equal(0n); }); it('lookup returns 0', async function () { expect(await this.methods.lowerLookup(0)).to.equal(0n); expect(await this.methods.upperLookup(0)).to.equal(0n); expect(await this.methods.upperLookupRecent(0)).to.equal(0n); }); }); describe('with checkpoints', function () { beforeEach('pushing checkpoints', async function () { this.checkpoints = [ { key: 2n, value: 17n }, { key: 3n, value: 42n }, { key: 5n, value: 101n }, { key: 7n, value: 23n }, { key: 11n, value: 99n }, ]; for (const { key, value } of this.checkpoints) { await this.methods.push(key, value); } }); it('at keys', async function () { for (const [index, { key, value }] of this.checkpoints.entries()) { const at = await this.methods.at(index); expect(at._value).to.equal(value); expect(at._key).to.equal(key); } }); it('length', async function () { expect(await this.methods.length()).to.equal(this.checkpoints.length); }); it('returns latest value', async function () { const latest = this.checkpoints.at(-1); expect(await this.methods.latest()).to.equal(latest.value); expect(await this.methods.latestCheckpoint()).to.deep.equal([true, latest.key, latest.value]); }); it('cannot push values in the past', async function () { await expect(this.methods.push(this.checkpoints.at(-1).key - 1n, 0n)).to.be.revertedWithCustomError( this.mock, 'CheckpointUnorderedInsertion', ); }); it('can update last value', async function () { const newValue = 42n; // check length before the update expect(await this.methods.length()).to.equal(this.checkpoints.length); // update last key await this.methods.push(this.checkpoints.at(-1).key, newValue); expect(await this.methods.latest()).to.equal(newValue); // check that length did not change expect(await this.methods.length()).to.equal(this.checkpoints.length); }); it('lower lookup', async function () { for (let i = 0; i < 14; ++i) { const value = this.checkpoints.find(x => i <= x.key)?.value || 0n; expect(await this.methods.lowerLookup(i)).to.equal(value); } }); it('upper lookup & upperLookupRecent', async function () { for (let i = 0; i < 14; ++i) { const value = this.checkpoints.findLast(x => i >= x.key)?.value || 0n; expect(await this.methods.upperLookup(i)).to.equal(value); expect(await this.methods.upperLookupRecent(i)).to.equal(value); } }); it('upperLookupRecent with more than 5 checkpoints', async function () { const moreCheckpoints = [ { key: 12n, value: 22n }, { key: 13n, value: 131n }, { key: 17n, value: 45n }, { key: 19n, value: 31452n }, { key: 21n, value: 0n }, ]; const allCheckpoints = [].concat(this.checkpoints, moreCheckpoints); for (const { key, value } of moreCheckpoints) { await this.methods.push(key, value); } for (let i = 0; i < 25; ++i) { const value = allCheckpoints.findLast(x => i >= x.key)?.value || 0n; expect(await this.methods.upperLookup(i)).to.equal(value); expect(await this.methods.upperLookupRecent(i)).to.equal(value); } }); }); }); } });