From d370ba60c0374197c386eb9966d1c83fb5f7b656 Mon Sep 17 00:00:00 2001 From: Marti Date: Mon, 19 Jan 2026 10:12:00 +0000 Subject: [PATCH 01/13] feat: generate test vectors with foundry --- .gitmodules | 3 + .../miden-agglayer/solidity-compat/.gitignore | 10 ++ .../miden-agglayer/solidity-compat/README.md | 61 ++++++++ .../solidity-compat/foundry.lock | 8 + .../solidity-compat/foundry.toml | 12 ++ .../solidity-compat/lib/forge-std | 1 + .../src/DepositContractBase.sol | 135 +++++++++++++++++ .../solidity-compat/src/Hashes.sol | 20 +++ .../test-vectors/mmr_frontier_vectors.json | 72 +++++++++ .../solidity-compat/test/MMRTestVectors.t.sol | 140 ++++++++++++++++++ .../tests/agglayer/mmr_frontier.rs | 123 +++++++++++++++ 11 files changed, 585 insertions(+) create mode 100644 .gitmodules create mode 100644 crates/miden-agglayer/solidity-compat/.gitignore create mode 100644 crates/miden-agglayer/solidity-compat/README.md create mode 100644 crates/miden-agglayer/solidity-compat/foundry.lock create mode 100644 crates/miden-agglayer/solidity-compat/foundry.toml create mode 160000 crates/miden-agglayer/solidity-compat/lib/forge-std create mode 100644 crates/miden-agglayer/solidity-compat/src/DepositContractBase.sol create mode 100644 crates/miden-agglayer/solidity-compat/src/Hashes.sol create mode 100644 crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json create mode 100644 crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..1b798beac --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "crates/miden-agglayer/solidity-compat/lib/forge-std"] + path = crates/miden-agglayer/solidity-compat/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/crates/miden-agglayer/solidity-compat/.gitignore b/crates/miden-agglayer/solidity-compat/.gitignore new file mode 100644 index 000000000..4a212ae49 --- /dev/null +++ b/crates/miden-agglayer/solidity-compat/.gitignore @@ -0,0 +1,10 @@ +# Foundry artifacts +/out/ +/cache/ +/lib/ + +# Foundry broadcast files +/broadcast/ + +# Environment +.env diff --git a/crates/miden-agglayer/solidity-compat/README.md b/crates/miden-agglayer/solidity-compat/README.md new file mode 100644 index 000000000..2678ba8c8 --- /dev/null +++ b/crates/miden-agglayer/solidity-compat/README.md @@ -0,0 +1,61 @@ +# Solidity Compatibility Tests + +This directory contains Foundry tests for generating test vectors to verify +that the Miden MMR Frontier implementation is compatible with the Solidity +`DepositContractBase.sol` from [agglayer-contracts](https://github.com/agglayer/agglayer-contracts). + +## Purpose + +The Miden implementation of the Keccak-based MMR frontier (`mmr_frontier32_keccak.masm`) +must produce identical results to the Solidity implementation. These tests generate +reference test vectors that can be compared against the Rust/MASM implementation. + +## Prerequisites + +Install [Foundry](https://book.getfoundry.sh/getting-started/installation): + +```bash +curl -L https://foundry.paradigm.xyz | bash +foundryup +``` + +## Running Tests + +From this directory: + +```bash +# Install dependencies (first time only) +forge install + +# Generate test vectors (human-readable) +forge test -vv --match-test test_generateVectors + +# Generate canonical zeros (for verifying canonical_zeros.masm) +forge test -vv --match-test test_generateCanonicalZeros + +# Generate JSON and save to file (test-vectors/mmr_frontier_vectors.json) +forge test -vv --match-test test_generateVectorsJSON + +# Run all tests +forge test -vv +``` + +### Canonical Zeros + +The `test_generateCanonicalZeros` output should match the constants in: +`crates/miden-agglayer/asm/lib/collections/canonical_zeros.masm` + +To convert Solidity hex to Miden u32 words: +- Solidity: `0xabcdef...` (64 hex chars = 32 bytes) +- Miden: 8 × u32 values, little-endian within each 4-byte chunk, reversed order + +### Test Vectors + +The `test_generateVectors` adds leaves `0, 1, 2, ...` (as left-padded 32-byte values) +and outputs the root after each addition. + +## Source Files + +- `src/DepositContractBase.sol` - Copy from agglayer-contracts @ e468f9b0967334403069aa650d9f1164b1731ebb +- `src/Hashes.sol` - Keccak256 hashing utility (based on OpenZeppelin) +- `test/MMRTestVectors.t.sol` - Test vector generation diff --git a/crates/miden-agglayer/solidity-compat/foundry.lock b/crates/miden-agglayer/solidity-compat/foundry.lock new file mode 100644 index 000000000..d0c0159b3 --- /dev/null +++ b/crates/miden-agglayer/solidity-compat/foundry.lock @@ -0,0 +1,8 @@ +{ + "lib/forge-std": { + "tag": { + "name": "v1.14.0", + "rev": "1801b0541f4fda118a10798fd3486bb7051c5dd6" + } + } +} \ No newline at end of file diff --git a/crates/miden-agglayer/solidity-compat/foundry.toml b/crates/miden-agglayer/solidity-compat/foundry.toml new file mode 100644 index 000000000..c89e0ae6b --- /dev/null +++ b/crates/miden-agglayer/solidity-compat/foundry.toml @@ -0,0 +1,12 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] +solc = "0.8.20" + +# Emit extra output for test vector generation +ffi = false +verbosity = 2 + +# Allow writing test vectors to file +fs_permissions = [{ access = "read-write", path = "test-vectors" }] diff --git a/crates/miden-agglayer/solidity-compat/lib/forge-std b/crates/miden-agglayer/solidity-compat/lib/forge-std new file mode 160000 index 000000000..1801b0541 --- /dev/null +++ b/crates/miden-agglayer/solidity-compat/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 1801b0541f4fda118a10798fd3486bb7051c5dd6 diff --git a/crates/miden-agglayer/solidity-compat/src/DepositContractBase.sol b/crates/miden-agglayer/solidity-compat/src/DepositContractBase.sol new file mode 100644 index 000000000..a197ce180 --- /dev/null +++ b/crates/miden-agglayer/solidity-compat/src/DepositContractBase.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: AGPL-3.0 +// Source: https://github.com/agglayer/agglayer-contracts/blob/e468f9b0967334403069aa650d9f1164b1731ebb/contracts/v2/lib/DepositContractBase.sol +pragma solidity ^0.8.20; + +import "./Hashes.sol"; + +/** + * This contract will be used as a helper for all the sparse merkle tree related functions + * Based on the implementation of the deposit eth2.0 contract https://github.com/ethereum/consensus-specs/blob/dev/solidity_deposit_contract/deposit_contract.sol + */ +contract DepositContractBase { + /** + * @dev Thrown when the merkle tree is full + */ + error MerkleTreeFull(); + + // Merkle tree levels + uint256 internal constant _DEPOSIT_CONTRACT_TREE_DEPTH = 32; + + // This ensures `depositCount` will fit into 32-bits + uint256 internal constant _MAX_DEPOSIT_COUNT = + 2 ** _DEPOSIT_CONTRACT_TREE_DEPTH - 1; + + // Branch array which contains the necessary sibilings to compute the next root when a new + // leaf is inserted + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] internal _branch; + + // Counter of current deposits + uint256 public depositCount; + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + */ + /// @custom:oz-renamed-from _gap + uint256[10] private __gap; + + /** + * @notice Computes and returns the merkle root + */ + function getRoot() public view virtual returns (bytes32) { + bytes32 node; + uint256 size = depositCount; + bytes32 currentZeroHashHeight = 0; + + for ( + uint256 height = 0; + height < _DEPOSIT_CONTRACT_TREE_DEPTH; + height++ + ) { + if (((size >> height) & 1) == 1) + node = Hashes.efficientKeccak256(_branch[height], node); + else node = Hashes.efficientKeccak256(node, currentZeroHashHeight); + + currentZeroHashHeight = Hashes.efficientKeccak256( + currentZeroHashHeight, + currentZeroHashHeight + ); + } + return node; + } + + /** + * @notice Add a new leaf to the merkle tree + * @param leaf Leaf + */ + function _addLeaf(bytes32 leaf) internal { + bytes32 node = leaf; + + // Avoid overflowing the Merkle tree (and prevent edge case in computing `_branch`) + if (depositCount >= _MAX_DEPOSIT_COUNT) { + revert MerkleTreeFull(); + } + + // Add deposit data root to Merkle tree (update a single `_branch` node) + uint256 size = ++depositCount; + for ( + uint256 height = 0; + height < _DEPOSIT_CONTRACT_TREE_DEPTH; + height++ + ) { + if (((size >> height) & 1) == 1) { + _branch[height] = node; + return; + } + node = Hashes.efficientKeccak256(_branch[height], node); + } + // As the loop should always end prematurely with the `return` statement, + // this code should be unreachable. We assert `false` just to be safe. + assert(false); + } + + /** + * @notice Verify merkle proof + * @param leafHash Leaf hash + * @param smtProof Smt proof + * @param index Index of the leaf + * @param root Merkle root + */ + function verifyMerkleProof( + bytes32 leafHash, + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProof, + uint32 index, + bytes32 root + ) public pure returns (bool) { + return calculateRoot(leafHash, smtProof, index) == root; + } + + /** + * @notice Calculate root from merkle proof + * @param leafHash Leaf hash + * @param smtProof Smt proof + * @param index Index of the leaf + */ + function calculateRoot( + bytes32 leafHash, + bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProof, + uint32 index + ) public pure returns (bytes32) { + bytes32 node = leafHash; + + // Compute root + for ( + uint256 height = 0; + height < _DEPOSIT_CONTRACT_TREE_DEPTH; + height++ + ) { + if (((index >> height) & 1) == 1) + node = Hashes.efficientKeccak256(smtProof[height], node); + else node = Hashes.efficientKeccak256(node, smtProof[height]); + } + + return node; + } +} diff --git a/crates/miden-agglayer/solidity-compat/src/Hashes.sol b/crates/miden-agglayer/solidity-compat/src/Hashes.sol new file mode 100644 index 000000000..22aaab6ff --- /dev/null +++ b/crates/miden-agglayer/solidity-compat/src/Hashes.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +// Based on OpenZeppelin's Hashes.sol +pragma solidity ^0.8.20; + +/** + * @dev Library of hashing functions used by the agglayer contracts. + */ +library Hashes { + /** + * @dev Computes keccak256(abi.encode(a, b)) more efficiently using assembly. + * This is equivalent to keccak256(a || b) where || is concatenation. + */ + function efficientKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32 value) { + assembly ("memory-safe") { + mstore(0x00, a) + mstore(0x20, b) + value := keccak256(0x00, 0x40) + } + } +} diff --git a/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json b/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json new file mode 100644 index 000000000..9d86f1809 --- /dev/null +++ b/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json @@ -0,0 +1,72 @@ +{ + "description": "Test vectors from DepositContractBase.sol", + "source_commit": "e468f9b0967334403069aa650d9f1164b1731ebb", + "vectors": [ + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000000", "root": "0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757", "count": 1}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000001", "root": "0x4a90a2c108a29b7755a0a915b9bb950233ce71f8a01859350d7b73cc56f57a62", "count": 2}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000002", "root": "0x2757cc260a62cc7c7708c387ea99f2a6bb5f034ed00da845734bec4d3fa3abfe", "count": 3}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000003", "root": "0xcb305ccda4331eb3fd9e17b81a5a0b336fb37a33f927698e9fb0604e534c6a01", "count": 4}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000004", "root": "0xa377a6262d3bae7be0ce09c2cc9f767b0f31848c268a4bdc12b63a451bb97281", "count": 5}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000005", "root": "0x440213f4dff167e3f5c655fbb6a3327af3512affed50ce3c1a3f139458a8a6d1", "count": 6}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000006", "root": "0xdd716d2905f2881005341ff1046ced5ee15cc63139716f56ed6be1d075c3f4a7", "count": 7}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000007", "root": "0xd6ebf96fcc3344fa755057b148162f95a93491bc6e8be756d06ec64df4df90fc", "count": 8}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000008", "root": "0x8b3bf2c95f3d0f941c109adfc3b652fadfeaf6f34be52524360a001cb151b5c9", "count": 9}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000009", "root": "0x74a5712654eccd015c44aca31817fd8bee8da400ada986a78384ef3594f2d459", "count": 10}, + {"leaf": "0x000000000000000000000000000000000000000000000000000000000000000a", "root": "0x95dd1209b92cce04311dfc8670b03428408c4ff62beb389e71847971f73702fa", "count": 11}, + {"leaf": "0x000000000000000000000000000000000000000000000000000000000000000b", "root": "0x0a83f3b2a75e19b7255b1de379ea9a71aef9716a3aef20a86abe625f088bbebf", "count": 12}, + {"leaf": "0x000000000000000000000000000000000000000000000000000000000000000c", "root": "0x601ba73b45858be76c8d02799fd70a5e1713e04031aa3be6746f95a17c343173", "count": 13}, + {"leaf": "0x000000000000000000000000000000000000000000000000000000000000000d", "root": "0x93d741c47aa73e36d3c7697758843d6af02b10ed38785f367d1602c8638adb0d", "count": 14}, + {"leaf": "0x000000000000000000000000000000000000000000000000000000000000000e", "root": "0x578f0d0a9b8ed5a4f86181b7e479da7ad72576ba7d3f36a1b72516aa0900c8ac", "count": 15}, + {"leaf": "0x000000000000000000000000000000000000000000000000000000000000000f", "root": "0x995c30e6b58c6e00e06faf4b5c94a21eb820b9db7ad30703f8e3370c2af10c11", "count": 16}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000010", "root": "0x49fb7257be1e954c377dc2557f5ca3f6fc7002d213f2772ab6899000e465236c", "count": 17}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000011", "root": "0x06fee72550896c50e28b894c60a3132bfe670e5c7a77ab4bb6a8ffb4abcf9446", "count": 18}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000012", "root": "0xbba3a807e79d33c6506cd5ecb5d50417360f8be58139f6dbe2f02c92e4d82491", "count": 19}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000013", "root": "0x1243fbd4d21287dbdaa542fa18a6a172b60d1af2c517b242914bdf8d82a98293", "count": 20}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000014", "root": "0x02b7b57e407fbccb506ed3199922d6d9bd0f703a1919d388c76867399ed44286", "count": 21}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000015", "root": "0xa15e7890d8f860a2ef391f9f58602dec7027c19e8f380980f140bbb92a3e00ba", "count": 22}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000016", "root": "0x2cb7eff4deb9bf6bbb906792bc152f1e63759b30e7829bfb5f3257ee600303f5", "count": 23}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000017", "root": "0xb1b034b4784411dc6858a0da771acef31be60216be0520a7950d29f66aee1fc5", "count": 24}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000018", "root": "0x3b17098f521ca0719e144a12bb79fdc51a3bc70385b5c2ee46b5762aae741f4f", "count": 25}, + {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000019", "root": "0xd3e054489aa750d41938143011666a83e5e6b1477cce5ad612447059c2d8b939", "count": 26}, + {"leaf": "0x000000000000000000000000000000000000000000000000000000000000001a", "root": "0x6d15443ab2f39cce7fbe131843cdad6f27400eb179efb866569dd48baaf3ed4d", "count": 27}, + {"leaf": "0x000000000000000000000000000000000000000000000000000000000000001b", "root": "0xf9386ef40320c369185e48132f8fbf2f3e78d9598495dd342bcf4f41388d460d", "count": 28}, + {"leaf": "0x000000000000000000000000000000000000000000000000000000000000001c", "root": "0xb618ebe1f7675ef246a8cbb93519469076d5caacd4656330801537933e27b172", "count": 29}, + {"leaf": "0x000000000000000000000000000000000000000000000000000000000000001d", "root": "0x6c8c90b5aa967c98061a2dd09ea74dfb61fd9e86e308f14453e9e0ae991116de", "count": 30}, + {"leaf": "0x000000000000000000000000000000000000000000000000000000000000001e", "root": "0x06f51cfc733d71220d6e5b70a6b33a8d47a1ab55ac045fac75f26c762d7b29c9", "count": 31}, + {"leaf": "0x000000000000000000000000000000000000000000000000000000000000001f", "root": "0x82d1ddf8c6d986dee7fc6fa2d7120592d1dc5026b1bb349fcc9d5c73ac026f56", "count": 32} + ], + "canonical_zeros": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5", + "0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30", + "0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85", + "0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344", + "0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d", + "0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968", + "0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83", + "0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af", + "0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0", + "0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5", + "0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892", + "0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c", + "0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb", + "0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc", + "0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2", + "0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f", + "0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a", + "0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0", + "0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0", + "0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2", + "0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9", + "0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377", + "0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652", + "0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef", + "0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d", + "0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0", + "0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e", + "0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e", + "0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322", + "0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735", + "0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9" + ] +} \ No newline at end of file diff --git a/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol b/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol new file mode 100644 index 000000000..4e9feef35 --- /dev/null +++ b/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "forge-std/Test.sol"; +import "../src/DepositContractBase.sol"; + +/** + * @title MMRTestVectors + * @notice Test contract that generates test vectors for verifying compatibility + * between Solidity's DepositContractBase and Miden's MMR Frontier implementation. + * + * Run with: forge test -vv --match-test test_generateVectors + * + * The output can be compared against the Rust KeccakMmrFrontier32 implementation + * in crates/miden-testing/tests/agglayer/mmr_frontier.rs + */ +contract MMRTestVectors is Test, DepositContractBase { + + /** + * @notice Generates test vectors by adding leaves and capturing roots. + * These vectors should match the Miden implementation. + */ + function test_generateVectors() public { + console.log("=== MMR Frontier Test Vectors ==="); + console.log("Source: DepositContractBase.sol @ e468f9b0967334403069aa650d9f1164b1731ebb"); + console.log(""); + + for (uint256 i = 0; i < 32; i++) { + // Create leaf as left-padded 32-byte representation of i + bytes32 leaf = bytes32(i); + + _addLeaf(leaf); + bytes32 root = getRoot(); + + console.log("--- Leaf %d ---", i); + console.log("leaf:"); + console.logBytes32(leaf); + console.log("root_after:"); + console.logBytes32(root); + console.log("deposit_count: %d", depositCount); + console.log(""); + } + } + + /** + * @notice Generates the canonical zeros used in the MMR. + * ZERO_0 = 0x0...0 (32 zero bytes) + * ZERO_n = keccak256(ZERO_{n-1} || ZERO_{n-1}) + * + * These should match the values in canonical_zeros.masm + */ + function test_generateCanonicalZeros() public pure { + console.log("=== Canonical Zeros ==="); + console.log("ZERO_n = keccak256(ZERO_{n-1} || ZERO_{n-1})"); + console.log(""); + + bytes32 zero = bytes32(0); + + for (uint256 height = 0; height < 32; height++) { + console.log("ZERO_%d:", height); + console.logBytes32(zero); + + // Compute next zero: hash(zero || zero) + zero = keccak256(abi.encodePacked(zero, zero)); + } + } + + /** + * @notice Test with zero leaves only - the root should remain constant + * as "empty MMR root" regardless of how many zero leaves are added. + */ + function test_zeroLeavesRoot() public { + console.log("=== Zero Leaves Test ==="); + console.log("Adding 32 zero leaves, root should be consistent with empty tree"); + console.log(""); + + for (uint256 i = 0; i < 32; i++) { + bytes32 zeroLeaf = bytes32(0); + _addLeaf(zeroLeaf); + bytes32 root = getRoot(); + + console.log("After %d zero leaves:", i + 1); + console.logBytes32(root); + } + } + + /** + * @notice Outputs vectors in JSON format and saves to file + * Run with: forge test -vv --match-test test_generateVectorsJSON + * Output file: test-vectors/mmr_frontier_vectors.json + */ + function test_generateVectorsJSON() public { + string memory json = "{\n"; + json = string.concat(json, ' "description": "Test vectors from DepositContractBase.sol",\n'); + json = string.concat(json, ' "source_commit": "e468f9b0967334403069aa650d9f1164b1731ebb",\n'); + json = string.concat(json, ' "vectors": [\n'); + + for (uint256 i = 0; i < 32; i++) { + bytes32 leaf = bytes32(i); + _addLeaf(leaf); + bytes32 root = getRoot(); + + // Build JSON object for this vector + string memory vectorJson = string.concat( + ' {"leaf": "', vm.toString(leaf), + '", "root": "', vm.toString(root), + '", "count": ', vm.toString(depositCount), "}" + ); + + if (i < 31) { + json = string.concat(json, vectorJson, ",\n"); + } else { + json = string.concat(json, vectorJson, "\n"); + } + } + + json = string.concat(json, " ],\n"); + + // Add canonical zeros + json = string.concat(json, ' "canonical_zeros": [\n'); + bytes32 zero = bytes32(0); + for (uint256 height = 0; height < 32; height++) { + if (height < 31) { + json = string.concat(json, ' "', vm.toString(zero), '",\n'); + } else { + json = string.concat(json, ' "', vm.toString(zero), '"\n'); + } + zero = keccak256(abi.encodePacked(zero, zero)); + } + json = string.concat(json, " ]\n}"); + + // Print to console + console.log(json); + + // Save to file + string memory outputPath = "test-vectors/mmr_frontier_vectors.json"; + vm.writeFile(outputPath, json); + console.log("\nSaved to:", outputPath); + } +} diff --git a/crates/miden-testing/tests/agglayer/mmr_frontier.rs b/crates/miden-testing/tests/agglayer/mmr_frontier.rs index b4e800703..e596191eb 100644 --- a/crates/miden-testing/tests/agglayer/mmr_frontier.rs +++ b/crates/miden-testing/tests/agglayer/mmr_frontier.rs @@ -128,6 +128,129 @@ async fn test_check_empty_mmr_root() -> anyhow::Result<()> { Ok(()) } +// SOLIDITY COMPATIBILITY TESTS +// ================================================================================================ +// These tests verify that the Rust KeccakMmrFrontier32 implementation produces identical +// results to the Solidity DepositContractBase.sol implementation. +// Test vectors generated from: https://github.com/agglayer/agglayer-contracts +// Commit: e468f9b0967334403069aa650d9f1164b1731ebb + +/// Test vectors from Solidity DepositContractBase.sol +/// Each tuple is (leaf_hex, expected_root_hex, expected_count) +const SOLIDITY_TEST_VECTORS: &[(&str, &str, u32)] = &[ + ("0x0000000000000000000000000000000000000000000000000000000000000000", "0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757", 1), + ("0x0000000000000000000000000000000000000000000000000000000000000001", "0x4a90a2c108a29b7755a0a915b9bb950233ce71f8a01859350d7b73cc56f57a62", 2), + ("0x0000000000000000000000000000000000000000000000000000000000000002", "0x2757cc260a62cc7c7708c387ea99f2a6bb5f034ed00da845734bec4d3fa3abfe", 3), + ("0x0000000000000000000000000000000000000000000000000000000000000003", "0xcb305ccda4331eb3fd9e17b81a5a0b336fb37a33f927698e9fb0604e534c6a01", 4), + ("0x0000000000000000000000000000000000000000000000000000000000000004", "0xa377a6262d3bae7be0ce09c2cc9f767b0f31848c268a4bdc12b63a451bb97281", 5), + ("0x0000000000000000000000000000000000000000000000000000000000000005", "0x440213f4dff167e3f5c655fbb6a3327af3512affed50ce3c1a3f139458a8a6d1", 6), + ("0x0000000000000000000000000000000000000000000000000000000000000006", "0xdd716d2905f2881005341ff1046ced5ee15cc63139716f56ed6be1d075c3f4a7", 7), + ("0x0000000000000000000000000000000000000000000000000000000000000007", "0xd6ebf96fcc3344fa755057b148162f95a93491bc6e8be756d06ec64df4df90fc", 8), + ("0x0000000000000000000000000000000000000000000000000000000000000008", "0x8b3bf2c95f3d0f941c109adfc3b652fadfeaf6f34be52524360a001cb151b5c9", 9), + ("0x0000000000000000000000000000000000000000000000000000000000000009", "0x74a5712654eccd015c44aca31817fd8bee8da400ada986a78384ef3594f2d459", 10), + ("0x000000000000000000000000000000000000000000000000000000000000000a", "0x95dd1209b92cce04311dfc8670b03428408c4ff62beb389e71847971f73702fa", 11), + ("0x000000000000000000000000000000000000000000000000000000000000000b", "0x0a83f3b2a75e19b7255b1de379ea9a71aef9716a3aef20a86abe625f088bbebf", 12), + ("0x000000000000000000000000000000000000000000000000000000000000000c", "0x601ba73b45858be76c8d02799fd70a5e1713e04031aa3be6746f95a17c343173", 13), + ("0x000000000000000000000000000000000000000000000000000000000000000d", "0x93d741c47aa73e36d3c7697758843d6af02b10ed38785f367d1602c8638adb0d", 14), + ("0x000000000000000000000000000000000000000000000000000000000000000e", "0x578f0d0a9b8ed5a4f86181b7e479da7ad72576ba7d3f36a1b72516aa0900c8ac", 15), + ("0x000000000000000000000000000000000000000000000000000000000000000f", "0x995c30e6b58c6e00e06faf4b5c94a21eb820b9db7ad30703f8e3370c2af10c11", 16), + ("0x0000000000000000000000000000000000000000000000000000000000000010", "0x49fb7257be1e954c377dc2557f5ca3f6fc7002d213f2772ab6899000e465236c", 17), + ("0x0000000000000000000000000000000000000000000000000000000000000011", "0x06fee72550896c50e28b894c60a3132bfe670e5c7a77ab4bb6a8ffb4abcf9446", 18), + ("0x0000000000000000000000000000000000000000000000000000000000000012", "0xbba3a807e79d33c6506cd5ecb5d50417360f8be58139f6dbe2f02c92e4d82491", 19), + ("0x0000000000000000000000000000000000000000000000000000000000000013", "0x1243fbd4d21287dbdaa542fa18a6a172b60d1af2c517b242914bdf8d82a98293", 20), + ("0x0000000000000000000000000000000000000000000000000000000000000014", "0x02b7b57e407fbccb506ed3199922d6d9bd0f703a1919d388c76867399ed44286", 21), + ("0x0000000000000000000000000000000000000000000000000000000000000015", "0xa15e7890d8f860a2ef391f9f58602dec7027c19e8f380980f140bbb92a3e00ba", 22), + ("0x0000000000000000000000000000000000000000000000000000000000000016", "0x2cb7eff4deb9bf6bbb906792bc152f1e63759b30e7829bfb5f3257ee600303f5", 23), + ("0x0000000000000000000000000000000000000000000000000000000000000017", "0xb1b034b4784411dc6858a0da771acef31be60216be0520a7950d29f66aee1fc5", 24), + ("0x0000000000000000000000000000000000000000000000000000000000000018", "0x3b17098f521ca0719e144a12bb79fdc51a3bc70385b5c2ee46b5762aae741f4f", 25), + ("0x0000000000000000000000000000000000000000000000000000000000000019", "0xd3e054489aa750d41938143011666a83e5e6b1477cce5ad612447059c2d8b939", 26), + ("0x000000000000000000000000000000000000000000000000000000000000001a", "0x6d15443ab2f39cce7fbe131843cdad6f27400eb179efb866569dd48baaf3ed4d", 27), + ("0x000000000000000000000000000000000000000000000000000000000000001b", "0xf9386ef40320c369185e48132f8fbf2f3e78d9598495dd342bcf4f41388d460d", 28), + ("0x000000000000000000000000000000000000000000000000000000000000001c", "0xb618ebe1f7675ef246a8cbb93519469076d5caacd4656330801537933e27b172", 29), + ("0x000000000000000000000000000000000000000000000000000000000000001d", "0x6c8c90b5aa967c98061a2dd09ea74dfb61fd9e86e308f14453e9e0ae991116de", 30), + ("0x000000000000000000000000000000000000000000000000000000000000001e", "0x06f51cfc733d71220d6e5b70a6b33a8d47a1ab55ac045fac75f26c762d7b29c9", 31), + ("0x000000000000000000000000000000000000000000000000000000000000001f", "0x82d1ddf8c6d986dee7fc6fa2d7120592d1dc5026b1bb349fcc9d5c73ac026f56", 32), +]; + +/// Canonical zeros from Solidity DepositContractBase.sol +/// ZERO_n = keccak256(ZERO_{n-1} || ZERO_{n-1}), where ZERO_0 = 0x00...00 +const SOLIDITY_CANONICAL_ZEROS: &[&str] = &[ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5", + "0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30", + "0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85", + "0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344", + "0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d", + "0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968", + "0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83", + "0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af", + "0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0", + "0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5", + "0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892", + "0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c", + "0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb", + "0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc", + "0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2", + "0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f", + "0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a", + "0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0", + "0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0", + "0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2", + "0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9", + "0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377", + "0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652", + "0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef", + "0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d", + "0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0", + "0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e", + "0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e", + "0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322", + "0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735", + "0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9", +]; + +/// Verifies that the Rust KeccakMmrFrontier32 produces the same canonical zeros as Solidity. +#[test] +fn test_solidity_canonical_zeros_compatibility() { + for (height, expected_hex) in SOLIDITY_CANONICAL_ZEROS.iter().enumerate() { + let expected = Keccak256Digest::try_from(*expected_hex).unwrap(); + let actual = CANONICAL_ZEROS_32[height]; + + assert_eq!( + actual, expected, + "Canonical zero mismatch at height {}: expected {}, got {:?}", + height, expected_hex, actual + ); + } +} + +/// Verifies that the Rust KeccakMmrFrontier32 produces the same roots as Solidity's +/// DepositContractBase after adding each leaf. +#[test] +fn test_solidity_mmr_frontier_compatibility() { + let mut mmr_frontier = KeccakMmrFrontier32::<32>::new(); + + for (leaf_hex, expected_root_hex, expected_count) in SOLIDITY_TEST_VECTORS.iter() { + let leaf = Keccak256Digest::try_from(*leaf_hex).unwrap(); + let expected_root = Keccak256Digest::try_from(*expected_root_hex).unwrap(); + + let actual_root = mmr_frontier.append_and_update_frontier(leaf); + let actual_count = mmr_frontier.num_leaves; + + assert_eq!( + actual_count, *expected_count, + "Leaf count mismatch after adding leaf {}: expected {}, got {}", + leaf_hex, expected_count, actual_count + ); + + assert_eq!( + actual_root, expected_root, + "Root mismatch after adding leaf {} (count={}): expected {}, got {:?}", + leaf_hex, expected_count, expected_root_hex, actual_root + ); + } +} + // HELPER FUNCTIONS // ================================================================================================ From 3b1ff177856e77998e56b16844ff898a729c9106 Mon Sep 17 00:00:00 2001 From: Marti Date: Mon, 19 Jan 2026 14:53:36 +0000 Subject: [PATCH 02/13] feat: use agglayer submodule instead of copying source files --- .gitmodules | 3 + Makefile | 6 + .../miden-agglayer/solidity-compat/.gitignore | 1 - .../miden-agglayer/solidity-compat/README.md | 11 +- .../solidity-compat/foundry.lock | 3 + .../solidity-compat/foundry.toml | 4 + .../solidity-compat/lib/agglayer-contracts | 1 + .../solidity-compat/lib/forge-std | 2 +- .../src/DepositContractBase.sol | 135 ------------------ .../solidity-compat/src/Hashes.sol | 20 --- .../solidity-compat/test/MMRTestVectors.t.sol | 2 +- 11 files changed, 27 insertions(+), 161 deletions(-) create mode 160000 crates/miden-agglayer/solidity-compat/lib/agglayer-contracts delete mode 100644 crates/miden-agglayer/solidity-compat/src/DepositContractBase.sol delete mode 100644 crates/miden-agglayer/solidity-compat/src/Hashes.sol diff --git a/.gitmodules b/.gitmodules index 1b798beac..b02c269a3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "crates/miden-agglayer/solidity-compat/lib/forge-std"] path = crates/miden-agglayer/solidity-compat/lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "crates/miden-agglayer/solidity-compat/lib/agglayer-contracts"] + path = crates/miden-agglayer/solidity-compat/lib/agglayer-contracts + url = https://github.com/agglayer/agglayer-contracts diff --git a/Makefile b/Makefile index 10f3f8823..eacbfb857 100644 --- a/Makefile +++ b/Makefile @@ -129,6 +129,12 @@ build-no-std: ## Build without the standard library build-no-std-testing: ## Build without the standard library. Includes the `testing` feature $(BUILD_GENERATED_FILES_IN_SRC) cargo build --no-default-features --target wasm32-unknown-unknown --workspace --exclude bench-transaction --features testing +# --- test vectors -------------------------------------------------------------------------------- + +.PHONY: generate-solidity-test-vectors +generate-solidity-test-vectors: ## Regenerate Solidity MMR test vectors using Foundry + cd crates/miden-agglayer/solidity-compat && forge test -vv --match-test test_generateVectorsJSON + # --- benchmarking -------------------------------------------------------------------------------- .PHONY: bench-tx diff --git a/crates/miden-agglayer/solidity-compat/.gitignore b/crates/miden-agglayer/solidity-compat/.gitignore index 4a212ae49..16fe32f77 100644 --- a/crates/miden-agglayer/solidity-compat/.gitignore +++ b/crates/miden-agglayer/solidity-compat/.gitignore @@ -1,7 +1,6 @@ # Foundry artifacts /out/ /cache/ -/lib/ # Foundry broadcast files /broadcast/ diff --git a/crates/miden-agglayer/solidity-compat/README.md b/crates/miden-agglayer/solidity-compat/README.md index 2678ba8c8..d2aa05b8b 100644 --- a/crates/miden-agglayer/solidity-compat/README.md +++ b/crates/miden-agglayer/solidity-compat/README.md @@ -21,7 +21,13 @@ foundryup ## Running Tests -From this directory: +From the repository root, you can regenerate the test vectors with: + +```bash +make generate-solidity-test-vectors +``` + +Or from this directory: ```bash # Install dependencies (first time only) @@ -56,6 +62,5 @@ and outputs the root after each addition. ## Source Files -- `src/DepositContractBase.sol` - Copy from agglayer-contracts @ e468f9b0967334403069aa650d9f1164b1731ebb -- `src/Hashes.sol` - Keccak256 hashing utility (based on OpenZeppelin) +- `lib/agglayer-contracts/` - Git submodule of [agglayer-contracts](https://github.com/agglayer/agglayer-contracts) @ e468f9b0967334403069aa650d9f1164b1731ebb - `test/MMRTestVectors.t.sol` - Test vector generation diff --git a/crates/miden-agglayer/solidity-compat/foundry.lock b/crates/miden-agglayer/solidity-compat/foundry.lock index d0c0159b3..8aa165ad7 100644 --- a/crates/miden-agglayer/solidity-compat/foundry.lock +++ b/crates/miden-agglayer/solidity-compat/foundry.lock @@ -1,4 +1,7 @@ { + "lib/agglayer-contracts": { + "rev": "e468f9b0967334403069aa650d9f1164b1731ebb" + }, "lib/forge-std": { "tag": { "name": "v1.14.0", diff --git a/crates/miden-agglayer/solidity-compat/foundry.toml b/crates/miden-agglayer/solidity-compat/foundry.toml index c89e0ae6b..edf5099be 100644 --- a/crates/miden-agglayer/solidity-compat/foundry.toml +++ b/crates/miden-agglayer/solidity-compat/foundry.toml @@ -4,6 +4,10 @@ out = "out" libs = ["lib"] solc = "0.8.20" +remappings = [ + "@agglayer/=lib/agglayer-contracts/contracts/" +] + # Emit extra output for test vector generation ffi = false verbosity = 2 diff --git a/crates/miden-agglayer/solidity-compat/lib/agglayer-contracts b/crates/miden-agglayer/solidity-compat/lib/agglayer-contracts new file mode 160000 index 000000000..e468f9b09 --- /dev/null +++ b/crates/miden-agglayer/solidity-compat/lib/agglayer-contracts @@ -0,0 +1 @@ +Subproject commit e468f9b0967334403069aa650d9f1164b1731ebb diff --git a/crates/miden-agglayer/solidity-compat/lib/forge-std b/crates/miden-agglayer/solidity-compat/lib/forge-std index 1801b0541..f61e4dd13 160000 --- a/crates/miden-agglayer/solidity-compat/lib/forge-std +++ b/crates/miden-agglayer/solidity-compat/lib/forge-std @@ -1 +1 @@ -Subproject commit 1801b0541f4fda118a10798fd3486bb7051c5dd6 +Subproject commit f61e4dd133379a4536a54ee57a808c9c00019b60 diff --git a/crates/miden-agglayer/solidity-compat/src/DepositContractBase.sol b/crates/miden-agglayer/solidity-compat/src/DepositContractBase.sol deleted file mode 100644 index a197ce180..000000000 --- a/crates/miden-agglayer/solidity-compat/src/DepositContractBase.sol +++ /dev/null @@ -1,135 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -// Source: https://github.com/agglayer/agglayer-contracts/blob/e468f9b0967334403069aa650d9f1164b1731ebb/contracts/v2/lib/DepositContractBase.sol -pragma solidity ^0.8.20; - -import "./Hashes.sol"; - -/** - * This contract will be used as a helper for all the sparse merkle tree related functions - * Based on the implementation of the deposit eth2.0 contract https://github.com/ethereum/consensus-specs/blob/dev/solidity_deposit_contract/deposit_contract.sol - */ -contract DepositContractBase { - /** - * @dev Thrown when the merkle tree is full - */ - error MerkleTreeFull(); - - // Merkle tree levels - uint256 internal constant _DEPOSIT_CONTRACT_TREE_DEPTH = 32; - - // This ensures `depositCount` will fit into 32-bits - uint256 internal constant _MAX_DEPOSIT_COUNT = - 2 ** _DEPOSIT_CONTRACT_TREE_DEPTH - 1; - - // Branch array which contains the necessary sibilings to compute the next root when a new - // leaf is inserted - bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] internal _branch; - - // Counter of current deposits - uint256 public depositCount; - - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - */ - /// @custom:oz-renamed-from _gap - uint256[10] private __gap; - - /** - * @notice Computes and returns the merkle root - */ - function getRoot() public view virtual returns (bytes32) { - bytes32 node; - uint256 size = depositCount; - bytes32 currentZeroHashHeight = 0; - - for ( - uint256 height = 0; - height < _DEPOSIT_CONTRACT_TREE_DEPTH; - height++ - ) { - if (((size >> height) & 1) == 1) - node = Hashes.efficientKeccak256(_branch[height], node); - else node = Hashes.efficientKeccak256(node, currentZeroHashHeight); - - currentZeroHashHeight = Hashes.efficientKeccak256( - currentZeroHashHeight, - currentZeroHashHeight - ); - } - return node; - } - - /** - * @notice Add a new leaf to the merkle tree - * @param leaf Leaf - */ - function _addLeaf(bytes32 leaf) internal { - bytes32 node = leaf; - - // Avoid overflowing the Merkle tree (and prevent edge case in computing `_branch`) - if (depositCount >= _MAX_DEPOSIT_COUNT) { - revert MerkleTreeFull(); - } - - // Add deposit data root to Merkle tree (update a single `_branch` node) - uint256 size = ++depositCount; - for ( - uint256 height = 0; - height < _DEPOSIT_CONTRACT_TREE_DEPTH; - height++ - ) { - if (((size >> height) & 1) == 1) { - _branch[height] = node; - return; - } - node = Hashes.efficientKeccak256(_branch[height], node); - } - // As the loop should always end prematurely with the `return` statement, - // this code should be unreachable. We assert `false` just to be safe. - assert(false); - } - - /** - * @notice Verify merkle proof - * @param leafHash Leaf hash - * @param smtProof Smt proof - * @param index Index of the leaf - * @param root Merkle root - */ - function verifyMerkleProof( - bytes32 leafHash, - bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProof, - uint32 index, - bytes32 root - ) public pure returns (bool) { - return calculateRoot(leafHash, smtProof, index) == root; - } - - /** - * @notice Calculate root from merkle proof - * @param leafHash Leaf hash - * @param smtProof Smt proof - * @param index Index of the leaf - */ - function calculateRoot( - bytes32 leafHash, - bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProof, - uint32 index - ) public pure returns (bytes32) { - bytes32 node = leafHash; - - // Compute root - for ( - uint256 height = 0; - height < _DEPOSIT_CONTRACT_TREE_DEPTH; - height++ - ) { - if (((index >> height) & 1) == 1) - node = Hashes.efficientKeccak256(smtProof[height], node); - else node = Hashes.efficientKeccak256(node, smtProof[height]); - } - - return node; - } -} diff --git a/crates/miden-agglayer/solidity-compat/src/Hashes.sol b/crates/miden-agglayer/solidity-compat/src/Hashes.sol deleted file mode 100644 index 22aaab6ff..000000000 --- a/crates/miden-agglayer/solidity-compat/src/Hashes.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT -// Based on OpenZeppelin's Hashes.sol -pragma solidity ^0.8.20; - -/** - * @dev Library of hashing functions used by the agglayer contracts. - */ -library Hashes { - /** - * @dev Computes keccak256(abi.encode(a, b)) more efficiently using assembly. - * This is equivalent to keccak256(a || b) where || is concatenation. - */ - function efficientKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32 value) { - assembly ("memory-safe") { - mstore(0x00, a) - mstore(0x20, b) - value := keccak256(0x00, 0x40) - } - } -} diff --git a/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol b/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol index 4e9feef35..d5ac2b820 100644 --- a/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol +++ b/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import "forge-std/Test.sol"; -import "../src/DepositContractBase.sol"; +import "@agglayer/v2/lib/DepositContractBase.sol"; /** * @title MMRTestVectors From a5517903ed90327a81f97c3de489a92e90752b69 Mon Sep 17 00:00:00 2001 From: Marti Date: Mon, 19 Jan 2026 15:05:57 +0000 Subject: [PATCH 03/13] chore: use generated test vectors in compat tests --- Cargo.lock | 2 + crates/miden-testing/Cargo.toml | 2 + .../tests/agglayer/mmr_frontier.rs | 119 ++++++------------ 3 files changed, 40 insertions(+), 83 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 08afafcf4..03cf4aaee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1770,6 +1770,8 @@ dependencies = [ "rand", "rand_chacha", "rstest", + "serde", + "serde_json", "tokio", "winter-rand-utils", "winterfell", diff --git a/crates/miden-testing/Cargo.toml b/crates/miden-testing/Cargo.toml index ebaa0c33b..d25df1dcc 100644 --- a/crates/miden-testing/Cargo.toml +++ b/crates/miden-testing/Cargo.toml @@ -45,5 +45,7 @@ miden-crypto = { workspace = true } miden-protocol = { features = ["std"], workspace = true } primitive-types = { workspace = true } rstest = { workspace = true } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } tokio = { features = ["macros", "rt"], workspace = true } winter-rand-utils = { version = "0.13" } diff --git a/crates/miden-testing/tests/agglayer/mmr_frontier.rs b/crates/miden-testing/tests/agglayer/mmr_frontier.rs index e596191eb..f7acba63a 100644 --- a/crates/miden-testing/tests/agglayer/mmr_frontier.rs +++ b/crates/miden-testing/tests/agglayer/mmr_frontier.rs @@ -7,6 +7,7 @@ use miden_protocol::Felt; use miden_protocol::utils::sync::LazyLock; use miden_standards::code_builder::CodeBuilder; use miden_testing::TransactionContextBuilder; +use serde::Deserialize; // KECCAK MMR FRONTIER // ================================================================================================ @@ -133,87 +134,39 @@ async fn test_check_empty_mmr_root() -> anyhow::Result<()> { // These tests verify that the Rust KeccakMmrFrontier32 implementation produces identical // results to the Solidity DepositContractBase.sol implementation. // Test vectors generated from: https://github.com/agglayer/agglayer-contracts -// Commit: e468f9b0967334403069aa650d9f1164b1731ebb - -/// Test vectors from Solidity DepositContractBase.sol -/// Each tuple is (leaf_hex, expected_root_hex, expected_count) -const SOLIDITY_TEST_VECTORS: &[(&str, &str, u32)] = &[ - ("0x0000000000000000000000000000000000000000000000000000000000000000", "0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757", 1), - ("0x0000000000000000000000000000000000000000000000000000000000000001", "0x4a90a2c108a29b7755a0a915b9bb950233ce71f8a01859350d7b73cc56f57a62", 2), - ("0x0000000000000000000000000000000000000000000000000000000000000002", "0x2757cc260a62cc7c7708c387ea99f2a6bb5f034ed00da845734bec4d3fa3abfe", 3), - ("0x0000000000000000000000000000000000000000000000000000000000000003", "0xcb305ccda4331eb3fd9e17b81a5a0b336fb37a33f927698e9fb0604e534c6a01", 4), - ("0x0000000000000000000000000000000000000000000000000000000000000004", "0xa377a6262d3bae7be0ce09c2cc9f767b0f31848c268a4bdc12b63a451bb97281", 5), - ("0x0000000000000000000000000000000000000000000000000000000000000005", "0x440213f4dff167e3f5c655fbb6a3327af3512affed50ce3c1a3f139458a8a6d1", 6), - ("0x0000000000000000000000000000000000000000000000000000000000000006", "0xdd716d2905f2881005341ff1046ced5ee15cc63139716f56ed6be1d075c3f4a7", 7), - ("0x0000000000000000000000000000000000000000000000000000000000000007", "0xd6ebf96fcc3344fa755057b148162f95a93491bc6e8be756d06ec64df4df90fc", 8), - ("0x0000000000000000000000000000000000000000000000000000000000000008", "0x8b3bf2c95f3d0f941c109adfc3b652fadfeaf6f34be52524360a001cb151b5c9", 9), - ("0x0000000000000000000000000000000000000000000000000000000000000009", "0x74a5712654eccd015c44aca31817fd8bee8da400ada986a78384ef3594f2d459", 10), - ("0x000000000000000000000000000000000000000000000000000000000000000a", "0x95dd1209b92cce04311dfc8670b03428408c4ff62beb389e71847971f73702fa", 11), - ("0x000000000000000000000000000000000000000000000000000000000000000b", "0x0a83f3b2a75e19b7255b1de379ea9a71aef9716a3aef20a86abe625f088bbebf", 12), - ("0x000000000000000000000000000000000000000000000000000000000000000c", "0x601ba73b45858be76c8d02799fd70a5e1713e04031aa3be6746f95a17c343173", 13), - ("0x000000000000000000000000000000000000000000000000000000000000000d", "0x93d741c47aa73e36d3c7697758843d6af02b10ed38785f367d1602c8638adb0d", 14), - ("0x000000000000000000000000000000000000000000000000000000000000000e", "0x578f0d0a9b8ed5a4f86181b7e479da7ad72576ba7d3f36a1b72516aa0900c8ac", 15), - ("0x000000000000000000000000000000000000000000000000000000000000000f", "0x995c30e6b58c6e00e06faf4b5c94a21eb820b9db7ad30703f8e3370c2af10c11", 16), - ("0x0000000000000000000000000000000000000000000000000000000000000010", "0x49fb7257be1e954c377dc2557f5ca3f6fc7002d213f2772ab6899000e465236c", 17), - ("0x0000000000000000000000000000000000000000000000000000000000000011", "0x06fee72550896c50e28b894c60a3132bfe670e5c7a77ab4bb6a8ffb4abcf9446", 18), - ("0x0000000000000000000000000000000000000000000000000000000000000012", "0xbba3a807e79d33c6506cd5ecb5d50417360f8be58139f6dbe2f02c92e4d82491", 19), - ("0x0000000000000000000000000000000000000000000000000000000000000013", "0x1243fbd4d21287dbdaa542fa18a6a172b60d1af2c517b242914bdf8d82a98293", 20), - ("0x0000000000000000000000000000000000000000000000000000000000000014", "0x02b7b57e407fbccb506ed3199922d6d9bd0f703a1919d388c76867399ed44286", 21), - ("0x0000000000000000000000000000000000000000000000000000000000000015", "0xa15e7890d8f860a2ef391f9f58602dec7027c19e8f380980f140bbb92a3e00ba", 22), - ("0x0000000000000000000000000000000000000000000000000000000000000016", "0x2cb7eff4deb9bf6bbb906792bc152f1e63759b30e7829bfb5f3257ee600303f5", 23), - ("0x0000000000000000000000000000000000000000000000000000000000000017", "0xb1b034b4784411dc6858a0da771acef31be60216be0520a7950d29f66aee1fc5", 24), - ("0x0000000000000000000000000000000000000000000000000000000000000018", "0x3b17098f521ca0719e144a12bb79fdc51a3bc70385b5c2ee46b5762aae741f4f", 25), - ("0x0000000000000000000000000000000000000000000000000000000000000019", "0xd3e054489aa750d41938143011666a83e5e6b1477cce5ad612447059c2d8b939", 26), - ("0x000000000000000000000000000000000000000000000000000000000000001a", "0x6d15443ab2f39cce7fbe131843cdad6f27400eb179efb866569dd48baaf3ed4d", 27), - ("0x000000000000000000000000000000000000000000000000000000000000001b", "0xf9386ef40320c369185e48132f8fbf2f3e78d9598495dd342bcf4f41388d460d", 28), - ("0x000000000000000000000000000000000000000000000000000000000000001c", "0xb618ebe1f7675ef246a8cbb93519469076d5caacd4656330801537933e27b172", 29), - ("0x000000000000000000000000000000000000000000000000000000000000001d", "0x6c8c90b5aa967c98061a2dd09ea74dfb61fd9e86e308f14453e9e0ae991116de", 30), - ("0x000000000000000000000000000000000000000000000000000000000000001e", "0x06f51cfc733d71220d6e5b70a6b33a8d47a1ab55ac045fac75f26c762d7b29c9", 31), - ("0x000000000000000000000000000000000000000000000000000000000000001f", "0x82d1ddf8c6d986dee7fc6fa2d7120592d1dc5026b1bb349fcc9d5c73ac026f56", 32), -]; - -/// Canonical zeros from Solidity DepositContractBase.sol -/// ZERO_n = keccak256(ZERO_{n-1} || ZERO_{n-1}), where ZERO_0 = 0x00...00 -const SOLIDITY_CANONICAL_ZEROS: &[&str] = &[ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5", - "0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30", - "0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85", - "0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344", - "0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d", - "0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968", - "0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83", - "0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af", - "0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0", - "0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5", - "0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892", - "0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c", - "0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb", - "0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc", - "0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2", - "0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f", - "0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a", - "0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0", - "0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0", - "0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2", - "0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9", - "0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377", - "0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652", - "0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef", - "0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d", - "0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0", - "0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e", - "0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e", - "0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322", - "0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735", - "0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9", -]; +// Run `make generate-solidity-test-vectors` to regenerate the test vectors. + +/// Test vectors JSON embedded at compile time from the Foundry-generated file. +const TEST_VECTORS_JSON: &str = + include_str!("../../../miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json"); + +/// Deserialized test vectors from Solidity DepositContractBase.sol +#[derive(Debug, Deserialize)] +struct TestVectorsFile { + #[allow(dead_code)] + description: String, + #[allow(dead_code)] + source_commit: String, + vectors: Vec, + canonical_zeros: Vec, +} + +#[derive(Debug, Deserialize)] +struct TestVector { + leaf: String, + root: String, + count: u32, +} + +/// Lazily parsed test vectors from the JSON file. +static SOLIDITY_TEST_VECTORS: LazyLock = + LazyLock::new(|| serde_json::from_str(TEST_VECTORS_JSON).expect("Failed to parse test vectors JSON")); /// Verifies that the Rust KeccakMmrFrontier32 produces the same canonical zeros as Solidity. #[test] fn test_solidity_canonical_zeros_compatibility() { - for (height, expected_hex) in SOLIDITY_CANONICAL_ZEROS.iter().enumerate() { - let expected = Keccak256Digest::try_from(*expected_hex).unwrap(); + for (height, expected_hex) in SOLIDITY_TEST_VECTORS.canonical_zeros.iter().enumerate() { + let expected = Keccak256Digest::try_from(expected_hex.as_str()).unwrap(); let actual = CANONICAL_ZEROS_32[height]; assert_eq!( @@ -230,23 +183,23 @@ fn test_solidity_canonical_zeros_compatibility() { fn test_solidity_mmr_frontier_compatibility() { let mut mmr_frontier = KeccakMmrFrontier32::<32>::new(); - for (leaf_hex, expected_root_hex, expected_count) in SOLIDITY_TEST_VECTORS.iter() { - let leaf = Keccak256Digest::try_from(*leaf_hex).unwrap(); - let expected_root = Keccak256Digest::try_from(*expected_root_hex).unwrap(); + for vector in &SOLIDITY_TEST_VECTORS.vectors { + let leaf = Keccak256Digest::try_from(vector.leaf.as_str()).unwrap(); + let expected_root = Keccak256Digest::try_from(vector.root.as_str()).unwrap(); let actual_root = mmr_frontier.append_and_update_frontier(leaf); let actual_count = mmr_frontier.num_leaves; assert_eq!( - actual_count, *expected_count, + actual_count, vector.count, "Leaf count mismatch after adding leaf {}: expected {}, got {}", - leaf_hex, expected_count, actual_count + vector.leaf, vector.count, actual_count ); assert_eq!( actual_root, expected_root, "Root mismatch after adding leaf {} (count={}): expected {}, got {:?}", - leaf_hex, expected_count, expected_root_hex, actual_root + vector.leaf, vector.count, vector.root, actual_root ); } } From 9ebf857422d2f3848778dacd90afb3f6fa625fa9 Mon Sep 17 00:00:00 2001 From: Marti Date: Mon, 19 Jan 2026 15:13:34 +0000 Subject: [PATCH 04/13] chore: remove the human-readable fn --- .../miden-agglayer/solidity-compat/README.md | 5 +--- .../solidity-compat/test/MMRTestVectors.t.sol | 30 ++----------------- 2 files changed, 3 insertions(+), 32 deletions(-) diff --git a/crates/miden-agglayer/solidity-compat/README.md b/crates/miden-agglayer/solidity-compat/README.md index d2aa05b8b..c9929592d 100644 --- a/crates/miden-agglayer/solidity-compat/README.md +++ b/crates/miden-agglayer/solidity-compat/README.md @@ -33,14 +33,11 @@ Or from this directory: # Install dependencies (first time only) forge install -# Generate test vectors (human-readable) -forge test -vv --match-test test_generateVectors - # Generate canonical zeros (for verifying canonical_zeros.masm) forge test -vv --match-test test_generateCanonicalZeros # Generate JSON and save to file (test-vectors/mmr_frontier_vectors.json) -forge test -vv --match-test test_generateVectorsJSON +forge test -vv --match-test test_generateVectors # Run all tests forge test -vv diff --git a/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol b/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol index d5ac2b820..7e10b09b9 100644 --- a/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol +++ b/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol @@ -16,32 +16,6 @@ import "@agglayer/v2/lib/DepositContractBase.sol"; */ contract MMRTestVectors is Test, DepositContractBase { - /** - * @notice Generates test vectors by adding leaves and capturing roots. - * These vectors should match the Miden implementation. - */ - function test_generateVectors() public { - console.log("=== MMR Frontier Test Vectors ==="); - console.log("Source: DepositContractBase.sol @ e468f9b0967334403069aa650d9f1164b1731ebb"); - console.log(""); - - for (uint256 i = 0; i < 32; i++) { - // Create leaf as left-padded 32-byte representation of i - bytes32 leaf = bytes32(i); - - _addLeaf(leaf); - bytes32 root = getRoot(); - - console.log("--- Leaf %d ---", i); - console.log("leaf:"); - console.logBytes32(leaf); - console.log("root_after:"); - console.logBytes32(root); - console.log("deposit_count: %d", depositCount); - console.log(""); - } - } - /** * @notice Generates the canonical zeros used in the MMR. * ZERO_0 = 0x0...0 (32 zero bytes) @@ -86,10 +60,10 @@ contract MMRTestVectors is Test, DepositContractBase { /** * @notice Outputs vectors in JSON format and saves to file - * Run with: forge test -vv --match-test test_generateVectorsJSON + * Run with: forge test -vv --match-test test_generateVectors * Output file: test-vectors/mmr_frontier_vectors.json */ - function test_generateVectorsJSON() public { + function test_generateVectors() public { string memory json = "{\n"; json = string.concat(json, ' "description": "Test vectors from DepositContractBase.sol",\n'); json = string.concat(json, ' "source_commit": "e468f9b0967334403069aa650d9f1164b1731ebb",\n'); From 3f08e25861a8e369dac0bb7056bb660ef527a8ab Mon Sep 17 00:00:00 2001 From: Marti Date: Mon, 19 Jan 2026 15:23:51 +0000 Subject: [PATCH 05/13] chore: split test vectors into leaf<>root<>count paris; and canonical zeros --- Makefile | 3 +- .../miden-agglayer/solidity-compat/README.md | 15 ++-- .../test-vectors/canonical_zeros.json | 38 ++++++++++ .../test-vectors/mmr_frontier_vectors.json | 36 +--------- .../solidity-compat/test/MMRTestVectors.t.sol | 69 +++++++------------ .../tests/agglayer/mmr_frontier.rs | 40 ++++++++--- 6 files changed, 104 insertions(+), 97 deletions(-) create mode 100644 crates/miden-agglayer/solidity-compat/test-vectors/canonical_zeros.json diff --git a/Makefile b/Makefile index eacbfb857..6cdc6e329 100644 --- a/Makefile +++ b/Makefile @@ -133,7 +133,8 @@ build-no-std-testing: ## Build without the standard library. Includes the `testi .PHONY: generate-solidity-test-vectors generate-solidity-test-vectors: ## Regenerate Solidity MMR test vectors using Foundry - cd crates/miden-agglayer/solidity-compat && forge test -vv --match-test test_generateVectorsJSON + cd crates/miden-agglayer/solidity-compat && forge test -vv --match-test test_generateVectors + cd crates/miden-agglayer/solidity-compat && forge test -vv --match-test test_generateCanonicalZeros # --- benchmarking -------------------------------------------------------------------------------- diff --git a/crates/miden-agglayer/solidity-compat/README.md b/crates/miden-agglayer/solidity-compat/README.md index c9929592d..6a9063057 100644 --- a/crates/miden-agglayer/solidity-compat/README.md +++ b/crates/miden-agglayer/solidity-compat/README.md @@ -33,26 +33,31 @@ Or from this directory: # Install dependencies (first time only) forge install -# Generate canonical zeros (for verifying canonical_zeros.masm) +# Generate canonical zeros (test-vectors/canonical_zeros.json) forge test -vv --match-test test_generateCanonicalZeros -# Generate JSON and save to file (test-vectors/mmr_frontier_vectors.json) +# Generate MMR frontier vectors (test-vectors/mmr_frontier_vectors.json) forge test -vv --match-test test_generateVectors # Run all tests forge test -vv ``` +## Generated Files + +- `test-vectors/canonical_zeros.json` - Canonical zeros for each tree height (ZERO_n = keccak256(ZERO_{n-1} || ZERO_{n-1})) +- `test-vectors/mmr_frontier_vectors.json` - Leaf-root pairs after adding leaves 0..31 + ### Canonical Zeros -The `test_generateCanonicalZeros` output should match the constants in: -`crates/miden-agglayer/asm/lib/collections/canonical_zeros.masm` +The canonical zeros should match the constants in: +`crates/miden-agglayer/asm/bridge/canonical_zeros.masm` To convert Solidity hex to Miden u32 words: - Solidity: `0xabcdef...` (64 hex chars = 32 bytes) - Miden: 8 × u32 values, little-endian within each 4-byte chunk, reversed order -### Test Vectors +### MMR Frontier Vectors The `test_generateVectors` adds leaves `0, 1, 2, ...` (as left-padded 32-byte values) and outputs the root after each addition. diff --git a/crates/miden-agglayer/solidity-compat/test-vectors/canonical_zeros.json b/crates/miden-agglayer/solidity-compat/test-vectors/canonical_zeros.json new file mode 100644 index 000000000..e242c6102 --- /dev/null +++ b/crates/miden-agglayer/solidity-compat/test-vectors/canonical_zeros.json @@ -0,0 +1,38 @@ +{ + "description": "Canonical zeros from DepositContractBase.sol", + "source_commit": "e468f9b0967334403069aa650d9f1164b1731ebb", + "canonical_zeros": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5", + "0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30", + "0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85", + "0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344", + "0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d", + "0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968", + "0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83", + "0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af", + "0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0", + "0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5", + "0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892", + "0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c", + "0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb", + "0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc", + "0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2", + "0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f", + "0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a", + "0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0", + "0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0", + "0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2", + "0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9", + "0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377", + "0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652", + "0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef", + "0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d", + "0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0", + "0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e", + "0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e", + "0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322", + "0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735", + "0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9" + ] +} \ No newline at end of file diff --git a/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json b/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json index 9d86f1809..18935b2fb 100644 --- a/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json +++ b/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json @@ -1,5 +1,5 @@ { - "description": "Test vectors from DepositContractBase.sol", + "description": "MMR frontier test vectors from DepositContractBase.sol", "source_commit": "e468f9b0967334403069aa650d9f1164b1731ebb", "vectors": [ {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000000", "root": "0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757", "count": 1}, @@ -34,39 +34,5 @@ {"leaf": "0x000000000000000000000000000000000000000000000000000000000000001d", "root": "0x6c8c90b5aa967c98061a2dd09ea74dfb61fd9e86e308f14453e9e0ae991116de", "count": 30}, {"leaf": "0x000000000000000000000000000000000000000000000000000000000000001e", "root": "0x06f51cfc733d71220d6e5b70a6b33a8d47a1ab55ac045fac75f26c762d7b29c9", "count": 31}, {"leaf": "0x000000000000000000000000000000000000000000000000000000000000001f", "root": "0x82d1ddf8c6d986dee7fc6fa2d7120592d1dc5026b1bb349fcc9d5c73ac026f56", "count": 32} - ], - "canonical_zeros": [ - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5", - "0xb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d30", - "0x21ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85", - "0xe58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a19344", - "0x0eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d", - "0x887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968", - "0xffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f83", - "0x9867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756af", - "0xcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0", - "0xf9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5", - "0xf8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf892", - "0x3490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99c", - "0xc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb", - "0x5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8becc", - "0xda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d2", - "0x2733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981f", - "0xe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a", - "0x5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0", - "0xb46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0", - "0xc65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2", - "0xf4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd9", - "0x5a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e377", - "0x4df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652", - "0xcdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef", - "0x0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618d", - "0xb8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0", - "0x838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e", - "0x662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e", - "0x388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea322", - "0x93237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d735", - "0x8448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9" ] } \ No newline at end of file diff --git a/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol b/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol index 7e10b09b9..fbac04523 100644 --- a/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol +++ b/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol @@ -9,7 +9,7 @@ import "@agglayer/v2/lib/DepositContractBase.sol"; * @notice Test contract that generates test vectors for verifying compatibility * between Solidity's DepositContractBase and Miden's MMR Frontier implementation. * - * Run with: forge test -vv --match-test test_generateVectors + * Run with: forge test -vv --match-contract MMRTestVectors * * The output can be compared against the Rust KeccakMmrFrontier32 implementation * in crates/miden-testing/tests/agglayer/mmr_frontier.rs @@ -17,55 +17,45 @@ import "@agglayer/v2/lib/DepositContractBase.sol"; contract MMRTestVectors is Test, DepositContractBase { /** - * @notice Generates the canonical zeros used in the MMR. + * @notice Generates the canonical zeros and saves to JSON file. * ZERO_0 = 0x0...0 (32 zero bytes) * ZERO_n = keccak256(ZERO_{n-1} || ZERO_{n-1}) * - * These should match the values in canonical_zeros.masm + * Output file: test-vectors/canonical_zeros.json */ - function test_generateCanonicalZeros() public pure { - console.log("=== Canonical Zeros ==="); - console.log("ZERO_n = keccak256(ZERO_{n-1} || ZERO_{n-1})"); - console.log(""); + function test_generateCanonicalZeros() public { + string memory json = "{\n"; + json = string.concat(json, ' "description": "Canonical zeros from DepositContractBase.sol",\n'); + json = string.concat(json, ' "source_commit": "e468f9b0967334403069aa650d9f1164b1731ebb",\n'); + json = string.concat(json, ' "canonical_zeros": [\n'); bytes32 zero = bytes32(0); - for (uint256 height = 0; height < 32; height++) { - console.log("ZERO_%d:", height); - console.logBytes32(zero); - - // Compute next zero: hash(zero || zero) + if (height < 31) { + json = string.concat(json, ' "', vm.toString(zero), '",\n'); + } else { + json = string.concat(json, ' "', vm.toString(zero), '"\n'); + } zero = keccak256(abi.encodePacked(zero, zero)); } - } - - /** - * @notice Test with zero leaves only - the root should remain constant - * as "empty MMR root" regardless of how many zero leaves are added. - */ - function test_zeroLeavesRoot() public { - console.log("=== Zero Leaves Test ==="); - console.log("Adding 32 zero leaves, root should be consistent with empty tree"); - console.log(""); + json = string.concat(json, " ]\n}"); - for (uint256 i = 0; i < 32; i++) { - bytes32 zeroLeaf = bytes32(0); - _addLeaf(zeroLeaf); - bytes32 root = getRoot(); - - console.log("After %d zero leaves:", i + 1); - console.logBytes32(root); - } + // Print to console + console.log(json); + + // Save to file + string memory outputPath = "test-vectors/canonical_zeros.json"; + vm.writeFile(outputPath, json); + console.log("\nSaved to:", outputPath); } /** - * @notice Outputs vectors in JSON format and saves to file - * Run with: forge test -vv --match-test test_generateVectors + * @notice Generates MMR frontier vectors (leaf-root pairs) and saves to JSON file. * Output file: test-vectors/mmr_frontier_vectors.json */ function test_generateVectors() public { string memory json = "{\n"; - json = string.concat(json, ' "description": "Test vectors from DepositContractBase.sol",\n'); + json = string.concat(json, ' "description": "MMR frontier test vectors from DepositContractBase.sol",\n'); json = string.concat(json, ' "source_commit": "e468f9b0967334403069aa650d9f1164b1731ebb",\n'); json = string.concat(json, ' "vectors": [\n'); @@ -88,19 +78,6 @@ contract MMRTestVectors is Test, DepositContractBase { } } - json = string.concat(json, " ],\n"); - - // Add canonical zeros - json = string.concat(json, ' "canonical_zeros": [\n'); - bytes32 zero = bytes32(0); - for (uint256 height = 0; height < 32; height++) { - if (height < 31) { - json = string.concat(json, ' "', vm.toString(zero), '",\n'); - } else { - json = string.concat(json, ' "', vm.toString(zero), '"\n'); - } - zero = keccak256(abi.encodePacked(zero, zero)); - } json = string.concat(json, " ]\n}"); // Print to console diff --git a/crates/miden-testing/tests/agglayer/mmr_frontier.rs b/crates/miden-testing/tests/agglayer/mmr_frontier.rs index f7acba63a..2bf8b17b1 100644 --- a/crates/miden-testing/tests/agglayer/mmr_frontier.rs +++ b/crates/miden-testing/tests/agglayer/mmr_frontier.rs @@ -136,21 +136,34 @@ async fn test_check_empty_mmr_root() -> anyhow::Result<()> { // Test vectors generated from: https://github.com/agglayer/agglayer-contracts // Run `make generate-solidity-test-vectors` to regenerate the test vectors. -/// Test vectors JSON embedded at compile time from the Foundry-generated file. -const TEST_VECTORS_JSON: &str = +/// Canonical zeros JSON embedded at compile time from the Foundry-generated file. +const CANONICAL_ZEROS_JSON: &str = + include_str!("../../../miden-agglayer/solidity-compat/test-vectors/canonical_zeros.json"); + +/// MMR frontier vectors JSON embedded at compile time from the Foundry-generated file. +const MMR_FRONTIER_VECTORS_JSON: &str = include_str!("../../../miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json"); -/// Deserialized test vectors from Solidity DepositContractBase.sol +/// Deserialized canonical zeros from Solidity DepositContractBase.sol #[derive(Debug, Deserialize)] -struct TestVectorsFile { +struct CanonicalZerosFile { #[allow(dead_code)] description: String, #[allow(dead_code)] source_commit: String, - vectors: Vec, canonical_zeros: Vec, } +/// Deserialized MMR frontier vectors from Solidity DepositContractBase.sol +#[derive(Debug, Deserialize)] +struct MmrFrontierVectorsFile { + #[allow(dead_code)] + description: String, + #[allow(dead_code)] + source_commit: String, + vectors: Vec, +} + #[derive(Debug, Deserialize)] struct TestVector { leaf: String, @@ -158,14 +171,21 @@ struct TestVector { count: u32, } -/// Lazily parsed test vectors from the JSON file. -static SOLIDITY_TEST_VECTORS: LazyLock = - LazyLock::new(|| serde_json::from_str(TEST_VECTORS_JSON).expect("Failed to parse test vectors JSON")); +/// Lazily parsed canonical zeros from the JSON file. +static SOLIDITY_CANONICAL_ZEROS: LazyLock = LazyLock::new(|| { + serde_json::from_str(CANONICAL_ZEROS_JSON).expect("Failed to parse canonical zeros JSON") +}); + +/// Lazily parsed MMR frontier vectors from the JSON file. +static SOLIDITY_MMR_FRONTIER_VECTORS: LazyLock = LazyLock::new(|| { + serde_json::from_str(MMR_FRONTIER_VECTORS_JSON) + .expect("Failed to parse MMR frontier vectors JSON") +}); /// Verifies that the Rust KeccakMmrFrontier32 produces the same canonical zeros as Solidity. #[test] fn test_solidity_canonical_zeros_compatibility() { - for (height, expected_hex) in SOLIDITY_TEST_VECTORS.canonical_zeros.iter().enumerate() { + for (height, expected_hex) in SOLIDITY_CANONICAL_ZEROS.canonical_zeros.iter().enumerate() { let expected = Keccak256Digest::try_from(expected_hex.as_str()).unwrap(); let actual = CANONICAL_ZEROS_32[height]; @@ -183,7 +203,7 @@ fn test_solidity_canonical_zeros_compatibility() { fn test_solidity_mmr_frontier_compatibility() { let mut mmr_frontier = KeccakMmrFrontier32::<32>::new(); - for vector in &SOLIDITY_TEST_VECTORS.vectors { + for vector in &SOLIDITY_MMR_FRONTIER_VECTORS.vectors { let leaf = Keccak256Digest::try_from(vector.leaf.as_str()).unwrap(); let expected_root = Keccak256Digest::try_from(vector.root.as_str()).unwrap(); From b4d80e7f6dd35b8e4a542faa422bdadcbc56bf24 Mon Sep 17 00:00:00 2001 From: Marti Date: Mon, 19 Jan 2026 15:27:26 +0000 Subject: [PATCH 06/13] chore: remove unnecessary metadata --- .../test-vectors/canonical_zeros.json | 2 -- .../test-vectors/mmr_frontier_vectors.json | 2 -- .../solidity-compat/test/MMRTestVectors.t.sol | 4 ---- crates/miden-testing/tests/agglayer/mmr_frontier.rs | 12 ++---------- 4 files changed, 2 insertions(+), 18 deletions(-) diff --git a/crates/miden-agglayer/solidity-compat/test-vectors/canonical_zeros.json b/crates/miden-agglayer/solidity-compat/test-vectors/canonical_zeros.json index e242c6102..fbf41c38b 100644 --- a/crates/miden-agglayer/solidity-compat/test-vectors/canonical_zeros.json +++ b/crates/miden-agglayer/solidity-compat/test-vectors/canonical_zeros.json @@ -1,6 +1,4 @@ { - "description": "Canonical zeros from DepositContractBase.sol", - "source_commit": "e468f9b0967334403069aa650d9f1164b1731ebb", "canonical_zeros": [ "0x0000000000000000000000000000000000000000000000000000000000000000", "0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5", diff --git a/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json b/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json index 18935b2fb..13d9e10b8 100644 --- a/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json +++ b/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json @@ -1,6 +1,4 @@ { - "description": "MMR frontier test vectors from DepositContractBase.sol", - "source_commit": "e468f9b0967334403069aa650d9f1164b1731ebb", "vectors": [ {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000000", "root": "0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757", "count": 1}, {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000001", "root": "0x4a90a2c108a29b7755a0a915b9bb950233ce71f8a01859350d7b73cc56f57a62", "count": 2}, diff --git a/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol b/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol index fbac04523..2af315365 100644 --- a/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol +++ b/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol @@ -25,8 +25,6 @@ contract MMRTestVectors is Test, DepositContractBase { */ function test_generateCanonicalZeros() public { string memory json = "{\n"; - json = string.concat(json, ' "description": "Canonical zeros from DepositContractBase.sol",\n'); - json = string.concat(json, ' "source_commit": "e468f9b0967334403069aa650d9f1164b1731ebb",\n'); json = string.concat(json, ' "canonical_zeros": [\n'); bytes32 zero = bytes32(0); @@ -55,8 +53,6 @@ contract MMRTestVectors is Test, DepositContractBase { */ function test_generateVectors() public { string memory json = "{\n"; - json = string.concat(json, ' "description": "MMR frontier test vectors from DepositContractBase.sol",\n'); - json = string.concat(json, ' "source_commit": "e468f9b0967334403069aa650d9f1164b1731ebb",\n'); json = string.concat(json, ' "vectors": [\n'); for (uint256 i = 0; i < 32; i++) { diff --git a/crates/miden-testing/tests/agglayer/mmr_frontier.rs b/crates/miden-testing/tests/agglayer/mmr_frontier.rs index 2bf8b17b1..98ab952a5 100644 --- a/crates/miden-testing/tests/agglayer/mmr_frontier.rs +++ b/crates/miden-testing/tests/agglayer/mmr_frontier.rs @@ -147,25 +147,17 @@ const MMR_FRONTIER_VECTORS_JSON: &str = /// Deserialized canonical zeros from Solidity DepositContractBase.sol #[derive(Debug, Deserialize)] struct CanonicalZerosFile { - #[allow(dead_code)] - description: String, - #[allow(dead_code)] - source_commit: String, canonical_zeros: Vec, } /// Deserialized MMR frontier vectors from Solidity DepositContractBase.sol #[derive(Debug, Deserialize)] struct MmrFrontierVectorsFile { - #[allow(dead_code)] - description: String, - #[allow(dead_code)] - source_commit: String, - vectors: Vec, + vectors: Vec, } #[derive(Debug, Deserialize)] -struct TestVector { +struct MmrFrontierTestVector { leaf: String, root: String, count: u32, From 32783f2f71c122da77c83a5b244417799b1587ed Mon Sep 17 00:00:00 2001 From: Marti Date: Mon, 19 Jan 2026 15:31:03 +0000 Subject: [PATCH 07/13] chore: cleanup readme --- .../miden-agglayer/solidity-compat/README.md | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/crates/miden-agglayer/solidity-compat/README.md b/crates/miden-agglayer/solidity-compat/README.md index 6a9063057..f93f83b5b 100644 --- a/crates/miden-agglayer/solidity-compat/README.md +++ b/crates/miden-agglayer/solidity-compat/README.md @@ -2,13 +2,7 @@ This directory contains Foundry tests for generating test vectors to verify that the Miden MMR Frontier implementation is compatible with the Solidity -`DepositContractBase.sol` from [agglayer-contracts](https://github.com/agglayer/agglayer-contracts). - -## Purpose - -The Miden implementation of the Keccak-based MMR frontier (`mmr_frontier32_keccak.masm`) -must produce identical results to the Solidity implementation. These tests generate -reference test vectors that can be compared against the Rust/MASM implementation. +`DepositContractBase.sol` from [agglayer-contracts v2](https://github.com/agglayer/agglayer-contracts). ## Prerequisites @@ -19,9 +13,9 @@ curl -L https://foundry.paradigm.xyz | bash foundryup ``` -## Running Tests +## Generating Test Vectors -From the repository root, you can regenerate the test vectors with: +From the repository root, you can regenerate both canonical zeros and MMR frontier test vectors with: ```bash make generate-solidity-test-vectors @@ -38,9 +32,6 @@ forge test -vv --match-test test_generateCanonicalZeros # Generate MMR frontier vectors (test-vectors/mmr_frontier_vectors.json) forge test -vv --match-test test_generateVectors - -# Run all tests -forge test -vv ``` ## Generated Files @@ -53,16 +44,7 @@ forge test -vv The canonical zeros should match the constants in: `crates/miden-agglayer/asm/bridge/canonical_zeros.masm` -To convert Solidity hex to Miden u32 words: -- Solidity: `0xabcdef...` (64 hex chars = 32 bytes) -- Miden: 8 × u32 values, little-endian within each 4-byte chunk, reversed order - ### MMR Frontier Vectors The `test_generateVectors` adds leaves `0, 1, 2, ...` (as left-padded 32-byte values) and outputs the root after each addition. - -## Source Files - -- `lib/agglayer-contracts/` - Git submodule of [agglayer-contracts](https://github.com/agglayer/agglayer-contracts) @ e468f9b0967334403069aa650d9f1164b1731ebb -- `test/MMRTestVectors.t.sol` - Test vector generation From 46f912e042ae2dd3ee32c35c6ce69d53075f1409 Mon Sep 17 00:00:00 2001 From: Marti Date: Mon, 19 Jan 2026 15:34:31 +0000 Subject: [PATCH 08/13] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f6807f56..580f89511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - [BREAKING] Introduce `NoteAttachment` as part of `NoteMetadata` and remove `aux` and `execution_hint` ([#2249](https://github.com/0xMiden/miden-base/pull/2249), [#2252](https://github.com/0xMiden/miden-base/pull/2252), [#2260](https://github.com/0xMiden/miden-base/pull/2260)). - [BREAKING] Introduce `NoteAttachment` as part of `NoteMetadata` and remove `aux` and `execution_hint` ([#2249](https://github.com/0xMiden/miden-base/pull/2249), [#2252](https://github.com/0xMiden/miden-base/pull/2252), [#2260](https://github.com/0xMiden/miden-base/pull/2260), [#2268](https://github.com/0xMiden/miden-base/pull/2268), [#2279](https://github.com/0xMiden/miden-base/pull/2279)). - Introduce standard `NetworkAccountTarget` attachment for use in network transactions which replaces `NoteTag::NetworkAccount` ([#2257](https://github.com/0xMiden/miden-base/pull/2257)). +- Add a foundry test suite for verifying AggLayer contracts compatibility ([#2312](https://github.com/0xMiden/miden-base/pull/2312)). ### Changes From db457019ffd31b1928f8de262092cd5eb06d4474 Mon Sep 17 00:00:00 2001 From: Marti Date: Mon, 19 Jan 2026 15:40:28 +0000 Subject: [PATCH 09/13] chore: ignore submodule from typos check --- _typos.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/_typos.toml b/_typos.toml index b3babf7b5..3f86ad03f 100644 --- a/_typos.toml +++ b/_typos.toml @@ -1,2 +1,5 @@ [default] extend-ignore-identifiers-re = [".*1st.*", ".*2nd.*", ".*3rd.*"] + +[files] +extend-exclude = ["crates/miden-agglayer/solidity-compat/lib"] \ No newline at end of file From d105a95c42518696040f7788c29e3cc030d780c2 Mon Sep 17 00:00:00 2001 From: Marti Date: Mon, 19 Jan 2026 15:44:06 +0000 Subject: [PATCH 10/13] chore: exclude submodule from toml fmt --- .taplo.toml | 2 ++ _typos.toml | 2 +- crates/miden-agglayer/solidity-compat/foundry.toml | 10 ++++------ crates/miden-testing/Cargo.toml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.taplo.toml b/.taplo.toml index b735451f6..b10bcd148 100644 --- a/.taplo.toml +++ b/.taplo.toml @@ -1,3 +1,5 @@ +exclude = ["crates/miden-agglayer/solidity-compat/lib/*"] + [formatting] align_entries = true column_width = 120 diff --git a/_typos.toml b/_typos.toml index 3f86ad03f..6bc0c6f20 100644 --- a/_typos.toml +++ b/_typos.toml @@ -2,4 +2,4 @@ extend-ignore-identifiers-re = [".*1st.*", ".*2nd.*", ".*3rd.*"] [files] -extend-exclude = ["crates/miden-agglayer/solidity-compat/lib"] \ No newline at end of file +extend-exclude = ["crates/miden-agglayer/solidity-compat/lib"] diff --git a/crates/miden-agglayer/solidity-compat/foundry.toml b/crates/miden-agglayer/solidity-compat/foundry.toml index edf5099be..c22ad7e3f 100644 --- a/crates/miden-agglayer/solidity-compat/foundry.toml +++ b/crates/miden-agglayer/solidity-compat/foundry.toml @@ -1,15 +1,13 @@ [profile.default] -src = "src" -out = "out" libs = ["lib"] +out = "out" solc = "0.8.20" +src = "src" -remappings = [ - "@agglayer/=lib/agglayer-contracts/contracts/" -] +remappings = ["@agglayer/=lib/agglayer-contracts/contracts/"] # Emit extra output for test vector generation -ffi = false +ffi = false verbosity = 2 # Allow writing test vectors to file diff --git a/crates/miden-testing/Cargo.toml b/crates/miden-testing/Cargo.toml index d25df1dcc..6fdec7e70 100644 --- a/crates/miden-testing/Cargo.toml +++ b/crates/miden-testing/Cargo.toml @@ -45,7 +45,7 @@ miden-crypto = { workspace = true } miden-protocol = { features = ["std"], workspace = true } primitive-types = { workspace = true } rstest = { workspace = true } -serde = { version = "1.0", features = ["derive"] } +serde = { features = ["derive"], version = "1.0" } serde_json = { version = "1.0" } tokio = { features = ["macros", "rt"], workspace = true } winter-rand-utils = { version = "0.13" } From db5f8daf93d5a6963861d806f83760b483a2f98e Mon Sep 17 00:00:00 2001 From: Alexander John Lee <77119221+partylikeits1983@users.noreply.github.com> Date: Tue, 20 Jan 2026 06:13:52 -0500 Subject: [PATCH 11/13] refactor: use foundry cheatcode (#2314) --- .../test-vectors/mmr_frontier_vectors.json | 134 +++++++++++++----- .../solidity-compat/test/MMRTestVectors.t.sol | 71 ++++------ .../tests/agglayer/mmr_frontier.rs | 38 ++--- 3 files changed, 150 insertions(+), 93 deletions(-) diff --git a/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json b/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json index 13d9e10b8..e51ea4e4e 100644 --- a/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json +++ b/crates/miden-agglayer/solidity-compat/test-vectors/mmr_frontier_vectors.json @@ -1,36 +1,104 @@ { - "vectors": [ - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000000", "root": "0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757", "count": 1}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000001", "root": "0x4a90a2c108a29b7755a0a915b9bb950233ce71f8a01859350d7b73cc56f57a62", "count": 2}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000002", "root": "0x2757cc260a62cc7c7708c387ea99f2a6bb5f034ed00da845734bec4d3fa3abfe", "count": 3}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000003", "root": "0xcb305ccda4331eb3fd9e17b81a5a0b336fb37a33f927698e9fb0604e534c6a01", "count": 4}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000004", "root": "0xa377a6262d3bae7be0ce09c2cc9f767b0f31848c268a4bdc12b63a451bb97281", "count": 5}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000005", "root": "0x440213f4dff167e3f5c655fbb6a3327af3512affed50ce3c1a3f139458a8a6d1", "count": 6}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000006", "root": "0xdd716d2905f2881005341ff1046ced5ee15cc63139716f56ed6be1d075c3f4a7", "count": 7}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000007", "root": "0xd6ebf96fcc3344fa755057b148162f95a93491bc6e8be756d06ec64df4df90fc", "count": 8}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000008", "root": "0x8b3bf2c95f3d0f941c109adfc3b652fadfeaf6f34be52524360a001cb151b5c9", "count": 9}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000009", "root": "0x74a5712654eccd015c44aca31817fd8bee8da400ada986a78384ef3594f2d459", "count": 10}, - {"leaf": "0x000000000000000000000000000000000000000000000000000000000000000a", "root": "0x95dd1209b92cce04311dfc8670b03428408c4ff62beb389e71847971f73702fa", "count": 11}, - {"leaf": "0x000000000000000000000000000000000000000000000000000000000000000b", "root": "0x0a83f3b2a75e19b7255b1de379ea9a71aef9716a3aef20a86abe625f088bbebf", "count": 12}, - {"leaf": "0x000000000000000000000000000000000000000000000000000000000000000c", "root": "0x601ba73b45858be76c8d02799fd70a5e1713e04031aa3be6746f95a17c343173", "count": 13}, - {"leaf": "0x000000000000000000000000000000000000000000000000000000000000000d", "root": "0x93d741c47aa73e36d3c7697758843d6af02b10ed38785f367d1602c8638adb0d", "count": 14}, - {"leaf": "0x000000000000000000000000000000000000000000000000000000000000000e", "root": "0x578f0d0a9b8ed5a4f86181b7e479da7ad72576ba7d3f36a1b72516aa0900c8ac", "count": 15}, - {"leaf": "0x000000000000000000000000000000000000000000000000000000000000000f", "root": "0x995c30e6b58c6e00e06faf4b5c94a21eb820b9db7ad30703f8e3370c2af10c11", "count": 16}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000010", "root": "0x49fb7257be1e954c377dc2557f5ca3f6fc7002d213f2772ab6899000e465236c", "count": 17}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000011", "root": "0x06fee72550896c50e28b894c60a3132bfe670e5c7a77ab4bb6a8ffb4abcf9446", "count": 18}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000012", "root": "0xbba3a807e79d33c6506cd5ecb5d50417360f8be58139f6dbe2f02c92e4d82491", "count": 19}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000013", "root": "0x1243fbd4d21287dbdaa542fa18a6a172b60d1af2c517b242914bdf8d82a98293", "count": 20}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000014", "root": "0x02b7b57e407fbccb506ed3199922d6d9bd0f703a1919d388c76867399ed44286", "count": 21}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000015", "root": "0xa15e7890d8f860a2ef391f9f58602dec7027c19e8f380980f140bbb92a3e00ba", "count": 22}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000016", "root": "0x2cb7eff4deb9bf6bbb906792bc152f1e63759b30e7829bfb5f3257ee600303f5", "count": 23}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000017", "root": "0xb1b034b4784411dc6858a0da771acef31be60216be0520a7950d29f66aee1fc5", "count": 24}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000018", "root": "0x3b17098f521ca0719e144a12bb79fdc51a3bc70385b5c2ee46b5762aae741f4f", "count": 25}, - {"leaf": "0x0000000000000000000000000000000000000000000000000000000000000019", "root": "0xd3e054489aa750d41938143011666a83e5e6b1477cce5ad612447059c2d8b939", "count": 26}, - {"leaf": "0x000000000000000000000000000000000000000000000000000000000000001a", "root": "0x6d15443ab2f39cce7fbe131843cdad6f27400eb179efb866569dd48baaf3ed4d", "count": 27}, - {"leaf": "0x000000000000000000000000000000000000000000000000000000000000001b", "root": "0xf9386ef40320c369185e48132f8fbf2f3e78d9598495dd342bcf4f41388d460d", "count": 28}, - {"leaf": "0x000000000000000000000000000000000000000000000000000000000000001c", "root": "0xb618ebe1f7675ef246a8cbb93519469076d5caacd4656330801537933e27b172", "count": 29}, - {"leaf": "0x000000000000000000000000000000000000000000000000000000000000001d", "root": "0x6c8c90b5aa967c98061a2dd09ea74dfb61fd9e86e308f14453e9e0ae991116de", "count": 30}, - {"leaf": "0x000000000000000000000000000000000000000000000000000000000000001e", "root": "0x06f51cfc733d71220d6e5b70a6b33a8d47a1ab55ac045fac75f26c762d7b29c9", "count": 31}, - {"leaf": "0x000000000000000000000000000000000000000000000000000000000000001f", "root": "0x82d1ddf8c6d986dee7fc6fa2d7120592d1dc5026b1bb349fcc9d5c73ac026f56", "count": 32} + "counts": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32 + ], + "leaves": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000004", + "0x0000000000000000000000000000000000000000000000000000000000000005", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000007", + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x000000000000000000000000000000000000000000000000000000000000000a", + "0x000000000000000000000000000000000000000000000000000000000000000b", + "0x000000000000000000000000000000000000000000000000000000000000000c", + "0x000000000000000000000000000000000000000000000000000000000000000d", + "0x000000000000000000000000000000000000000000000000000000000000000e", + "0x000000000000000000000000000000000000000000000000000000000000000f", + "0x0000000000000000000000000000000000000000000000000000000000000010", + "0x0000000000000000000000000000000000000000000000000000000000000011", + "0x0000000000000000000000000000000000000000000000000000000000000012", + "0x0000000000000000000000000000000000000000000000000000000000000013", + "0x0000000000000000000000000000000000000000000000000000000000000014", + "0x0000000000000000000000000000000000000000000000000000000000000015", + "0x0000000000000000000000000000000000000000000000000000000000000016", + "0x0000000000000000000000000000000000000000000000000000000000000017", + "0x0000000000000000000000000000000000000000000000000000000000000018", + "0x0000000000000000000000000000000000000000000000000000000000000019", + "0x000000000000000000000000000000000000000000000000000000000000001a", + "0x000000000000000000000000000000000000000000000000000000000000001b", + "0x000000000000000000000000000000000000000000000000000000000000001c", + "0x000000000000000000000000000000000000000000000000000000000000001d", + "0x000000000000000000000000000000000000000000000000000000000000001e", + "0x000000000000000000000000000000000000000000000000000000000000001f" + ], + "roots": [ + "0x27ae5ba08d7291c96c8cbddcc148bf48a6d68c7974b94356f53754ef6171d757", + "0x4a90a2c108a29b7755a0a915b9bb950233ce71f8a01859350d7b73cc56f57a62", + "0x2757cc260a62cc7c7708c387ea99f2a6bb5f034ed00da845734bec4d3fa3abfe", + "0xcb305ccda4331eb3fd9e17b81a5a0b336fb37a33f927698e9fb0604e534c6a01", + "0xa377a6262d3bae7be0ce09c2cc9f767b0f31848c268a4bdc12b63a451bb97281", + "0x440213f4dff167e3f5c655fbb6a3327af3512affed50ce3c1a3f139458a8a6d1", + "0xdd716d2905f2881005341ff1046ced5ee15cc63139716f56ed6be1d075c3f4a7", + "0xd6ebf96fcc3344fa755057b148162f95a93491bc6e8be756d06ec64df4df90fc", + "0x8b3bf2c95f3d0f941c109adfc3b652fadfeaf6f34be52524360a001cb151b5c9", + "0x74a5712654eccd015c44aca31817fd8bee8da400ada986a78384ef3594f2d459", + "0x95dd1209b92cce04311dfc8670b03428408c4ff62beb389e71847971f73702fa", + "0x0a83f3b2a75e19b7255b1de379ea9a71aef9716a3aef20a86abe625f088bbebf", + "0x601ba73b45858be76c8d02799fd70a5e1713e04031aa3be6746f95a17c343173", + "0x93d741c47aa73e36d3c7697758843d6af02b10ed38785f367d1602c8638adb0d", + "0x578f0d0a9b8ed5a4f86181b7e479da7ad72576ba7d3f36a1b72516aa0900c8ac", + "0x995c30e6b58c6e00e06faf4b5c94a21eb820b9db7ad30703f8e3370c2af10c11", + "0x49fb7257be1e954c377dc2557f5ca3f6fc7002d213f2772ab6899000e465236c", + "0x06fee72550896c50e28b894c60a3132bfe670e5c7a77ab4bb6a8ffb4abcf9446", + "0xbba3a807e79d33c6506cd5ecb5d50417360f8be58139f6dbe2f02c92e4d82491", + "0x1243fbd4d21287dbdaa542fa18a6a172b60d1af2c517b242914bdf8d82a98293", + "0x02b7b57e407fbccb506ed3199922d6d9bd0f703a1919d388c76867399ed44286", + "0xa15e7890d8f860a2ef391f9f58602dec7027c19e8f380980f140bbb92a3e00ba", + "0x2cb7eff4deb9bf6bbb906792bc152f1e63759b30e7829bfb5f3257ee600303f5", + "0xb1b034b4784411dc6858a0da771acef31be60216be0520a7950d29f66aee1fc5", + "0x3b17098f521ca0719e144a12bb79fdc51a3bc70385b5c2ee46b5762aae741f4f", + "0xd3e054489aa750d41938143011666a83e5e6b1477cce5ad612447059c2d8b939", + "0x6d15443ab2f39cce7fbe131843cdad6f27400eb179efb866569dd48baaf3ed4d", + "0xf9386ef40320c369185e48132f8fbf2f3e78d9598495dd342bcf4f41388d460d", + "0xb618ebe1f7675ef246a8cbb93519469076d5caacd4656330801537933e27b172", + "0x6c8c90b5aa967c98061a2dd09ea74dfb61fd9e86e308f14453e9e0ae991116de", + "0x06f51cfc733d71220d6e5b70a6b33a8d47a1ab55ac045fac75f26c762d7b29c9", + "0x82d1ddf8c6d986dee7fc6fa2d7120592d1dc5026b1bb349fcc9d5c73ac026f56" ] } \ No newline at end of file diff --git a/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol b/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol index 2af315365..2e5b01623 100644 --- a/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol +++ b/crates/miden-agglayer/solidity-compat/test/MMRTestVectors.t.sol @@ -20,68 +20,55 @@ contract MMRTestVectors is Test, DepositContractBase { * @notice Generates the canonical zeros and saves to JSON file. * ZERO_0 = 0x0...0 (32 zero bytes) * ZERO_n = keccak256(ZERO_{n-1} || ZERO_{n-1}) - * + * * Output file: test-vectors/canonical_zeros.json */ function test_generateCanonicalZeros() public { - string memory json = "{\n"; - json = string.concat(json, ' "canonical_zeros": [\n'); + bytes32[] memory zeros = new bytes32[](32); - bytes32 zero = bytes32(0); - for (uint256 height = 0; height < 32; height++) { - if (height < 31) { - json = string.concat(json, ' "', vm.toString(zero), '",\n'); - } else { - json = string.concat(json, ' "', vm.toString(zero), '"\n'); - } - zero = keccak256(abi.encodePacked(zero, zero)); + bytes32 z = bytes32(0); + for (uint256 i = 0; i < 32; i++) { + zeros[i] = z; + z = keccak256(abi.encodePacked(z, z)); } - json = string.concat(json, " ]\n}"); - - // Print to console - console.log(json); + + // Foundry serializes bytes32[] to a JSON array automatically + string memory json = vm.serializeBytes32("root", "canonical_zeros", zeros); // Save to file string memory outputPath = "test-vectors/canonical_zeros.json"; - vm.writeFile(outputPath, json); - console.log("\nSaved to:", outputPath); + vm.writeJson(json, outputPath); + console.log("Saved canonical zeros to:", outputPath); } /** * @notice Generates MMR frontier vectors (leaf-root pairs) and saves to JSON file. + * Uses parallel arrays instead of array of objects for cleaner serialization. * Output file: test-vectors/mmr_frontier_vectors.json */ function test_generateVectors() public { - string memory json = "{\n"; - json = string.concat(json, ' "vectors": [\n'); - + bytes32[] memory leaves = new bytes32[](32); + bytes32[] memory roots = new bytes32[](32); + uint256[] memory counts = new uint256[](32); + for (uint256 i = 0; i < 32; i++) { bytes32 leaf = bytes32(i); _addLeaf(leaf); - bytes32 root = getRoot(); - - // Build JSON object for this vector - string memory vectorJson = string.concat( - ' {"leaf": "', vm.toString(leaf), - '", "root": "', vm.toString(root), - '", "count": ', vm.toString(depositCount), "}" - ); - - if (i < 31) { - json = string.concat(json, vectorJson, ",\n"); - } else { - json = string.concat(json, vectorJson, "\n"); - } + + leaves[i] = leaf; + roots[i] = getRoot(); + counts[i] = depositCount; } - - json = string.concat(json, " ]\n}"); - - // Print to console - console.log(json); - + + // Serialize parallel arrays to JSON + string memory obj = "root"; + vm.serializeBytes32(obj, "leaves", leaves); + vm.serializeBytes32(obj, "roots", roots); + string memory json = vm.serializeUint(obj, "counts", counts); + // Save to file string memory outputPath = "test-vectors/mmr_frontier_vectors.json"; - vm.writeFile(outputPath, json); - console.log("\nSaved to:", outputPath); + vm.writeJson(json, outputPath); + console.log("Saved MMR frontier vectors to:", outputPath); } } diff --git a/crates/miden-testing/tests/agglayer/mmr_frontier.rs b/crates/miden-testing/tests/agglayer/mmr_frontier.rs index 98ab952a5..a849b085c 100644 --- a/crates/miden-testing/tests/agglayer/mmr_frontier.rs +++ b/crates/miden-testing/tests/agglayer/mmr_frontier.rs @@ -151,16 +151,12 @@ struct CanonicalZerosFile { } /// Deserialized MMR frontier vectors from Solidity DepositContractBase.sol +/// Uses parallel arrays for leaves, roots, and counts instead of array of objects #[derive(Debug, Deserialize)] struct MmrFrontierVectorsFile { - vectors: Vec, -} - -#[derive(Debug, Deserialize)] -struct MmrFrontierTestVector { - leaf: String, - root: String, - count: u32, + leaves: Vec, + roots: Vec, + counts: Vec, } /// Lazily parsed canonical zeros from the JSON file. @@ -171,7 +167,7 @@ static SOLIDITY_CANONICAL_ZEROS: LazyLock = LazyLock::new(|| /// Lazily parsed MMR frontier vectors from the JSON file. static SOLIDITY_MMR_FRONTIER_VECTORS: LazyLock = LazyLock::new(|| { serde_json::from_str(MMR_FRONTIER_VECTORS_JSON) - .expect("Failed to parse MMR frontier vectors JSON") + .expect("failed to parse MMR frontier vectors JSON") }); /// Verifies that the Rust KeccakMmrFrontier32 produces the same canonical zeros as Solidity. @@ -183,7 +179,7 @@ fn test_solidity_canonical_zeros_compatibility() { assert_eq!( actual, expected, - "Canonical zero mismatch at height {}: expected {}, got {:?}", + "canonical zero mismatch at height {}: expected {}, got {:?}", height, expected_hex, actual ); } @@ -193,25 +189,31 @@ fn test_solidity_canonical_zeros_compatibility() { /// DepositContractBase after adding each leaf. #[test] fn test_solidity_mmr_frontier_compatibility() { + let v = &*SOLIDITY_MMR_FRONTIER_VECTORS; + + // Validate parallel arrays have same length + assert_eq!(v.leaves.len(), v.roots.len()); + assert_eq!(v.leaves.len(), v.counts.len()); + let mut mmr_frontier = KeccakMmrFrontier32::<32>::new(); - for vector in &SOLIDITY_MMR_FRONTIER_VECTORS.vectors { - let leaf = Keccak256Digest::try_from(vector.leaf.as_str()).unwrap(); - let expected_root = Keccak256Digest::try_from(vector.root.as_str()).unwrap(); + for i in 0..v.leaves.len() { + let leaf = Keccak256Digest::try_from(v.leaves[i].as_str()).unwrap(); + let expected_root = Keccak256Digest::try_from(v.roots[i].as_str()).unwrap(); let actual_root = mmr_frontier.append_and_update_frontier(leaf); let actual_count = mmr_frontier.num_leaves; assert_eq!( - actual_count, vector.count, - "Leaf count mismatch after adding leaf {}: expected {}, got {}", - vector.leaf, vector.count, actual_count + actual_count, v.counts[i], + "leaf count mismatch after adding leaf {}: expected {}, got {}", + v.leaves[i], v.counts[i], actual_count ); assert_eq!( actual_root, expected_root, - "Root mismatch after adding leaf {} (count={}): expected {}, got {:?}", - vector.leaf, vector.count, vector.root, actual_root + "root mismatch after adding leaf {} (count={}): expected {}, got {:?}", + v.leaves[i], v.counts[i], v.roots[i], actual_root ); } } From 3b025745c7d75d7c47360b7e9b259601a578470d Mon Sep 17 00:00:00 2001 From: Marti Date: Tue, 20 Jan 2026 11:25:25 +0000 Subject: [PATCH 12/13] chore: use workspace serde dep --- crates/miden-testing/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/miden-testing/Cargo.toml b/crates/miden-testing/Cargo.toml index f1768bffb..37196eeab 100644 --- a/crates/miden-testing/Cargo.toml +++ b/crates/miden-testing/Cargo.toml @@ -46,7 +46,7 @@ miden-crypto = { workspace = true } miden-protocol = { features = ["std"], workspace = true } primitive-types = { workspace = true } rstest = { workspace = true } -serde = { features = ["derive"], version = "1.0" } +serde = { features = ["derive"], workspace = true } serde_json = { version = "1.0" } tokio = { features = ["macros", "rt"], workspace = true } winter-rand-utils = { version = "0.13" } From 73bf3209333f030dc5039d00b0a2b5938f1ecf91 Mon Sep 17 00:00:00 2001 From: Marti Date: Tue, 20 Jan 2026 11:28:28 +0000 Subject: [PATCH 13/13] chore: cleanup changelog --- CHANGELOG.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a5b35796..a93b95247 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,15 +18,10 @@ - Add `AccountId::parse()` helper function to parse both hex and bech32 formats ([#2223](https://github.com/0xMiden/miden-base/pull/2223)). - Add Keccak-based MMR frontier structure to the Agglayer library ([#2245](https://github.com/0xMiden/miden-base/pull/2245)). - Add `read_foreign_account_inputs()`, `read_vault_asset_witnesses()`, and `read_storage_map_witness()` for `TransactionInputs` ([#2246](https://github.com/0xMiden/miden-base/pull/2246)). -- [BREAKING] Introduce `NoteAttachment` as part of `NoteMetadata` and remove `aux` and `execution_hint` ([#2249](https://github.com/0xMiden/miden-base/pull/2249)). -- [BREAKING] Introduce `NoteAttachment` as part of `NoteMetadata` and remove `aux` and `execution_hint` ([#2249](https://github.com/0xMiden/miden-base/pull/2249), [#2252](https://github.com/0xMiden/miden-base/pull/2252)). -- [BREAKING] Introduce `NoteAttachment` as part of `NoteMetadata` and remove `aux` and `execution_hint` ([#2249](https://github.com/0xMiden/miden-base/pull/2249), [#2252](https://github.com/0xMiden/miden-base/pull/2252), [#2260](https://github.com/0xMiden/miden-base/pull/2260)). - [BREAKING] Introduce `NoteAttachment` as part of `NoteMetadata` and remove `aux` and `execution_hint` ([#2249](https://github.com/0xMiden/miden-base/pull/2249), [#2252](https://github.com/0xMiden/miden-base/pull/2252), [#2260](https://github.com/0xMiden/miden-base/pull/2260), [#2268](https://github.com/0xMiden/miden-base/pull/2268), [#2279](https://github.com/0xMiden/miden-base/pull/2279)). - Introduce standard `NetworkAccountTarget` attachment for use in network transactions which replaces `NoteTag::NetworkAccount` ([#2257](https://github.com/0xMiden/miden-base/pull/2257)). - Add a foundry test suite for verifying AggLayer contracts compatibility ([#2312](https://github.com/0xMiden/miden-base/pull/2312)). -- [BREAKING] Introduced `NoteAttachment` as part of `NoteMetadata` and remove `aux` and `execution_hint` ([#2249](https://github.com/0xMiden/miden-base/pull/2249), [#2252](https://github.com/0xMiden/miden-base/pull/2252), [#2260](https://github.com/0xMiden/miden-base/pull/2260), [#2268](https://github.com/0xMiden/miden-base/pull/2268), [#2279](https://github.com/0xMiden/miden-base/pull/2279)). - Added `AccountSchemaCommitment` component to expose account storage schema commitments ([#2253](https://github.com/0xMiden/miden-base/pull/2253)). -- Introduced standard `NetworkAccountTarget` attachment for use in network transactions which replaces `NoteTag::NetworkAccount` ([#2257](https://github.com/0xMiden/miden-base/pull/2257)). - Added an `AccountBuilder` extension trait to help build the schema commitment; added `AccountComponentMetadata` to `AccountComponent` ([#2269](https://github.com/0xMiden/miden-base/pull/2269)). - Added `miden::standards::access::ownable` standard module for component ownership management, and integrated it into the `network_fungible` faucet (including new tests). ([#2228](https://github.com/0xMiden/miden-base/pull/2228)).