Checkpoints.test.js 7.6 KB

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