const { ethers } = require('hardhat'); const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic'); const { StandardMerkleTree } = require('@openzeppelin/merkle-tree'); const { generators } = require('../../helpers/random'); const makeTree = (leafs = [ethers.ZeroHash]) => StandardMerkleTree.of( leafs.map(leaf => [leaf]), ['bytes32'], { sortLeaves: false }, ); const hashLeaf = leaf => makeTree().leafHash([leaf]); const DEPTH = 4n; // 16 slots const ZERO = hashLeaf(ethers.ZeroHash); async function fixture() { const mock = await ethers.deployContract('MerkleTreeMock'); await mock.setup(DEPTH, ZERO); return { mock }; } describe('MerkleTree', function () { beforeEach(async function () { Object.assign(this, await loadFixture(fixture)); }); it('sets initial values at setup', async function () { const merkleTree = makeTree(Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash)); expect(await this.mock.root()).to.equal(merkleTree.root); expect(await this.mock.depth()).to.equal(DEPTH); expect(await this.mock.nextLeafIndex()).to.equal(0n); }); describe('push', function () { it('tree is correctly updated', async function () { const leafs = Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash); // for each leaf slot for (const i in leafs) { // generate random leaf and hash it const hashedLeaf = hashLeaf((leafs[i] = generators.bytes32())); // update leaf list and rebuild tree. const tree = makeTree(leafs); // push value to tree await expect(this.mock.push(hashedLeaf)).to.emit(this.mock, 'LeafInserted').withArgs(hashedLeaf, i, tree.root); // check tree expect(await this.mock.root()).to.equal(tree.root); expect(await this.mock.nextLeafIndex()).to.equal(BigInt(i) + 1n); } }); it('revert when tree is full', async function () { await Promise.all(Array.from({ length: 2 ** Number(DEPTH) }).map(() => this.mock.push(ethers.ZeroHash))); await expect(this.mock.push(ethers.ZeroHash)).to.be.revertedWithPanic(PANIC_CODES.TOO_MUCH_MEMORY_ALLOCATED); }); }); it('reset', async function () { // empty tree const zeroLeafs = Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash); const zeroTree = makeTree(zeroLeafs); // tree with one element const leafs = Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash); const hashedLeaf = hashLeaf((leafs[0] = generators.bytes32())); // fill first leaf and hash it const tree = makeTree(leafs); // root should be that of a zero tree expect(await this.mock.root()).to.equal(zeroTree.root); expect(await this.mock.nextLeafIndex()).to.equal(0n); // push leaf and check root await expect(this.mock.push(hashedLeaf)).to.emit(this.mock, 'LeafInserted').withArgs(hashedLeaf, 0, tree.root); expect(await this.mock.root()).to.equal(tree.root); expect(await this.mock.nextLeafIndex()).to.equal(1n); // reset tree await this.mock.setup(DEPTH, ZERO); expect(await this.mock.root()).to.equal(zeroTree.root); expect(await this.mock.nextLeafIndex()).to.equal(0n); // re-push leaf and check root await expect(this.mock.push(hashedLeaf)).to.emit(this.mock, 'LeafInserted').withArgs(hashedLeaf, 0, tree.root); expect(await this.mock.root()).to.equal(tree.root); expect(await this.mock.nextLeafIndex()).to.equal(1n); }); });