Checkpoints.test.js 8.8 KB

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