mirror of
https://github.com/privacy-scaling-explorations/zk-kit.git
synced 2026-04-22 03:00:15 -04:00
feat: add incremental-merkle-tree.sol pkg
This commit is contained in:
2
packages/incremental-merkle-tree.sol/.env.example
Normal file
2
packages/incremental-merkle-tree.sol/.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
COINMARKETCAP_API_KEY=
|
||||
REPORT_GAS=false
|
||||
21
packages/incremental-merkle-tree.sol/.solhint.json
Normal file
21
packages/incremental-merkle-tree.sol/.solhint.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "solhint:recommended",
|
||||
"plugins": ["prettier"],
|
||||
"rules": {
|
||||
"code-complexity": ["error", 7],
|
||||
"compiler-version": ["error", ">=0.8.0"],
|
||||
"const-name-snakecase": "off",
|
||||
"no-empty-blocks": "off",
|
||||
"constructor-syntax": "error",
|
||||
"func-visibility": ["error", { "ignoreConstructors": true }],
|
||||
"max-line-length": ["error", 120],
|
||||
"not-rely-on-time": "off",
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
],
|
||||
"reason-string": ["warn", { "maxLength": 64 }]
|
||||
}
|
||||
}
|
||||
1
packages/incremental-merkle-tree.sol/.solhintignore
Normal file
1
packages/incremental-merkle-tree.sol/.solhintignore
Normal file
@@ -0,0 +1 @@
|
||||
contracts/Verifier.sol
|
||||
68
packages/incremental-merkle-tree.sol/README.md
Normal file
68
packages/incremental-merkle-tree.sol/README.md
Normal file
@@ -0,0 +1,68 @@
|
||||
<p align="center">
|
||||
<h1 align="center">
|
||||
Incremental Merkle Trees (Solidity)
|
||||
</h1>
|
||||
<p align="center">Incremental Merkle tree smart contracts.</p>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/appliedzkp/zk-kit">
|
||||
<img src="https://img.shields.io/badge/project-zk--kit-blue.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/appliedzkp/zk-kit/blob/main/LICENSE">
|
||||
<img alt="Github license" src="https://img.shields.io/github/license/appliedzkp/zk-kit.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/@zk-kit/incremental-merkle-tree.sol">
|
||||
<img alt="NPM version" src="https://img.shields.io/npm/v/@zk-kit/incremental-merkle-tree.sol?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://npmjs.org/package/@zk-kit/incremental-merkle-tree.sol">
|
||||
<img alt="Downloads" src="https://img.shields.io/npm/dm/@zk-kit/incremental-merkle-tree.sol.svg?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://bundlephobia.com/package/@zk-kit/incremental-merkle-tree.sol">
|
||||
<img alt="npm bundle size (scoped)" src="https://img.shields.io/bundlephobia/minzip/@zk-kit/incremental-merkle-tree.sol" />
|
||||
</a>
|
||||
<a href="https://eslint.org/">
|
||||
<img alt="Linter eslint" src="https://img.shields.io/badge/linter-eslint-8080f2?style=flat-square&logo=eslint" />
|
||||
</a>
|
||||
<a href="https://prettier.io/">
|
||||
<img alt="Code style prettier" src="https://img.shields.io/badge/code%20style-prettier-f8bc45?style=flat-square&logo=prettier" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<h4>
|
||||
<a href="https://discord.gg/9B9WgGP6YM">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 🛠 Install
|
||||
|
||||
### npm or yarn
|
||||
|
||||
Install the `@zk-kit/incremental-merkle-tree.sol` package with npm:
|
||||
|
||||
```bash
|
||||
npm i @zk-kit/incremental-merkle-tree.sol --save
|
||||
```
|
||||
|
||||
or yarn:
|
||||
|
||||
```bash
|
||||
yarn add @zk-kit/incremental-merkle-tree.sol
|
||||
```
|
||||
|
||||
## 📜 Usage
|
||||
|
||||
|
||||
|
||||
## Contacts
|
||||
|
||||
### Developers
|
||||
|
||||
- e-mail : me@cedoor.dev
|
||||
- github : [@cedoor](https://github.com/cedoor)
|
||||
- website : https://cedoor.dev
|
||||
10
packages/incremental-merkle-tree.sol/contracts/Hashes.sol
Normal file
10
packages/incremental-merkle-tree.sol/contracts/Hashes.sol
Normal file
@@ -0,0 +1,10 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.4;
|
||||
|
||||
library PoseidonT3 {
|
||||
function poseidon(uint256[2] memory) public pure returns (uint256) {}
|
||||
}
|
||||
|
||||
library PoseidonT6 {
|
||||
function poseidon(uint256[5] memory) public pure returns (uint256) {}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.4;
|
||||
|
||||
import {PoseidonT3} from "./Hashes.sol";
|
||||
|
||||
// Each incremental tree has certain properties and data that will
|
||||
// be used to add new leaves.
|
||||
struct IncrementalTreeData {
|
||||
uint8 depth; // Depth of the tree (levels - 1).
|
||||
uint256 root; // Root hash of the tree.
|
||||
uint256 numberOfLeaves; // Number of leaves of the tree.
|
||||
mapping(uint256 => uint256) zeroes; // Zero hashes used for empty nodes (level -> zero hash).
|
||||
// The nodes of the subtrees used in the last addition of a leaf (level -> [left node, right node]).
|
||||
mapping(uint256 => uint256[2]) lastSubtrees; // Caching these values is essential to efficient appends.
|
||||
}
|
||||
|
||||
/// @title Incremental binary Merkle tree.
|
||||
/// @dev The incremental tree allows to calculate the root hash each time a leaf is added, ensuring
|
||||
/// the integrity of the tree.
|
||||
library IncrementalBinaryTree {
|
||||
uint8 internal constant MAX_DEPTH = 32;
|
||||
uint256 internal constant SNARK_SCALAR_FIELD =
|
||||
21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||
|
||||
/// @dev Initializes a tree.
|
||||
/// @param self: Tree data.
|
||||
/// @param depth: Depth of the tree.
|
||||
/// @param zero: Zero value to be used.
|
||||
function init(
|
||||
IncrementalTreeData storage self,
|
||||
uint8 depth,
|
||||
uint256 zero
|
||||
) public {
|
||||
require(
|
||||
depth > 0 && depth <= MAX_DEPTH,
|
||||
"IncrementalBinaryTree: tree depth must be between 1 and 32"
|
||||
);
|
||||
|
||||
self.depth = depth;
|
||||
|
||||
for (uint8 i = 0; i < depth; i++) {
|
||||
self.zeroes[i] = zero;
|
||||
zero = PoseidonT3.poseidon([zero, zero]);
|
||||
}
|
||||
|
||||
self.root = zero;
|
||||
}
|
||||
|
||||
/// @dev Inserts a leaf in the tree.
|
||||
/// @param self: Tree data.
|
||||
/// @param leaf: Leaf to be inserted.
|
||||
function insert(IncrementalTreeData storage self, uint256 leaf) public {
|
||||
require(
|
||||
leaf < SNARK_SCALAR_FIELD,
|
||||
"IncrementalBinaryTree: leaf must be < SNARK_SCALAR_FIELD"
|
||||
);
|
||||
require(
|
||||
self.numberOfLeaves < 2**self.depth,
|
||||
"IncrementalBinaryTree: tree is full"
|
||||
);
|
||||
|
||||
uint256 index = self.numberOfLeaves;
|
||||
uint256 hash = leaf;
|
||||
|
||||
for (uint8 i = 0; i < self.depth; i++) {
|
||||
if (index % 2 == 0) {
|
||||
self.lastSubtrees[i] = [hash, self.zeroes[i]];
|
||||
} else {
|
||||
self.lastSubtrees[i][1] = hash;
|
||||
}
|
||||
|
||||
hash = PoseidonT3.poseidon(self.lastSubtrees[i]);
|
||||
index /= 2;
|
||||
}
|
||||
|
||||
self.root = hash;
|
||||
self.numberOfLeaves += 1;
|
||||
}
|
||||
|
||||
/// @dev Removes a leaf from the tree.
|
||||
/// @param self: Tree data.
|
||||
/// @param leaf: Leaf to be removed.
|
||||
/// @param proofSiblingNodes: Array of the sibling nodes of the proof of membership.
|
||||
/// @param proofPath: Path of the proof of membership.
|
||||
function remove(
|
||||
IncrementalTreeData storage self,
|
||||
uint256 leaf,
|
||||
uint256[] memory proofSiblingNodes,
|
||||
uint8[] memory proofPath
|
||||
) public {
|
||||
require(
|
||||
verify(self, leaf, proofSiblingNodes, proofPath),
|
||||
"IncrementalBinaryTree: leaf is not part of the tree"
|
||||
);
|
||||
|
||||
uint256 hash = self.zeroes[0];
|
||||
|
||||
for (uint8 i = 0; i < self.depth; i++) {
|
||||
if (proofPath[i] == 0) {
|
||||
if (proofSiblingNodes[i] == self.lastSubtrees[i][1]) {
|
||||
self.lastSubtrees[i][0] = hash;
|
||||
}
|
||||
|
||||
hash = PoseidonT3.poseidon([hash, proofSiblingNodes[i]]);
|
||||
} else {
|
||||
if (proofSiblingNodes[i] == self.lastSubtrees[i][0]) {
|
||||
self.lastSubtrees[i][1] = hash;
|
||||
}
|
||||
|
||||
hash = PoseidonT3.poseidon([proofSiblingNodes[i], hash]);
|
||||
}
|
||||
}
|
||||
|
||||
self.root = hash;
|
||||
}
|
||||
|
||||
/// @dev Verify if the path is correct and the leaf is part of the tree.
|
||||
/// @param self: Tree data.
|
||||
/// @param leaf: Leaf to be removed.
|
||||
/// @param proofSiblingNodes: Array of the sibling nodes of the proof of membership.
|
||||
/// @param proofPath: Path of the proof of membership.
|
||||
/// @return True or false.
|
||||
function verify(
|
||||
IncrementalTreeData storage self,
|
||||
uint256 leaf,
|
||||
uint256[] memory proofSiblingNodes,
|
||||
uint8[] memory proofPath
|
||||
) private view returns (bool) {
|
||||
require(
|
||||
leaf < SNARK_SCALAR_FIELD,
|
||||
"IncrementalBinaryTree: leaf must be < SNARK_SCALAR_FIELD"
|
||||
);
|
||||
require(
|
||||
proofPath.length == self.depth &&
|
||||
proofSiblingNodes.length == self.depth,
|
||||
"IncrementalBinaryTree: length of path is not correct"
|
||||
);
|
||||
|
||||
uint256 hash = leaf;
|
||||
|
||||
for (uint8 i = 0; i < self.depth; i++) {
|
||||
require(
|
||||
proofSiblingNodes[i] < SNARK_SCALAR_FIELD,
|
||||
"IncrementalBinaryTree: sibling node must be < SNARK_SCALAR_FIELD"
|
||||
);
|
||||
|
||||
if (proofPath[i] == 0) {
|
||||
hash = PoseidonT3.poseidon([hash, proofSiblingNodes[i]]);
|
||||
} else {
|
||||
hash = PoseidonT3.poseidon([proofSiblingNodes[i], hash]);
|
||||
}
|
||||
}
|
||||
|
||||
return hash == self.root;
|
||||
}
|
||||
}
|
||||
43
packages/incremental-merkle-tree.sol/contracts/Test.sol
Normal file
43
packages/incremental-merkle-tree.sol/contracts/Test.sol
Normal file
@@ -0,0 +1,43 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.4;
|
||||
|
||||
import "./IncrementalBinaryTree.sol";
|
||||
|
||||
contract Test {
|
||||
using IncrementalBinaryTree for IncrementalTreeData;
|
||||
|
||||
event TreeCreated(bytes32 id, uint8 depth);
|
||||
event LeafInserted(bytes32 indexed treeId, uint256 leaf, uint256 root);
|
||||
event LeafRemoved(bytes32 indexed treeId, uint256 leaf, uint256 root);
|
||||
|
||||
mapping(bytes32 => IncrementalTreeData) public trees;
|
||||
|
||||
function createTree(bytes32 _id, uint8 _depth) external {
|
||||
require(trees[_id].depth == 0, "Test: tree already exists");
|
||||
|
||||
trees[_id].init(_depth, 0);
|
||||
|
||||
emit TreeCreated(_id, _depth);
|
||||
}
|
||||
|
||||
function insertLeaf(bytes32 _treeId, uint256 _leaf) external {
|
||||
require(trees[_treeId].depth != 0, "Test: tree does not exist");
|
||||
|
||||
trees[_treeId].insert(_leaf);
|
||||
|
||||
emit LeafInserted(_treeId, _leaf, trees[_treeId].root);
|
||||
}
|
||||
|
||||
function removeLeaf(
|
||||
bytes32 _treeId,
|
||||
uint256 _leaf,
|
||||
uint256[] memory _proofSiblings,
|
||||
uint8[] memory _proofPathIndices
|
||||
) external {
|
||||
require(trees[_treeId].depth != 0, "Test: tree does not exist");
|
||||
|
||||
trees[_treeId].remove(_leaf, _proofSiblings, _proofPathIndices);
|
||||
|
||||
emit LeafRemoved(_treeId, _leaf, trees[_treeId].root);
|
||||
}
|
||||
}
|
||||
28
packages/incremental-merkle-tree.sol/hardhat.config.ts
Normal file
28
packages/incremental-merkle-tree.sol/hardhat.config.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import "@nomiclabs/hardhat-ethers"
|
||||
import "@nomiclabs/hardhat-waffle"
|
||||
import { config as dotenvConfig } from "dotenv"
|
||||
import "hardhat-gas-reporter"
|
||||
import { HardhatUserConfig } from "hardhat/config"
|
||||
import { resolve } from "path"
|
||||
import "solidity-coverage"
|
||||
import { config } from "./package.json"
|
||||
import "./tasks/deploy-test"
|
||||
|
||||
dotenvConfig({ path: resolve(__dirname, "./.env") })
|
||||
|
||||
const hardhatConfig: HardhatUserConfig = {
|
||||
solidity: config.solidity,
|
||||
paths: {
|
||||
sources: config.paths.contracts,
|
||||
tests: config.paths.tests,
|
||||
cache: config.paths.cache,
|
||||
artifacts: config.paths.build.contracts
|
||||
},
|
||||
gasReporter: {
|
||||
currency: "USD",
|
||||
enabled: process.env.REPORT_GAS === "true",
|
||||
coinmarketcap: process.env.COINMARKETCAP_API_KEY
|
||||
}
|
||||
}
|
||||
|
||||
export default hardhatConfig
|
||||
74
packages/incremental-merkle-tree.sol/package.json
Normal file
74
packages/incremental-merkle-tree.sol/package.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"name": "@zk-kit/incremental-merkle-tree.sol",
|
||||
"version": "0.1.0",
|
||||
"description": "Incremental Merkle tree smart contracts.",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"contracts/",
|
||||
"!contracts/Test.sol",
|
||||
"README.md"
|
||||
],
|
||||
"keywords": [
|
||||
"blockchain",
|
||||
"ethereum",
|
||||
"hardhat",
|
||||
"smart-contracts",
|
||||
"solidity",
|
||||
"libraries",
|
||||
"merkle-tree",
|
||||
"incremental-merkle-tree"
|
||||
],
|
||||
"repository": "git@github.com:appliedzkp/zk-kit.git",
|
||||
"homepage": "https://github.com/appliedzkp/zk-kit/tree/main/packages/incremental-merkle-tree.sol",
|
||||
"author": {
|
||||
"name": "Omar Desogus",
|
||||
"email": "me@cedoor.dev",
|
||||
"url": "https://cedoor.dev"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "hardhat node",
|
||||
"compile": "hardhat compile",
|
||||
"deploy:test": "hardhat deploy:test",
|
||||
"test": "hardhat test",
|
||||
"test:report-gas": "REPORT_GAS=true hardhat test",
|
||||
"test:coverage": "hardhat coverage",
|
||||
"test:prod": "yarn lint && yarn coverage",
|
||||
"lint": "solhint 'contracts/**/*.sol'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nomiclabs/hardhat-ethers": "^2.0.4",
|
||||
"@nomiclabs/hardhat-waffle": "^2.0.2",
|
||||
"@types/chai": "^4.3.0",
|
||||
"@types/mocha": "^9.1.0",
|
||||
"@zk-kit/incremental-merkle-tree": "^0.4.0",
|
||||
"chai": "^4.3.5",
|
||||
"circomlibjs": "^0.0.8",
|
||||
"dotenv": "^14.3.2",
|
||||
"ethereum-waffle": "^3.4.0",
|
||||
"ethers": "^5.5.3",
|
||||
"hardhat": "^2.8.3",
|
||||
"hardhat-gas-reporter": "^1.0.7",
|
||||
"mocha": "^8.4.0",
|
||||
"prettier": "^2.5.1",
|
||||
"prettier-plugin-solidity": "^1.0.0-beta.19",
|
||||
"solhint": "^3.3.6",
|
||||
"solhint-plugin-prettier": "^0.0.5",
|
||||
"solidity-coverage": "^0.7.18"
|
||||
},
|
||||
"config": {
|
||||
"solidity": {
|
||||
"version": "0.8.4"
|
||||
},
|
||||
"paths": {
|
||||
"contracts": "./contracts",
|
||||
"circuit": "./circuit",
|
||||
"tests": "./test",
|
||||
"cache": "./cache",
|
||||
"build": {
|
||||
"snark": "./build/snark",
|
||||
"contracts": "./build/contracts",
|
||||
"typechain": "./build/typechain"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
packages/incremental-merkle-tree.sol/tasks/deploy-test.ts
Normal file
62
packages/incremental-merkle-tree.sol/tasks/deploy-test.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { poseidon_gencontract as poseidonContract } from "circomlibjs"
|
||||
import { Contract } from "ethers"
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("deploy:test", "Deploy a Test contract")
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ logs }, { ethers }): Promise<Contract> => {
|
||||
const poseidonT3ABI = poseidonContract.generateABI(2)
|
||||
const poseidonT3Bytecode = poseidonContract.createCode(2)
|
||||
|
||||
const [signer] = await ethers.getSigners()
|
||||
|
||||
const PoseidonLibT3Factory = new ethers.ContractFactory(
|
||||
poseidonT3ABI,
|
||||
poseidonT3Bytecode,
|
||||
signer
|
||||
)
|
||||
const poseidonT3Lib = await PoseidonLibT3Factory.deploy()
|
||||
|
||||
await poseidonT3Lib.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(
|
||||
`PoseidonT3 library has been deployed to: ${poseidonT3Lib.address}`
|
||||
)
|
||||
}
|
||||
|
||||
const IncrementalBinaryTreeLibFactory = await ethers.getContractFactory(
|
||||
"IncrementalBinaryTree",
|
||||
{
|
||||
libraries: {
|
||||
PoseidonT3: poseidonT3Lib.address
|
||||
}
|
||||
}
|
||||
)
|
||||
const incrementalBinaryTreeLib =
|
||||
await IncrementalBinaryTreeLibFactory.deploy()
|
||||
|
||||
await incrementalBinaryTreeLib.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(
|
||||
`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTreeLib.address}`
|
||||
)
|
||||
}
|
||||
|
||||
const ContractFactory = await ethers.getContractFactory("Test", {
|
||||
libraries: {
|
||||
IncrementalBinaryTree: incrementalBinaryTreeLib.address
|
||||
}
|
||||
})
|
||||
|
||||
const contract = await ContractFactory.deploy()
|
||||
|
||||
await contract.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Test contract has been deployed to: ${contract.address}`)
|
||||
}
|
||||
|
||||
return contract
|
||||
})
|
||||
214
packages/incremental-merkle-tree.sol/test/Test.ts
Normal file
214
packages/incremental-merkle-tree.sol/test/Test.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
import { expect } from "chai"
|
||||
import { Contract } from "ethers"
|
||||
import { ethers, run } from "hardhat"
|
||||
import { createTree } from "./utils"
|
||||
|
||||
/* eslint-disable jest/valid-expect */
|
||||
describe("Test", () => {
|
||||
let contract: Contract
|
||||
|
||||
const treeId = ethers.utils.formatBytes32String("treeId")
|
||||
const leaf = BigInt(1)
|
||||
const depth = 16
|
||||
|
||||
before(async () => {
|
||||
contract = await run("deploy:test", { logs: false })
|
||||
})
|
||||
|
||||
it("Should not create a tree with a depth > 32", async () => {
|
||||
const transaction = contract.createTree(treeId, 33)
|
||||
|
||||
await expect(transaction).to.be.revertedWith(
|
||||
"IncrementalBinaryTree: tree depth must be between 1 and 32"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should create a tree", async () => {
|
||||
const transaction = contract.createTree(treeId, depth)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(contract, "TreeCreated")
|
||||
.withArgs(treeId, depth)
|
||||
})
|
||||
|
||||
it("Should not create a tree with an existing id", async () => {
|
||||
const transaction = contract.createTree(treeId, depth)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Test: tree already exists")
|
||||
})
|
||||
|
||||
it("Should not insert a leaf if the tree does not exist", async () => {
|
||||
const treeId = ethers.utils.formatBytes32String("treeId2")
|
||||
|
||||
const transaction = contract.insertLeaf(treeId, leaf)
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Test: tree does not exist")
|
||||
})
|
||||
|
||||
it("Should not insert a leaf if its value is > SNARK_SCALAR_FIELD", async () => {
|
||||
const leaf = BigInt(
|
||||
"21888242871839275222246405745257275088548364400416034343698204186575808495618"
|
||||
)
|
||||
|
||||
const transaction = contract.insertLeaf(treeId, leaf)
|
||||
|
||||
await expect(transaction).to.be.revertedWith(
|
||||
"IncrementalBinaryTree: leaf must be < SNARK_SCALAR_FIELD"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should insert a leaf in a tree", async () => {
|
||||
const transaction = contract.insertLeaf(treeId, leaf)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(contract, "LeafInserted")
|
||||
.withArgs(
|
||||
treeId,
|
||||
leaf,
|
||||
"16211261537006706331557500769845541584780950636316907182067421710925347020533"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should not insert a leaf if the tree is full", async () => {
|
||||
const treeId = ethers.utils.formatBytes32String("tinyTree")
|
||||
|
||||
await contract.createTree(treeId, 1)
|
||||
await contract.insertLeaf(treeId, leaf)
|
||||
await contract.insertLeaf(treeId, leaf)
|
||||
|
||||
const transaction = contract.insertLeaf(treeId, leaf)
|
||||
|
||||
await expect(transaction).to.be.revertedWith(
|
||||
"IncrementalBinaryTree: tree is full"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should not remove a leaf if the tree does not exist", async () => {
|
||||
const treeId = ethers.utils.formatBytes32String("none")
|
||||
|
||||
const transaction = contract.removeLeaf(treeId, leaf, [0, 1], [0, 1])
|
||||
|
||||
await expect(transaction).to.be.revertedWith("Test: tree does not exist")
|
||||
})
|
||||
|
||||
it("Should not remove a leaf if its value is > SNARK_SCALAR_FIELD", async () => {
|
||||
const leaf = BigInt(
|
||||
"21888242871839275222246405745257275088548364400416034343698204186575808495618"
|
||||
)
|
||||
|
||||
const transaction = contract.removeLeaf(treeId, leaf, [0, 1], [0, 1])
|
||||
|
||||
await expect(transaction).to.be.revertedWith(
|
||||
"IncrementalBinaryTree: leaf must be < SNARK_SCALAR_FIELD"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should remove a leaf", async () => {
|
||||
const treeId = ethers.utils.formatBytes32String("hello")
|
||||
const tree = createTree(depth, 3)
|
||||
|
||||
tree.delete(0)
|
||||
|
||||
await contract.createTree(treeId, depth)
|
||||
await contract.insertLeaf(treeId, BigInt(1))
|
||||
await contract.insertLeaf(treeId, BigInt(2))
|
||||
await contract.insertLeaf(treeId, BigInt(3))
|
||||
|
||||
const { siblings, pathIndices, root } = tree.createProof(0)
|
||||
|
||||
const transaction = contract.removeLeaf(
|
||||
treeId,
|
||||
BigInt(1),
|
||||
siblings.map((s) => s[0]),
|
||||
pathIndices
|
||||
)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(contract, "LeafRemoved")
|
||||
.withArgs(treeId, BigInt(1), root)
|
||||
})
|
||||
|
||||
it("Should remove another leaf", async () => {
|
||||
const treeId = ethers.utils.formatBytes32String("hello")
|
||||
const tree = createTree(depth, 3)
|
||||
|
||||
tree.delete(0)
|
||||
tree.delete(1)
|
||||
|
||||
const { siblings, pathIndices, root } = tree.createProof(1)
|
||||
|
||||
const transaction = contract.removeLeaf(
|
||||
treeId,
|
||||
BigInt(2),
|
||||
siblings.map((s) => s[0]),
|
||||
pathIndices
|
||||
)
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(contract, "LeafRemoved")
|
||||
.withArgs(treeId, BigInt(2), root)
|
||||
})
|
||||
|
||||
it("Should not remove a leaf that does not exist", async () => {
|
||||
const treeId = ethers.utils.formatBytes32String("hello")
|
||||
const tree = createTree(depth, 3)
|
||||
|
||||
tree.delete(0)
|
||||
tree.delete(1)
|
||||
|
||||
const { siblings, pathIndices } = tree.createProof(0)
|
||||
|
||||
const transaction = contract.removeLeaf(
|
||||
treeId,
|
||||
BigInt(4),
|
||||
siblings.map((s) => s[0]),
|
||||
pathIndices
|
||||
)
|
||||
|
||||
await expect(transaction).to.be.revertedWith(
|
||||
"IncrementalBinaryTree: leaf is not part of the tree"
|
||||
)
|
||||
})
|
||||
|
||||
it("Should insert a leaf in a tree after a removal", async () => {
|
||||
const treeId = ethers.utils.formatBytes32String("hello")
|
||||
const tree = createTree(depth, 4)
|
||||
|
||||
tree.delete(0)
|
||||
tree.delete(1)
|
||||
|
||||
const transaction = contract.insertLeaf(treeId, BigInt(4))
|
||||
|
||||
await expect(transaction)
|
||||
.to.emit(contract, "LeafInserted")
|
||||
.withArgs(treeId, BigInt(4), tree.root)
|
||||
})
|
||||
|
||||
it("Should insert 4 leaves and remove them all", async () => {
|
||||
const treeId = ethers.utils.formatBytes32String("complex")
|
||||
const tree = createTree(depth, 4)
|
||||
|
||||
await contract.createTree(treeId, depth)
|
||||
|
||||
for (let i = 0; i < 4; i += 1) {
|
||||
await contract.insertLeaf(treeId, BigInt(i + 1))
|
||||
}
|
||||
|
||||
for (let i = 0; i < 4; i += 1) {
|
||||
tree.delete(i)
|
||||
|
||||
const { siblings, pathIndices } = tree.createProof(i)
|
||||
|
||||
await contract.removeLeaf(
|
||||
treeId,
|
||||
BigInt(i + 1),
|
||||
siblings.map((s) => s[0]),
|
||||
pathIndices
|
||||
)
|
||||
}
|
||||
|
||||
const { root } = await contract.trees(treeId)
|
||||
|
||||
expect(root).to.equal(tree.root)
|
||||
})
|
||||
})
|
||||
18
packages/incremental-merkle-tree.sol/test/utils.ts
Normal file
18
packages/incremental-merkle-tree.sol/test/utils.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { IncrementalMerkleTree } from "@zk-kit/incremental-merkle-tree"
|
||||
import { poseidon } from "circomlibjs"
|
||||
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export function createTree(
|
||||
depth: number,
|
||||
numberOfNodes = 0,
|
||||
zeroValue = BigInt(0),
|
||||
arity = 2
|
||||
): IncrementalMerkleTree {
|
||||
const tree = new IncrementalMerkleTree(poseidon, depth, zeroValue, arity)
|
||||
|
||||
for (let i = 0; i < numberOfNodes; i += 1) {
|
||||
tree.insert(BigInt(i + 1))
|
||||
}
|
||||
|
||||
return tree
|
||||
}
|
||||
8
packages/incremental-merkle-tree.sol/tsconfig.json
Normal file
8
packages/incremental-merkle-tree.sol/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["tasks/**/*", "test/**/*"],
|
||||
"files": ["hardhat.config.ts"]
|
||||
}
|
||||
10284
packages/incremental-merkle-tree.sol/yarn.lock
Normal file
10284
packages/incremental-merkle-tree.sol/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user