Checkpoints.test.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. const { expectRevert, time } = require('@openzeppelin/test-helpers');
  2. const { expect } = require('chai');
  3. const { batchInBlock } = require('../helpers/txpool');
  4. const CheckpointsMock = artifacts.require('CheckpointsMock');
  5. const first = (array) => array.length ? array[0] : undefined;
  6. const last = (array) => array.length ? array[array.length - 1] : undefined;
  7. contract('Checkpoints', function (accounts) {
  8. describe('History checkpoints', function () {
  9. beforeEach(async function () {
  10. this.checkpoint = await CheckpointsMock.new();
  11. });
  12. describe('without checkpoints', function () {
  13. it('returns zero as latest value', async function () {
  14. expect(await this.checkpoint.latest()).to.be.bignumber.equal('0');
  15. });
  16. it('returns zero as past value', async function () {
  17. await time.advanceBlock();
  18. expect(await this.checkpoint.getAtBlock(await web3.eth.getBlockNumber() - 1))
  19. .to.be.bignumber.equal('0');
  20. expect(await this.checkpoint.getAtProbablyRecentBlock(await web3.eth.getBlockNumber() - 1))
  21. .to.be.bignumber.equal('0');
  22. });
  23. });
  24. describe('with checkpoints', function () {
  25. beforeEach('pushing checkpoints', async function () {
  26. this.tx1 = await this.checkpoint.push(1);
  27. this.tx2 = await this.checkpoint.push(2);
  28. await time.advanceBlock();
  29. this.tx3 = await this.checkpoint.push(3);
  30. await time.advanceBlock();
  31. await time.advanceBlock();
  32. });
  33. it('returns latest value', async function () {
  34. expect(await this.checkpoint.latest()).to.be.bignumber.equal('3');
  35. });
  36. for (const fn of [ 'getAtBlock(uint256)', 'getAtProbablyRecentBlock(uint256)' ]) {
  37. describe(`lookup: ${fn}`, function () {
  38. it('returns past values', async function () {
  39. expect(await this.checkpoint.methods[fn](this.tx1.receipt.blockNumber - 1)).to.be.bignumber.equal('0');
  40. expect(await this.checkpoint.methods[fn](this.tx1.receipt.blockNumber)).to.be.bignumber.equal('1');
  41. expect(await this.checkpoint.methods[fn](this.tx2.receipt.blockNumber)).to.be.bignumber.equal('2');
  42. // Block with no new checkpoints
  43. expect(await this.checkpoint.methods[fn](this.tx2.receipt.blockNumber + 1)).to.be.bignumber.equal('2');
  44. expect(await this.checkpoint.methods[fn](this.tx3.receipt.blockNumber)).to.be.bignumber.equal('3');
  45. expect(await this.checkpoint.methods[fn](this.tx3.receipt.blockNumber + 1)).to.be.bignumber.equal('3');
  46. });
  47. it('reverts if block number >= current block', async function () {
  48. await expectRevert(
  49. this.checkpoint.methods[fn](await web3.eth.getBlockNumber()),
  50. 'Checkpoints: block not yet mined',
  51. );
  52. await expectRevert(
  53. this.checkpoint.methods[fn](await web3.eth.getBlockNumber() + 1),
  54. 'Checkpoints: block not yet mined',
  55. );
  56. });
  57. });
  58. }
  59. it('multiple checkpoints in the same block', async function () {
  60. const lengthBefore = await this.checkpoint.length();
  61. await batchInBlock([
  62. () => this.checkpoint.push(8, { gas: 100000 }),
  63. () => this.checkpoint.push(9, { gas: 100000 }),
  64. () => this.checkpoint.push(10, { gas: 100000 }),
  65. ]);
  66. expect(await this.checkpoint.length()).to.be.bignumber.equal(lengthBefore.addn(1));
  67. expect(await this.checkpoint.latest()).to.be.bignumber.equal('10');
  68. });
  69. it('more than 5 checkpoints', async function () {
  70. for (let i = 4; i <= 6; i++) {
  71. await this.checkpoint.push(i);
  72. }
  73. expect(await this.checkpoint.length()).to.be.bignumber.equal('6');
  74. const block = await web3.eth.getBlockNumber();
  75. // recent
  76. expect(await this.checkpoint.getAtProbablyRecentBlock(block - 1)).to.be.bignumber.equal('5');
  77. // non-recent
  78. expect(await this.checkpoint.getAtProbablyRecentBlock(block - 9)).to.be.bignumber.equal('0');
  79. });
  80. });
  81. });
  82. for (const length of [160, 224]) {
  83. describe(`Trace${length}`, function () {
  84. beforeEach(async function () {
  85. this.contract = await artifacts.require(`Checkpoints${length}Mock`).new();
  86. });
  87. describe('without checkpoints', function () {
  88. it('returns zero as latest value', async function () {
  89. expect(await this.contract.latest()).to.be.bignumber.equal('0');
  90. });
  91. it('lookup returns 0', async function () {
  92. expect(await this.contract.lowerLookup(0)).to.be.bignumber.equal('0');
  93. expect(await this.contract.upperLookup(0)).to.be.bignumber.equal('0');
  94. });
  95. });
  96. describe('with checkpoints', function () {
  97. beforeEach('pushing checkpoints', async function () {
  98. this.checkpoints = [
  99. { key: 2, value: '17' },
  100. { key: 3, value: '42' },
  101. { key: 5, value: '101' },
  102. { key: 7, value: '23' },
  103. { key: 11, value: '99' },
  104. ];
  105. for (const { key, value } of this.checkpoints) {
  106. await this.contract.push(key, value);
  107. }
  108. });
  109. it('returns latest value', async function () {
  110. expect(await this.contract.latest())
  111. .to.be.bignumber.equal(last(this.checkpoints).value);
  112. });
  113. it('cannot push values in the past', async function () {
  114. await expectRevert(this.contract.push(last(this.checkpoints).key - 1, '0'), 'Checkpoint: invalid key');
  115. });
  116. it('can update last value', async function () {
  117. const newValue = '42';
  118. // check length before the update
  119. expect(await this.contract.length()).to.be.bignumber.equal(this.checkpoints.length.toString());
  120. // update last key
  121. await this.contract.push(last(this.checkpoints).key, newValue);
  122. expect(await this.contract.latest()).to.be.bignumber.equal(newValue);
  123. // check that length did not change
  124. expect(await this.contract.length()).to.be.bignumber.equal(this.checkpoints.length.toString());
  125. });
  126. it('lower lookup', async function () {
  127. for (let i = 0; i < 14; ++i) {
  128. const value = first(this.checkpoints.filter(x => i <= x.key))?.value || '0';
  129. expect(await this.contract.lowerLookup(i)).to.be.bignumber.equal(value);
  130. }
  131. });
  132. it('upper lookup', async function () {
  133. for (let i = 0; i < 14; ++i) {
  134. const value = last(this.checkpoints.filter(x => i >= x.key))?.value || '0';
  135. expect(await this.contract.upperLookup(i)).to.be.bignumber.equal(value);
  136. }
  137. });
  138. });
  139. });
  140. }
  141. });