CommitmentTree
The CommitmentTree contract maintains an incremental Merkle tree of deposit commitments. It stores a ring buffer of historical roots so that proofs generated against recent states remain valid even after new deposits are recorded.
Deployed address: 0xE29DD14998f6FE8e7862571c883090d14FE29475
Constants
uint256 public constant TREE_DEPTH = 20;
The Merkle tree has a fixed depth of 20, supporting up to 2^20 (1,048,576) commitment leaves.
uint256 public constant ROOT_HISTORY_SIZE = 100;
The contract maintains a ring buffer of the last 100 Merkle roots. Any proof referencing one of these roots is considered valid, providing a window for concurrent deposits and reveals.
Functions
recordCommitment
function recordCommitment(bytes32 commitment) external returns (uint256 leafIndex)
Inserts a new commitment leaf into the Merkle tree.
Parameters:
| Name | Type | Description |
|---|---|---|
commitment | bytes32 | The Pedersen commitment to record as a leaf in the tree. |
Returns:
| Name | Type | Description |
|---|---|---|
leafIndex | uint256 | The zero-based index of the newly inserted leaf. |
Access control: Only the CommitRevealVault contract can call this function.
Behavior:
- Assigns the commitment to the next available leaf slot.
- Increments the internal commitment counter.
- Returns the leaf index for use in the
Committedevent.
Reverts if:
- Caller is not the authorized vault.
- Tree is full (all 2^20 leaves occupied).
updateRoot
function updateRoot(bytes32 newRoot) external
Pushes a new Merkle root into the ring buffer. Called by the off-chain operator after recomputing the tree root from the latest leaves.
Parameters:
| Name | Type | Description |
|---|---|---|
newRoot | bytes32 | The newly computed Merkle root. |
Access control: Only the designated operator address can call this function.
Behavior:
- Writes
newRootinto the ring buffer at positioncurrentIndex % ROOT_HISTORY_SIZE. - Advances the ring buffer index.
isKnownRoot
function isKnownRoot(bytes32 root) external view returns (bool)
Checks whether a given root exists in the root history ring buffer.
Parameters:
| Name | Type | Description |
|---|---|---|
root | bytes32 | The Merkle root to check. |
Returns:
| Name | Type | Description |
|---|---|---|
| (unnamed) | bool | true if the root is found in the ring buffer, false otherwise. |
Usage: Called by CommitRevealVault.reveal() to verify that the proof's Merkle root is recent and valid.
getLastRoot
function getLastRoot() external view returns (bytes32)
Returns the most recently stored Merkle root.
Returns:
| Name | Type | Description |
|---|---|---|
| (unnamed) | bytes32 | The latest Merkle root in the ring buffer. |
getCommitmentCount
function getCommitmentCount() external view returns (uint256)
Returns the total number of commitments recorded in the tree.
Returns:
| Name | Type | Description |
|---|---|---|
| (unnamed) | uint256 | The number of leaves inserted so far (0-indexed next slot). |
Storage Layout
| Slot | Type | Description |
|---|---|---|
roots | bytes32[ROOT_HISTORY_SIZE] | Ring buffer of historical Merkle roots. |
currentRootIndex | uint256 | Current write position in the ring buffer. |
nextLeafIndex | uint256 | Index of the next leaf to be inserted. |
commitments | mapping(uint256 => bytes32) | Maps leaf index to commitment value. |
Ring Buffer Mechanics
The root history ring buffer works as follows:
Index: 0 1 2 ... 99 0 1 ...
Root: R0 R1 R2 ... R99 R100 R101 ...
When a new root is stored, it overwrites the oldest entry once the buffer wraps. This means:
- The most recent 100 roots are always available for proof verification.
- Proofs generated against a root older than 100 updates will fail
isKnownRootand cannot be used. - Users should generate and submit proofs promptly to avoid root expiration.
Usage Example
// Check if a root is valid (called internally by CommitRevealVault)
bool valid = CommitmentTree(treeAddress).isKnownRoot(proofRoot);
require(valid, "Unknown merkle root");
// Query the current state
uint256 totalDeposits = CommitmentTree(treeAddress).getCommitmentCount();
bytes32 latestRoot = CommitmentTree(treeAddress).getLastRoot();
Integration Notes
- The tree uses Poseidon hashing (via
PoseidonT3) for internal node computation, matching the ZK circuit's hash function. recordCommitmentonly stores the leaf; it does not recompute the Merkle root on-chain. Root updates are performed by the operator viaupdateRootafter off-chain tree recomputation. This design saves gas while maintaining verifiability.- The operator must call
updateRootbefore reveals referencing the newly inserted leaves can succeed.