From 3660b80e0f3966e240ff9416997be76617f2de14 Mon Sep 17 00:00:00 2001 From: 0xbok <1689531+0xbok@users.noreply.github.com> Date: Wed, 20 Jul 2022 22:44:35 +0530 Subject: [PATCH 1/3] refactor(incremental-merkle-tree.sol): gas optimizations --- .../contracts/IncrementalBinaryTree.sol | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/packages/incremental-merkle-tree.sol/contracts/IncrementalBinaryTree.sol b/packages/incremental-merkle-tree.sol/contracts/IncrementalBinaryTree.sol index 1ced29e..8523efa 100644 --- a/packages/incremental-merkle-tree.sol/contracts/IncrementalBinaryTree.sol +++ b/packages/incremental-merkle-tree.sol/contracts/IncrementalBinaryTree.sol @@ -6,7 +6,7 @@ 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 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). @@ -28,7 +28,7 @@ library IncrementalBinaryTree { /// @param zero: Zero value to be used. function init( IncrementalTreeData storage self, - uint8 depth, + uint256 depth, uint256 zero ) public { require(zero < SNARK_SCALAR_FIELD, "IncrementalBinaryTree: leaf must be < SNARK_SCALAR_FIELD"); @@ -36,9 +36,12 @@ library IncrementalBinaryTree { self.depth = depth; - for (uint8 i = 0; i < depth; i++) { + for (uint8 i = 0; i < depth;) { self.zeroes[i] = zero; zero = PoseidonT3.poseidon([zero, zero]); + unchecked { + ++i; + } } self.root = zero; @@ -49,12 +52,13 @@ library IncrementalBinaryTree { /// @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 depth = self.depth; + require(self.numberOfLeaves < 2**depth, "IncrementalBinaryTree: tree is full"); uint256 index = self.numberOfLeaves; uint256 hash = leaf; - for (uint8 i = 0; i < self.depth; i++) { + for (uint8 i = 0; i < depth;) { if (index % 2 == 0) { self.lastSubtrees[i] = [hash, self.zeroes[i]]; } else { @@ -63,6 +67,9 @@ library IncrementalBinaryTree { hash = PoseidonT3.poseidon(self.lastSubtrees[i]); index /= 2; + unchecked { + ++i; + } } self.root = hash; @@ -89,7 +96,8 @@ library IncrementalBinaryTree { uint256 hash = newLeaf; - for (uint8 i = 0; i < self.depth; i++) { + uint256 depth = self.depth; + for (uint8 i = 0; i < depth;) { if (proofPathIndices[i] == 0) { if (proofSiblings[i] == self.lastSubtrees[i][1]) { self.lastSubtrees[i][0] = hash; @@ -103,6 +111,9 @@ library IncrementalBinaryTree { hash = PoseidonT3.poseidon([proofSiblings[i], hash]); } + unchecked { + ++i; + } } self.root = hash; @@ -126,7 +137,8 @@ library IncrementalBinaryTree { uint256 hash = self.zeroes[0]; - for (uint8 i = 0; i < self.depth; i++) { + uint256 depth = self.depth; + for (uint8 i = 0; i < depth;) { if (proofPathIndices[i] == 0) { if (proofSiblings[i] == self.lastSubtrees[i][1]) { self.lastSubtrees[i][0] = hash; @@ -140,6 +152,9 @@ library IncrementalBinaryTree { hash = PoseidonT3.poseidon([proofSiblings[i], hash]); } + unchecked { + ++i; + } } self.root = hash; @@ -158,14 +173,15 @@ library IncrementalBinaryTree { uint8[] calldata proofPathIndices ) private view returns (bool) { require(leaf < SNARK_SCALAR_FIELD, "IncrementalBinaryTree: leaf must be < SNARK_SCALAR_FIELD"); + uint256 depth = self.depth; require( - proofPathIndices.length == self.depth && proofSiblings.length == self.depth, + proofPathIndices.length == depth && proofSiblings.length == depth, "IncrementalBinaryTree: length of path is not correct" ); uint256 hash = leaf; - for (uint8 i = 0; i < self.depth; i++) { + for (uint8 i = 0; i < depth;) { require( proofSiblings[i] < SNARK_SCALAR_FIELD, "IncrementalBinaryTree: sibling node must be < SNARK_SCALAR_FIELD" @@ -176,6 +192,9 @@ library IncrementalBinaryTree { } else { hash = PoseidonT3.poseidon([proofSiblings[i], hash]); } + unchecked { + ++i; + } } return hash == self.root; From 965f789f1f2f2307729d2c0b58857743478f7ed6 Mon Sep 17 00:00:00 2001 From: 0xbok <1689531+0xbok@users.noreply.github.com> Date: Wed, 20 Jul 2022 22:57:53 +0530 Subject: [PATCH 2/3] refactor(incremental-merkle-tree.sol): use bit operators --- .../contracts/IncrementalBinaryTree.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/incremental-merkle-tree.sol/contracts/IncrementalBinaryTree.sol b/packages/incremental-merkle-tree.sol/contracts/IncrementalBinaryTree.sol index 8523efa..25dd4e6 100644 --- a/packages/incremental-merkle-tree.sol/contracts/IncrementalBinaryTree.sol +++ b/packages/incremental-merkle-tree.sol/contracts/IncrementalBinaryTree.sol @@ -59,14 +59,14 @@ library IncrementalBinaryTree { uint256 hash = leaf; for (uint8 i = 0; i < depth;) { - if (index % 2 == 0) { + if (index & 1 == 0) { self.lastSubtrees[i] = [hash, self.zeroes[i]]; } else { self.lastSubtrees[i][1] = hash; } hash = PoseidonT3.poseidon(self.lastSubtrees[i]); - index /= 2; + index >>= 1; unchecked { ++i; } From f0e5bcf47c0629fa678bf5ab0417171812978bdd Mon Sep 17 00:00:00 2001 From: 0xbok <1689531+0xbok@users.noreply.github.com> Date: Wed, 20 Jul 2022 23:22:47 +0530 Subject: [PATCH 3/3] refactor(incremental-merkle-tree.sol): gas optimizations quin tree --- .../contracts/IncrementalBinaryTree.sol | 10 +-- .../contracts/IncrementalQuinTree.sol | 63 ++++++++++++++----- 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/packages/incremental-merkle-tree.sol/contracts/IncrementalBinaryTree.sol b/packages/incremental-merkle-tree.sol/contracts/IncrementalBinaryTree.sol index 25dd4e6..8685850 100644 --- a/packages/incremental-merkle-tree.sol/contracts/IncrementalBinaryTree.sol +++ b/packages/incremental-merkle-tree.sol/contracts/IncrementalBinaryTree.sol @@ -36,7 +36,7 @@ library IncrementalBinaryTree { self.depth = depth; - for (uint8 i = 0; i < depth;) { + for (uint8 i = 0; i < depth; ) { self.zeroes[i] = zero; zero = PoseidonT3.poseidon([zero, zero]); unchecked { @@ -58,7 +58,7 @@ library IncrementalBinaryTree { uint256 index = self.numberOfLeaves; uint256 hash = leaf; - for (uint8 i = 0; i < depth;) { + for (uint8 i = 0; i < depth; ) { if (index & 1 == 0) { self.lastSubtrees[i] = [hash, self.zeroes[i]]; } else { @@ -97,7 +97,7 @@ library IncrementalBinaryTree { uint256 hash = newLeaf; uint256 depth = self.depth; - for (uint8 i = 0; i < depth;) { + for (uint8 i = 0; i < depth; ) { if (proofPathIndices[i] == 0) { if (proofSiblings[i] == self.lastSubtrees[i][1]) { self.lastSubtrees[i][0] = hash; @@ -138,7 +138,7 @@ library IncrementalBinaryTree { uint256 hash = self.zeroes[0]; uint256 depth = self.depth; - for (uint8 i = 0; i < depth;) { + for (uint8 i = 0; i < depth; ) { if (proofPathIndices[i] == 0) { if (proofSiblings[i] == self.lastSubtrees[i][1]) { self.lastSubtrees[i][0] = hash; @@ -181,7 +181,7 @@ library IncrementalBinaryTree { uint256 hash = leaf; - for (uint8 i = 0; i < depth;) { + for (uint8 i = 0; i < depth; ) { require( proofSiblings[i] < SNARK_SCALAR_FIELD, "IncrementalBinaryTree: sibling node must be < SNARK_SCALAR_FIELD" diff --git a/packages/incremental-merkle-tree.sol/contracts/IncrementalQuinTree.sol b/packages/incremental-merkle-tree.sol/contracts/IncrementalQuinTree.sol index 8519067..6ec4d68 100644 --- a/packages/incremental-merkle-tree.sol/contracts/IncrementalQuinTree.sol +++ b/packages/incremental-merkle-tree.sol/contracts/IncrementalQuinTree.sol @@ -6,7 +6,7 @@ import {PoseidonT6} 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 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). @@ -28,23 +28,28 @@ library IncrementalQuinTree { /// @param zero: Zero value to be used. function init( IncrementalTreeData storage self, - uint8 depth, + uint256 depth, uint256 zero ) public { require(zero < SNARK_SCALAR_FIELD, "IncrementalBinaryTree: leaf must be < SNARK_SCALAR_FIELD"); require(depth > 0 && depth <= MAX_DEPTH, "IncrementalQuinTree: tree depth must be between 1 and 32"); self.depth = depth; - - for (uint8 i = 0; i < depth; i++) { + for (uint8 i = 0; i < depth; ) { self.zeroes[i] = zero; uint256[5] memory zeroChildren; - for (uint8 j = 0; j < 5; j++) { + for (uint8 j = 0; j < 5; ) { zeroChildren[j] = zero; + unchecked { + ++j; + } } zero = PoseidonT6.poseidon(zeroChildren); + unchecked { + ++i; + } } self.root = zero; @@ -55,24 +60,31 @@ library IncrementalQuinTree { /// @param leaf: Leaf to be inserted. function insert(IncrementalTreeData storage self, uint256 leaf) public { require(leaf < SNARK_SCALAR_FIELD, "IncrementalQuinTree: leaf must be < SNARK_SCALAR_FIELD"); - require(self.numberOfLeaves < 5**self.depth, "IncrementalQuinTree: tree is full"); + uint256 depth = self.depth; + require(self.numberOfLeaves < 5**depth, "IncrementalQuinTree: tree is full"); uint256 index = self.numberOfLeaves; uint256 hash = leaf; - for (uint8 i = 0; i < self.depth; i++) { + for (uint8 i = 0; i < depth; ) { uint8 position = uint8(index % 5); self.lastSubtrees[i][position] = hash; if (position == 0) { - for (uint8 j = 1; j < 5; j++) { + for (uint8 j = 1; j < 5; ) { self.lastSubtrees[i][j] = self.zeroes[i]; + unchecked { + ++j; + } } } hash = PoseidonT6.poseidon(self.lastSubtrees[i]); index /= 5; + unchecked { + ++i; + } } self.root = hash; @@ -99,10 +111,11 @@ library IncrementalQuinTree { uint256 hash = newLeaf; - for (uint8 i = 0; i < self.depth; i++) { + uint256 depth = self.depth; + for (uint8 i = 0; i < depth; ) { uint256[5] memory nodes; - for (uint8 j = 0; j < 5; j++) { + for (uint8 j = 0; j < 5; ) { if (j < proofPathIndices[i]) { nodes[j] = proofSiblings[i][j]; } else if (j == proofPathIndices[i]) { @@ -110,6 +123,9 @@ library IncrementalQuinTree { } else { nodes[j] = proofSiblings[i][j - 1]; } + unchecked { + ++j; + } } if (nodes[0] == self.lastSubtrees[i][0] || nodes[4] == self.lastSubtrees[i][4]) { @@ -117,6 +133,9 @@ library IncrementalQuinTree { } hash = PoseidonT6.poseidon(nodes); + unchecked { + ++i; + } } self.root = hash; @@ -140,10 +159,11 @@ library IncrementalQuinTree { uint256 hash = self.zeroes[0]; - for (uint8 i = 0; i < self.depth; i++) { + uint256 depth = self.depth; + for (uint8 i = 0; i < depth; ) { uint256[5] memory nodes; - for (uint8 j = 0; j < 5; j++) { + for (uint8 j = 0; j < 5; ) { if (j < proofPathIndices[i]) { nodes[j] = proofSiblings[i][j]; } else if (j == proofPathIndices[i]) { @@ -151,6 +171,9 @@ library IncrementalQuinTree { } else { nodes[j] = proofSiblings[i][j - 1]; } + unchecked { + ++j; + } } if (nodes[0] == self.lastSubtrees[i][0] || nodes[4] == self.lastSubtrees[i][4]) { @@ -158,6 +181,9 @@ library IncrementalQuinTree { } hash = PoseidonT6.poseidon(nodes); + unchecked { + ++i; + } } self.root = hash; @@ -176,17 +202,18 @@ library IncrementalQuinTree { uint8[] calldata proofPathIndices ) private view returns (bool) { require(leaf < SNARK_SCALAR_FIELD, "IncrementalQuinTree: leaf must be < SNARK_SCALAR_FIELD"); + uint256 depth = self.depth; require( - proofPathIndices.length == self.depth && proofSiblings.length == self.depth, + proofPathIndices.length == depth && proofSiblings.length == depth, "IncrementalQuinTree: length of path is not correct" ); uint256 hash = leaf; - for (uint8 i = 0; i < self.depth; i++) { + for (uint8 i = 0; i < depth; ) { uint256[5] memory nodes; - for (uint8 j = 0; j < 5; j++) { + for (uint8 j = 0; j < 5; ) { if (j < proofPathIndices[i]) { require( proofSiblings[i][j] < SNARK_SCALAR_FIELD, @@ -204,9 +231,15 @@ library IncrementalQuinTree { nodes[j] = proofSiblings[i][j - 1]; } + unchecked { + ++j; + } } hash = PoseidonT6.poseidon(nodes); + unchecked { + ++i; + } } return hash == self.root;