Checkpoints.test.js 6.47 KB
Newer Older
vicotor's avatar
vicotor committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
require('@openzeppelin/test-helpers');

const { expect } = require('chai');

const { VALUE_SIZES } = require('../../../scripts/generate/templates/Checkpoints.opts.js');
const { expectRevertCustomError } = require('../../helpers/customError.js');
const { expectRevert } = require('@openzeppelin/test-helpers');

const $Checkpoints = artifacts.require('$Checkpoints');

// The library name may be 'Checkpoints' or 'CheckpointsUpgradeable'
const libraryName = $Checkpoints._json.contractName.replace(/^\$/, '');

const first = array => (array.length ? array[0] : undefined);
const last = array => (array.length ? array[array.length - 1] : undefined);

contract('Checkpoints', function () {
  beforeEach(async function () {
    this.mock = await $Checkpoints.new();
  });

  for (const length of VALUE_SIZES) {
    describe(`Trace${length}`, function () {
      beforeEach(async function () {
        this.methods = {
          at: (...args) => this.mock.methods[`$at_${libraryName}_Trace${length}(uint256,uint32)`](0, ...args),
          latest: (...args) => this.mock.methods[`$latest_${libraryName}_Trace${length}(uint256)`](0, ...args),
          latestCheckpoint: (...args) =>
            this.mock.methods[`$latestCheckpoint_${libraryName}_Trace${length}(uint256)`](0, ...args),
          length: (...args) => this.mock.methods[`$length_${libraryName}_Trace${length}(uint256)`](0, ...args),
          push: (...args) => this.mock.methods[`$push(uint256,uint${256 - length},uint${length})`](0, ...args),
          lowerLookup: (...args) => this.mock.methods[`$lowerLookup(uint256,uint${256 - length})`](0, ...args),
          upperLookup: (...args) => this.mock.methods[`$upperLookup(uint256,uint${256 - length})`](0, ...args),
          upperLookupRecent: (...args) =>
            this.mock.methods[`$upperLookupRecent(uint256,uint${256 - length})`](0, ...args),
        };
      });

      describe('without checkpoints', function () {
        it('at zero reverts', async function () {
          // Reverts with array out of bound access, which is unspecified
          await expectRevert.unspecified(this.methods.at(0));
        });

        it('returns zero as latest value', async function () {
          expect(await this.methods.latest()).to.be.bignumber.equal('0');

          const ckpt = await this.methods.latestCheckpoint();
          expect(ckpt[0]).to.be.equal(false);
          expect(ckpt[1]).to.be.bignumber.equal('0');
          expect(ckpt[2]).to.be.bignumber.equal('0');
        });

        it('lookup returns 0', async function () {
          expect(await this.methods.lowerLookup(0)).to.be.bignumber.equal('0');
          expect(await this.methods.upperLookup(0)).to.be.bignumber.equal('0');
          expect(await this.methods.upperLookupRecent(0)).to.be.bignumber.equal('0');
        });
      });

      describe('with checkpoints', function () {
        beforeEach('pushing checkpoints', async function () {
          this.checkpoints = [
            { key: '2', value: '17' },
            { key: '3', value: '42' },
            { key: '5', value: '101' },
            { key: '7', value: '23' },
            { key: '11', value: '99' },
          ];
          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.be.bignumber.equal(value);
            expect(at._key).to.be.bignumber.equal(key);
          }
        });

        it('length', async function () {
          expect(await this.methods.length()).to.be.bignumber.equal(this.checkpoints.length.toString());
        });

        it('returns latest value', async function () {
          expect(await this.methods.latest()).to.be.bignumber.equal(last(this.checkpoints).value);

          const ckpt = await this.methods.latestCheckpoint();
          expect(ckpt[0]).to.be.equal(true);
          expect(ckpt[1]).to.be.bignumber.equal(last(this.checkpoints).key);
          expect(ckpt[2]).to.be.bignumber.equal(last(this.checkpoints).value);
        });

        it('cannot push values in the past', async function () {
          await expectRevertCustomError(
            this.methods.push(last(this.checkpoints).key - 1, '0'),
            'CheckpointUnorderedInsertion',
            [],
          );
        });

        it('can update last value', async function () {
          const newValue = '42';

          // check length before the update
          expect(await this.methods.length()).to.be.bignumber.equal(this.checkpoints.length.toString());

          // update last key
          await this.methods.push(last(this.checkpoints).key, newValue);
          expect(await this.methods.latest()).to.be.bignumber.equal(newValue);

          // check that length did not change
          expect(await this.methods.length()).to.be.bignumber.equal(this.checkpoints.length.toString());
        });

        it('lower lookup', async function () {
          for (let i = 0; i < 14; ++i) {
            const value = first(this.checkpoints.filter(x => i <= x.key))?.value || '0';

            expect(await this.methods.lowerLookup(i)).to.be.bignumber.equal(value);
          }
        });

        it('upper lookup & upperLookupRecent', async function () {
          for (let i = 0; i < 14; ++i) {
            const value = last(this.checkpoints.filter(x => i >= x.key))?.value || '0';

            expect(await this.methods.upperLookup(i)).to.be.bignumber.equal(value);
            expect(await this.methods.upperLookupRecent(i)).to.be.bignumber.equal(value);
          }
        });

        it('upperLookupRecent with more than 5 checkpoints', async function () {
          const moreCheckpoints = [
            { key: '12', value: '22' },
            { key: '13', value: '131' },
            { key: '17', value: '45' },
            { key: '19', value: '31452' },
            { key: '21', value: '0' },
          ];
          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 = last(allCheckpoints.filter(x => i >= x.key))?.value || '0';
            expect(await this.methods.upperLookup(i)).to.be.bignumber.equal(value);
            expect(await this.methods.upperLookupRecent(i)).to.be.bignumber.equal(value);
          }
        });
      });
    });
  }
});