MerkleTree.test.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
  4. const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic');
  5. const { StandardMerkleTree } = require('@openzeppelin/merkle-tree');
  6. const { generators } = require('../../helpers/random');
  7. const makeTree = (leafs = [ethers.ZeroHash]) =>
  8. StandardMerkleTree.of(
  9. leafs.map(leaf => [leaf]),
  10. ['bytes32'],
  11. { sortLeaves: false },
  12. );
  13. const hashLeaf = leaf => makeTree().leafHash([leaf]);
  14. const DEPTH = 4n; // 16 slots
  15. const ZERO = hashLeaf(ethers.ZeroHash);
  16. async function fixture() {
  17. const mock = await ethers.deployContract('MerkleTreeMock');
  18. await mock.setup(DEPTH, ZERO);
  19. return { mock };
  20. }
  21. describe('MerkleTree', function () {
  22. beforeEach(async function () {
  23. Object.assign(this, await loadFixture(fixture));
  24. });
  25. it('sets initial values at setup', async function () {
  26. const merkleTree = makeTree(Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash));
  27. expect(await this.mock.root()).to.equal(merkleTree.root);
  28. expect(await this.mock.depth()).to.equal(DEPTH);
  29. expect(await this.mock.nextLeafIndex()).to.equal(0n);
  30. });
  31. describe('push', function () {
  32. it('tree is correctly updated', async function () {
  33. const leafs = Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash);
  34. // for each leaf slot
  35. for (const i in leafs) {
  36. // generate random leaf and hash it
  37. const hashedLeaf = hashLeaf((leafs[i] = generators.bytes32()));
  38. // update leaf list and rebuild tree.
  39. const tree = makeTree(leafs);
  40. // push value to tree
  41. await expect(this.mock.push(hashedLeaf)).to.emit(this.mock, 'LeafInserted').withArgs(hashedLeaf, i, tree.root);
  42. // check tree
  43. expect(await this.mock.root()).to.equal(tree.root);
  44. expect(await this.mock.nextLeafIndex()).to.equal(BigInt(i) + 1n);
  45. }
  46. });
  47. it('revert when tree is full', async function () {
  48. await Promise.all(Array.from({ length: 2 ** Number(DEPTH) }).map(() => this.mock.push(ethers.ZeroHash)));
  49. await expect(this.mock.push(ethers.ZeroHash)).to.be.revertedWithPanic(PANIC_CODES.TOO_MUCH_MEMORY_ALLOCATED);
  50. });
  51. });
  52. it('reset', async function () {
  53. // empty tree
  54. const zeroLeafs = Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash);
  55. const zeroTree = makeTree(zeroLeafs);
  56. // tree with one element
  57. const leafs = Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash);
  58. const hashedLeaf = hashLeaf((leafs[0] = generators.bytes32())); // fill first leaf and hash it
  59. const tree = makeTree(leafs);
  60. // root should be that of a zero tree
  61. expect(await this.mock.root()).to.equal(zeroTree.root);
  62. expect(await this.mock.nextLeafIndex()).to.equal(0n);
  63. // push leaf and check root
  64. await expect(this.mock.push(hashedLeaf)).to.emit(this.mock, 'LeafInserted').withArgs(hashedLeaf, 0, tree.root);
  65. expect(await this.mock.root()).to.equal(tree.root);
  66. expect(await this.mock.nextLeafIndex()).to.equal(1n);
  67. // reset tree
  68. await this.mock.setup(DEPTH, ZERO);
  69. expect(await this.mock.root()).to.equal(zeroTree.root);
  70. expect(await this.mock.nextLeafIndex()).to.equal(0n);
  71. // re-push leaf and check root
  72. await expect(this.mock.push(hashedLeaf)).to.emit(this.mock, 'LeafInserted').withArgs(hashedLeaf, 0, tree.root);
  73. expect(await this.mock.root()).to.equal(tree.root);
  74. expect(await this.mock.nextLeafIndex()).to.equal(1n);
  75. });
  76. });