Skip to content

Commit 43529fa

Browse files
committed
refactor: optimize events
1 parent eb2cd7d commit 43529fa

5 files changed

Lines changed: 34 additions & 290 deletions

File tree

contracts/src/interfaces/ICommitmentAccumulator.sol

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,4 @@ interface ICommitmentAccumulator {
2323
function verifyMerkleProof(bytes32 root, bytes32 commitment, bytes32[] calldata path, uint256 directionBits)
2424
external
2525
view;
26-
27-
/// @notice Returns the Merkle proof and associated root for a commitment leaf in the tree.
28-
/// @param commitment The commitment leaf to proof inclusion in the tree for.
29-
/// @return siblings The siblings constituting the path from the leaf to the root.
30-
/// @return directionBits The direction bits for the proof.
31-
function merkleProof(bytes32 commitment) external view returns (bytes32[] memory siblings, uint256 directionBits);
3226
}

contracts/src/libs/MerkleTree.sol

Lines changed: 31 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ import {SHA256} from "../libs/SHA256.sol";
1010
/// @notice A Merkle tree implementation populating a tree of variable depth from left to right
1111
/// and providing on-chain Merkle proofs.
1212
/// @dev This is a modified version of the OpenZeppelin `MerkleTree` and `MerkleProof` implementation.
13-
/// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.3.0/contracts/utils/structs/MerkleTree.sol
14-
/// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.3.0/contracts/utils/cryptography/MerkleProof.sol
13+
/// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.4.0/contracts/utils/structs/MerkleTree.sol
14+
/// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.4.0/contracts/utils/cryptography/MerkleProof.sol
1515
/// @custom:security-contact security@anoma.foundation
1616
library MerkleTree {
1717
struct Tree {
1818
uint256 _nextLeafIndex;
19-
mapping(uint256 level => mapping(uint256 index => bytes32 node)) _nodes;
19+
bytes32[] _sides;
2020
bytes32[] _zeros;
2121
}
2222

@@ -30,7 +30,15 @@ library MerkleTree {
3030
function setup(Tree storage self) internal returns (bytes32 initialRoot) {
3131
initialRoot = SHA256.EMPTY_HASH;
3232

33-
self._zeros.push(SHA256.EMPTY_HASH);
33+
// Store depth in the dynamic array
34+
Arrays.unsafeSetLength(self._zeros, 256);
35+
36+
// Build each root of zero-filled subtrees
37+
bytes32 currentZero = SHA256.EMPTY_HASH;
38+
for (uint256 i = 0; i < 256; ++i) {
39+
Arrays.unsafeAccess(self._zeros, i).value = currentZero;
40+
currentZero = SHA256.hash(currentZero, currentZero);
41+
}
3442

3543
self._nextLeafIndex = 0;
3644
}
@@ -47,110 +55,43 @@ library MerkleTree {
4755
// Get the next leaf index and increment it after assignment.
4856
index = self._nextLeafIndex++;
4957

58+
// Rebuild the branch from leaf to root.
59+
uint256 currentIndex = index;
5060
bytes32 currentLevelHash = leaf;
61+
for (uint256 i = 0; i < treeDepth; ++i) {
62+
// Compute the next level hash for depth `i+1`.
63+
// Check whether the `currentIndex` node is the left or right child of its parent.
64+
if (isLeftChild(currentIndex)) {
65+
// Store the current hash as the sibling (side) for the current level.
66+
Arrays.unsafeAccess(self._sides, i).value = currentLevelHash;
5167

52-
if (treeDepth == 0) {
53-
self._nodes[0][0] = currentLevelHash;
54-
} else {
55-
uint256 currentIndex = index;
56-
57-
// Rebuild the branch from leaf to root.
58-
for (uint256 i = 0; i < treeDepth; ++i) {
59-
// Store the current node hash at depth `i`.
60-
self._nodes[i][currentIndex] = currentLevelHash;
61-
62-
// Compute the next level hash for depth `i+1`.
63-
// Check whether the `currentIndex` node is the left or right child of its parent.
64-
if (isLeftChild(currentIndex)) {
65-
// Compute the `currentLevelHash` using the right sibling.
66-
// Because we fill the tree from left to right,
67-
// the right child is empty and we must use the depth `i` zero hash.
68-
currentLevelHash = SHA256.hash(currentLevelHash, Arrays.unsafeAccess(self._zeros, i).value);
69-
} else {
70-
// Compute the `currentLevelHash` using the left sibling.
71-
// Because we fill the tree from left to right,
72-
// the left child is the previous node at depth `i`.
73-
currentLevelHash = SHA256.hash(self._nodes[i][currentIndex - 1], currentLevelHash);
74-
}
75-
76-
currentIndex >>= 1;
68+
// Compute the current level hash using the right sibling, which is the zero hash of this level.
69+
currentLevelHash = SHA256.hash(currentLevelHash, Arrays.unsafeAccess(self._zeros, i).value);
70+
} else {
71+
// Compute the current level hash using the left sibling (side).
72+
currentLevelHash = SHA256.hash(Arrays.unsafeAccess(self._sides, i).value, currentLevelHash);
7773
}
74+
75+
currentIndex >>= 1;
7876
}
7977

8078
// Expand the tree if the capacity is reached.
8179
if (self._nextLeafIndex == capacity(self)) {
82-
// Store the current hash in the current level at index 0.
83-
self._nodes[treeDepth][0] = currentLevelHash;
84-
85-
// Compute the new current level hash of the expanded tree.
86-
bytes32 currentZero = Arrays.unsafeAccess(self._zeros, treeDepth).value;
80+
// Store the current level hash as the sibling (side) for the current level.
81+
self._sides.push(currentLevelHash);
8782

8883
// Compute the new current level hash.
89-
currentLevelHash = SHA256.hash(currentLevelHash, currentZero);
90-
91-
// Compute the next zero for the next level.
92-
bytes32 nextZero = SHA256.hash(currentZero, currentZero);
93-
self._zeros.push(nextZero);
84+
currentLevelHash = SHA256.hash(currentLevelHash, Arrays.unsafeAccess(self._zeros, treeDepth).value);
9485
}
9586

9687
newRoot = currentLevelHash;
9788
}
9889

99-
/// @notice Computes a Merkle proof consisting of the sibling at each depth and the associated direction bit
100-
/// indicating whether the sibling is left (0) or right (1) at the respective depth.
101-
/// @param self The tree data structure.
102-
/// @param index The index of the leaf.
103-
/// @return siblings The siblings of the leaf to proof inclusion for.
104-
/// @return directionBits The direction bits indicating whether the siblings are left of right.
105-
function merkleProof(Tree storage self, uint256 index)
106-
internal
107-
view
108-
returns (bytes32[] memory siblings, uint256 directionBits)
109-
{
110-
uint256 treeDepth = depth(self);
111-
112-
// Check whether the index exists or not.
113-
if (index + 1 > self._nextLeafIndex) revert NonExistentLeafIndex(index);
114-
115-
siblings = new bytes32[](treeDepth);
116-
uint256 currentIndex = index;
117-
bytes32 currentSibling;
118-
119-
// Iterate over the different tree levels starting at the bottom at the leaf level.
120-
for (uint256 i = 0; i < treeDepth; ++i) {
121-
// Check if the current node the left or right child of its parent.
122-
if (isLeftChild(currentIndex)) {
123-
// Sibling is right.
124-
currentSibling = self._nodes[i][currentIndex + 1];
125-
126-
// Set the direction bit at position `i` to 1.
127-
directionBits |= (1 << i);
128-
} else {
129-
// Sibling is left.
130-
currentSibling = self._nodes[i][currentIndex - 1];
131-
132-
// Leave the direction bit at position `i` as 0.
133-
}
134-
135-
// Check if the sibling is an empty subtree.
136-
if (currentSibling == bytes32(0)) {
137-
// The subtree node doesn't exist, so we store the zero hash instead.
138-
siblings[i] = Arrays.unsafeAccess(self._zeros, i).value;
139-
} else {
140-
// The subtree node exists, so we store it.
141-
siblings[i] = currentSibling;
142-
}
143-
144-
// Shift the number one bit to the right to drop the last binary digit.
145-
currentIndex >>= 1;
146-
}
147-
}
148-
14990
/// @notice Returns the tree depth.
15091
/// @param self The tree data structure.
15192
/// @return treeDepth The depth of the tree.
15293
function depth(Tree storage self) internal view returns (uint256 treeDepth) {
153-
treeDepth = self._zeros.length - 1;
94+
treeDepth = self._sides.length;
15495
}
15596

15697
/// @notice Returns the number of leaves that have been added to the tree.

contracts/src/state/CommitmentAccumulator.sol

Lines changed: 0 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,9 @@ contract CommitmentAccumulator is ICommitmentAccumulator {
1919
using EnumerableSet for EnumerableSet.Bytes32Set;
2020
using Arrays for bytes32[];
2121

22-
uint256 internal constant _COMMITMENT_INDEX_OFFSET = 1;
23-
2422
MerkleTree.Tree internal _merkleTree;
2523
EnumerableSet.Bytes32Set internal _roots;
2624

27-
mapping(bytes32 commitment => uint256 index) internal _indices;
28-
2925
error EmptyCommitment();
3026
error NonExistingCommitment(bytes32 commitment);
3127
error PreExistingCommitment(bytes32 commitment);
@@ -63,25 +59,12 @@ contract CommitmentAccumulator is ICommitmentAccumulator {
6359
_verifyMerkleProof({root: root, commitment: commitment, path: path, directionBits: directionBits});
6460
}
6561

66-
/// @inheritdoc ICommitmentAccumulator
67-
function merkleProof(bytes32 commitment)
68-
external
69-
view
70-
override
71-
returns (bytes32[] memory siblings, uint256 directionBits)
72-
{
73-
(siblings, directionBits) = _merkleProof(commitment);
74-
}
75-
7662
/// @notice Adds a commitment to to the set, if it does not exist already and returns the new root.
7763
/// @param commitment The commitment to add.
7864
/// @return newRoot The resulting new root.
7965
function _addCommitment(bytes32 commitment) internal returns (bytes32 newRoot) {
80-
_checkCommitmentNonExistence(commitment);
81-
8266
uint256 index;
8367
(index, newRoot) = _merkleTree.push(commitment);
84-
_indices[commitment] = index + _COMMITMENT_INDEX_OFFSET; // Add 1 to use 0 as a sentinel value
8568
}
8669

8770
/// @notice Stores a root in the set of historical roots.
@@ -119,80 +102,6 @@ contract CommitmentAccumulator is ICommitmentAccumulator {
119102
}
120103
}
121104

122-
/// @notice An internal function returning the Merkle proof and associated root for a commitment leaf in the tree.
123-
/// @param commitment The commitment leaf to proof inclusion in the tree for.
124-
/// @return siblings The siblings constituting the path from the leaf to the root.
125-
/// @return directionBits The direction bits for the proof.
126-
function _merkleProof(bytes32 commitment)
127-
internal
128-
view
129-
returns (bytes32[] memory siblings, uint256 directionBits)
130-
{
131-
uint256 leafIndex = _findCommitmentIndex(commitment);
132-
(siblings, directionBits) = (_merkleTree.merkleProof(leafIndex));
133-
}
134-
135-
/// @notice Returns whether a commitment is already contained in the accumulator.
136-
/// @param commitment The commitment to check.
137-
/// @return isContained Whether the commitment is contained or not.
138-
function _isContained(bytes32 commitment) internal view returns (bool isContained) {
139-
uint256 index = _indices[commitment];
140-
141-
isContained = index != 0;
142-
}
143-
144-
/// @notice Finds the index of a commitment in the accumulator or reverts.
145-
/// @param commitment The commitment to find the index for.
146-
/// @return index The index of the commitment in the accumulator.
147-
function _findCommitmentIndex(bytes32 commitment) internal view returns (uint256 index) {
148-
if (commitment == _emptyLeaf()) {
149-
revert EmptyCommitment();
150-
}
151-
152-
index = _indices[commitment];
153-
154-
if (index == 0) {
155-
revert NonExistingCommitment(commitment);
156-
}
157-
158-
index -= _COMMITMENT_INDEX_OFFSET;
159-
160-
bytes32 retrieved = _commitmentAtIndex(index);
161-
if (retrieved != commitment) {
162-
revert CommitmentMismatch({expected: commitment, actual: retrieved});
163-
}
164-
}
165-
166-
/// @notice Returns a commitment based on its index in the accumulator or reverts.
167-
/// @param index The index to find the commitment for.
168-
/// @return commitment The commitment associated with the index.
169-
function _commitmentAtIndex(uint256 index) internal view returns (bytes32 commitment) {
170-
if (index + 1 > _merkleTree._nextLeafIndex) {
171-
revert CommitmentIndexOutOfBounds({current: index, limit: _merkleTree._nextLeafIndex});
172-
}
173-
174-
commitment = _merkleTree._nodes[0][index];
175-
}
176-
177-
/// @notice Checks the non-existence of a commitment in the tree.
178-
/// @param commitment The commitment to check.
179-
function _checkCommitmentNonExistence(bytes32 commitment) internal view {
180-
if (_isContained(commitment)) {
181-
revert PreExistingCommitment(commitment);
182-
}
183-
}
184-
185-
// slither-disable-start dead-code
186-
/// @notice Checks the existence of a commitment in the tree.
187-
/// @param commitment The commitment to check.
188-
189-
function _checkCommitmentPreExistence(bytes32 commitment) internal view {
190-
if (!_isContained(commitment)) {
191-
revert NonExistingCommitment(commitment);
192-
}
193-
}
194-
// slither-disable-end dead-code
195-
196105
/// @notice Checks the existence of a root in the set of historical roots.
197106
/// @param root The root to check.
198107
function _checkRootPreExistence(bytes32 root) internal view {
@@ -201,12 +110,6 @@ contract CommitmentAccumulator is ICommitmentAccumulator {
201110
}
202111
}
203112

204-
/// @notice Returns the hash indicating that a leaf in the tree is empty.
205-
/// @return emptyLeaf The empty leaf hash.
206-
function _emptyLeaf() internal view returns (bytes32 emptyLeaf) {
207-
emptyLeaf = _merkleTree._zeros[0];
208-
}
209-
210113
/// @notice Returns the latest commitment tree state root.
211114
/// @return root The latest commitment tree state root.
212115
function _latestRoot() internal view returns (bytes32 root) {

contracts/test/mocks/CommitmentAccumulator.m.sol

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,6 @@ contract CommitmentAccumulatorMock is CommitmentAccumulator {
4444
hash = _merkleTreeZero(0);
4545
}
4646

47-
function findCommitmentIndex(bytes32 commitment) external view returns (uint256 index) {
48-
index = _findCommitmentIndex(commitment);
49-
}
50-
51-
function commitmentAtIndex(uint256 index) external view returns (bytes32 commitment) {
52-
commitment = _commitmentAtIndex(index);
53-
}
54-
5547
function _merkleTreeZero(uint256 level) internal view returns (bytes32 zeroHash) {
5648
zeroHash = _merkleTree._zeros[level];
5749
}

0 commit comments

Comments
 (0)