Checkpoints.test.js 9.9 KB

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