From 1b350af8909aa520928a7b8579a1c06a1244c018 Mon Sep 17 00:00:00 2001 From: Marti Date: Fri, 16 Jan 2026 08:52:22 +0000 Subject: [PATCH 1/4] feat: implement verify_leaf_bridge --- .../asm/bridge/agglayer_faucet.masm | 23 ++++++------- .../miden-agglayer/asm/bridge/bridge_in.masm | 32 ++++++++++++++----- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/crates/miden-agglayer/asm/bridge/agglayer_faucet.masm b/crates/miden-agglayer/asm/bridge/agglayer_faucet.masm index 4c1278306..c9a4ca0d1 100644 --- a/crates/miden-agglayer/asm/bridge/agglayer_faucet.masm +++ b/crates/miden-agglayer/asm/bridge/agglayer_faucet.masm @@ -57,28 +57,23 @@ const ERR_INVALID_CLAIM_PROOF = "invalid claim proof" #! #! Invocation: exec proc validate_claim - # Get bridge_in::check_claim_proof procedure MAST root - procref.bridge_in::check_claim_proof - # => [BRIDGE_PROC_MAST_ROOT] + # get bridge_in::verify_leaf_bridge procedure MAST root + procref.bridge_in::verify_leaf_bridge + # => [BRIDGE_PROC_MAST_ROOT, PROOF_DATA_KEY, LEAF_DATA_KEY] push.BRIDGE_ID_SLOT[0..2] - # => [bridge_id_idx, BRIDGE_PROC_MAST_ROOT] + # => [bridge_id_idx, BRIDGE_PROC_MAST_ROOT, PROOF_DATA_KEY, LEAF_DATA_KEY] - # Get Bridge AccountId + # get bridge account ID exec.active_account::get_item - # => [bridge_account_id_prefix, bridge_account_id_suffix, 0, 0, BRIDGE_PROC_MAST_ROOT] + # => [bridge_account_id_prefix, bridge_account_id_suffix, 0, 0, BRIDGE_PROC_MAST_ROOT, PROOF_DATA_KEY, LEAF_DATA_KEY] movup.2 drop movup.2 drop - # => [bridge_account_id_prefix, bridge_account_id_suffix, BRIDGE_PROC_MAST_ROOT] + # => [bridge_account_id_prefix, bridge_account_id_suffix, BRIDGE_PROC_MAST_ROOT, PROOF_DATA_KEY, LEAF_DATA_KEY] - # Call check_claim_proof procedure on Bridge - # Calling: bridge_in::check_claim_proof + # call bridge_in::verify_leaf_bridge exec.tx::execute_foreign_procedure - # => [validation_result] - - # Assert valid proof data - assert.err=ERR_INVALID_CLAIM_PROOF drop - # => [] + # => [] end # Inputs: [] diff --git a/crates/miden-agglayer/asm/bridge/bridge_in.masm b/crates/miden-agglayer/asm/bridge/bridge_in.masm index 1862fc5e3..c31824e13 100644 --- a/crates/miden-agglayer/asm/bridge/bridge_in.masm +++ b/crates/miden-agglayer/asm/bridge/bridge_in.masm @@ -7,7 +7,7 @@ pub proc get_rollup_exit_root push.0.0.0.0.0.0.0.0 # dummy GER end -#! Checks the validity of the GET proof +#! Computes the leaf value and verifies it against the AggLayer bridge. #! #! Inputs: #! Operand stack: [PROOF_DATA_KEY, LEAF_DATA_KEY, pad(8)] @@ -30,14 +30,30 @@ end #! ], #! } #! +#! Outputs: [pad(16)] +#! #! Invocation: call -pub proc check_claim_proof - exec.get_rollup_exit_root - # => [GER_ROOT[8], CLAIM_NOTE_RPO_COMMITMENT] +pub proc verify_leaf_bridge + # get the leaf value. We have all the necessary leaf data in the advice map + swapw + # => [LEAF_DATA_KEY, PROOF_DATA_KEY, pad(8)] + exec.crypto_utils::get_leaf_value + # => [LEAF_VALUE[8], PROOF_DATA_KEY, pad(8)] + + movupw.3 dropw + # => [LEAF_VALUE[8], PROOF_DATA_KEY, pad(4)] - # Check CLAIM note proof data against current GER - exec.crypto_utils::verify_claim_proof - # => [is_valid_claim_proof] + # delegate proof verification (stubbed for now) + exec.verify_leaf +end - swap drop +#! Verify leaf and checks that it has not been claimed. +#! +#! Inputs: +#! Operand stack: [LEAF_VALUE[8], PROOF_DATA_KEY, pad(4)] +#! Invocation: exec +proc verify_leaf + # TODO: Awaiting https://github.com/0xMiden/miden-base/issues/2277 + dropw dropw dropw + # => [pad(16)] end From 5afc32e03864722795970e208d60995ea2d9c2ee Mon Sep 17 00:00:00 2001 From: Marti Date: Fri, 16 Jan 2026 08:53:24 +0000 Subject: [PATCH 2/4] chore: stack is empty, can swap instead of pad --- crates/miden-agglayer/asm/bridge/agglayer_faucet.masm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/miden-agglayer/asm/bridge/agglayer_faucet.masm b/crates/miden-agglayer/asm/bridge/agglayer_faucet.masm index c9a4ca0d1..4c310542c 100644 --- a/crates/miden-agglayer/asm/bridge/agglayer_faucet.masm +++ b/crates/miden-agglayer/asm/bridge/agglayer_faucet.masm @@ -223,7 +223,7 @@ pub proc claim # => [] # VALIDATE CLAIM - mem_loadw_be.LEAF_DATA_KEY_MEM_ADDR padw + mem_loadw_be.LEAF_DATA_KEY_MEM_ADDR swapw mem_loadw_be.PROOF_DATA_KEY_MEM_ADDR # => [PROOF_DATA_KEY, LEAF_DATA_KEY] From 132356ba768e4d3acdba83fc9766ac93ddcf1652 Mon Sep 17 00:00:00 2001 From: Marti Date: Fri, 16 Jan 2026 08:57:58 +0000 Subject: [PATCH 3/4] chore: load leaf first, proof later --- .../asm/bridge/agglayer_faucet.masm | 16 ++++++++-------- crates/miden-agglayer/asm/bridge/bridge_in.masm | 4 +--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/miden-agglayer/asm/bridge/agglayer_faucet.masm b/crates/miden-agglayer/asm/bridge/agglayer_faucet.masm index 4c310542c..08f71ec36 100644 --- a/crates/miden-agglayer/asm/bridge/agglayer_faucet.masm +++ b/crates/miden-agglayer/asm/bridge/agglayer_faucet.masm @@ -47,7 +47,7 @@ const P2ID_OUTPUT_NOTE_AMOUNT_MEM_PTR = 611 const ERR_INVALID_CLAIM_PROOF = "invalid claim proof" -#! Inputs: [PROOF_DATA_KEY, LEAF_DATA_KEY] +#! Inputs: [LEAF_DATA_KEY, PROOF_DATA_KEY] #! Outputs: [] #! #! Panics if: @@ -59,17 +59,17 @@ const ERR_INVALID_CLAIM_PROOF = "invalid claim proof" proc validate_claim # get bridge_in::verify_leaf_bridge procedure MAST root procref.bridge_in::verify_leaf_bridge - # => [BRIDGE_PROC_MAST_ROOT, PROOF_DATA_KEY, LEAF_DATA_KEY] + # => [BRIDGE_PROC_MAST_ROOT, LEAF_DATA_KEY, PROOF_DATA_KEY] push.BRIDGE_ID_SLOT[0..2] - # => [bridge_id_idx, BRIDGE_PROC_MAST_ROOT, PROOF_DATA_KEY, LEAF_DATA_KEY] + # => [bridge_id_idx, BRIDGE_PROC_MAST_ROOT, LEAF_DATA_KEY, PROOF_DATA_KEY] # get bridge account ID exec.active_account::get_item - # => [bridge_account_id_prefix, bridge_account_id_suffix, 0, 0, BRIDGE_PROC_MAST_ROOT, PROOF_DATA_KEY, LEAF_DATA_KEY] + # => [bridge_account_id_prefix, bridge_account_id_suffix, 0, 0, BRIDGE_PROC_MAST_ROOT, LEAF_DATA_KEY, PROOF_DATA_KEY] movup.2 drop movup.2 drop - # => [bridge_account_id_prefix, bridge_account_id_suffix, BRIDGE_PROC_MAST_ROOT, PROOF_DATA_KEY, LEAF_DATA_KEY] + # => [bridge_account_id_prefix, bridge_account_id_suffix, BRIDGE_PROC_MAST_ROOT, LEAF_DATA_KEY, PROOF_DATA_KEY] # call bridge_in::verify_leaf_bridge exec.tx::execute_foreign_procedure @@ -223,9 +223,9 @@ pub proc claim # => [] # VALIDATE CLAIM - mem_loadw_be.LEAF_DATA_KEY_MEM_ADDR swapw - mem_loadw_be.PROOF_DATA_KEY_MEM_ADDR - # => [PROOF_DATA_KEY, LEAF_DATA_KEY] + mem_loadw_be.PROOF_DATA_KEY_MEM_ADDR swapw + mem_loadw_be.LEAF_DATA_KEY_MEM_ADDR + # => [LEAF_DATA_KEY, PROOF_DATA_KEY] # Errors on invalid proof exec.validate_claim diff --git a/crates/miden-agglayer/asm/bridge/bridge_in.masm b/crates/miden-agglayer/asm/bridge/bridge_in.masm index c31824e13..005c4dc76 100644 --- a/crates/miden-agglayer/asm/bridge/bridge_in.masm +++ b/crates/miden-agglayer/asm/bridge/bridge_in.masm @@ -10,7 +10,7 @@ end #! Computes the leaf value and verifies it against the AggLayer bridge. #! #! Inputs: -#! Operand stack: [PROOF_DATA_KEY, LEAF_DATA_KEY, pad(8)] +#! Operand stack: [LEAF_DATA_KEY, PROOF_DATA_KEY, pad(8)] #! Advice map: { #! PROOF_DATA_KEY => [ #! smtProofLocalExitRoot[256], // SMT proof for local exit root (256 felts, bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH]) @@ -35,8 +35,6 @@ end #! Invocation: call pub proc verify_leaf_bridge # get the leaf value. We have all the necessary leaf data in the advice map - swapw - # => [LEAF_DATA_KEY, PROOF_DATA_KEY, pad(8)] exec.crypto_utils::get_leaf_value # => [LEAF_VALUE[8], PROOF_DATA_KEY, pad(8)] From b31afb1113224f81de00e1a0ad9bef12a827c143 Mon Sep 17 00:00:00 2001 From: Marti Date: Fri, 23 Jan 2026 10:04:21 +0100 Subject: [PATCH 4/4] feat(AggLayer): implement `verify_leaf` (#2295) * feat: verify_leaf stubbed feat: fill some TODOs * chore: test global index processing * chore: update comments * self review * changelog --- CHANGELOG.md | 1 + .../miden-agglayer/asm/bridge/bridge_in.masm | 93 +++++++++++++++++-- .../asm/bridge/crypto_utils.masm | 63 ++++++++----- crates/miden-agglayer/src/errors/agglayer.rs | 9 ++ crates/miden-agglayer/src/lib.rs | 4 +- .../tests/agglayer/global_index.rs | 82 ++++++++++++++++ crates/miden-testing/tests/agglayer/mod.rs | 2 + .../tests/agglayer/test_utils.rs | 35 +++++++ 8 files changed, 254 insertions(+), 35 deletions(-) create mode 100644 crates/miden-testing/tests/agglayer/global_index.rs create mode 100644 crates/miden-testing/tests/agglayer/test_utils.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index c7a6accca..688b8988d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,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)). +- Implemented verification of AggLayer deposits (claims) against GER ([#2295](https://github.com/0xMiden/miden-base/pull/2295), [#2288](https://github.com/0xMiden/miden-base/pull/2288)). ### Changes diff --git a/crates/miden-agglayer/asm/bridge/bridge_in.masm b/crates/miden-agglayer/asm/bridge/bridge_in.masm index 005c4dc76..a3165a3d8 100644 --- a/crates/miden-agglayer/asm/bridge/bridge_in.masm +++ b/crates/miden-agglayer/asm/bridge/bridge_in.masm @@ -1,10 +1,23 @@ use miden::agglayer::crypto_utils +use miden::core::crypto::hashes::keccak256 +use miden::core::mem -# Inputs: [] -# Output: [GER_ROOT[8]] -pub proc get_rollup_exit_root - # Push dummy GER (8 elements) - push.0.0.0.0.0.0.0.0 # dummy GER +const PROOF_DATA_PTR = 0 +const PROOF_DATA_WORD_LEN = 134 +const SMT_PROOF_LOCAL_EXIT_ROOT_PTR = 0 # local SMT proof is first +const GLOBAL_INDEX_PTR = PROOF_DATA_PTR + 2 * 256 # 512 +const EXIT_ROOTS_PTR = GLOBAL_INDEX_PTR + 8 # 520 +const MAINNET_EXIT_ROOT_PTR = GLOBAL_INDEX_PTR # it's the first exit root + +const ERR_BRIDGE_NOT_MAINNET = "bridge not mainnet" +const ERR_LEADING_BITS_NON_ZERO = "leading bits of global index must be zero" +const ERR_ROLLUP_INDEX_NON_ZERO = "rollup index must be zero for a mainnet deposit" + +# Inputs: [GER_ROOT[8]] +# Output: [] +pub proc assert_valid_ger + # TODO verify that GER is in storage + dropw dropw end #! Computes the leaf value and verifies it against the AggLayer bridge. @@ -41,17 +54,77 @@ pub proc verify_leaf_bridge movupw.3 dropw # => [LEAF_VALUE[8], PROOF_DATA_KEY, pad(4)] - # delegate proof verification (stubbed for now) + # delegate proof verification exec.verify_leaf end #! Verify leaf and checks that it has not been claimed. #! #! Inputs: -#! Operand stack: [LEAF_VALUE[8], PROOF_DATA_KEY, pad(4)] +#! Operand stack: [LEAF_VALUE[8], PROOF_DATA_KEY] +#! +#! Outputs: [] +#! #! Invocation: exec proc verify_leaf - # TODO: Awaiting https://github.com/0xMiden/miden-base/issues/2277 - dropw dropw dropw - # => [pad(16)] + movupw.2 + # load proof data from the advice map into memory + adv.push_mapval + # => [PROOF_DATA_KEY, LEAF_VALUE[8]] + + push.SMT_PROOF_LOCAL_EXIT_ROOT_PTR push.PROOF_DATA_WORD_LEN + exec.mem::pipe_preimage_to_memory drop + + # 1. compute GER from mainnet + rollup exit roots + push.EXIT_ROOTS_PTR + # => [exit_roots_ptr, LEAF_VALUE[8]] + exec.crypto_utils::compute_ger + # => [GER[8], LEAF_VALUE[8]] + + # 2. assert the GER is valid + exec.assert_valid_ger + # => [LEAF_VALUE[8]] + + # 3. load global index from memory + padw mem_loadw_le.GLOBAL_INDEX_PTR + padw push.GLOBAL_INDEX_PTR add.4 mem_loadw_le swapw + # => [GLOBAL_INDEX[8], LEAF_VALUE[8]] + + # to see if we're dealing with a deposit from mainnet or from a rollup, process the global index + # TODO currently only implemented for mainnet deposits (mainnet flag must be 1) + exec.process_global_index_mainnet + # => [leaf_index] + + # load the pointers to the merkle proof and root, to pass to `verify_merkle_proof` + push.SMT_PROOF_LOCAL_EXIT_ROOT_PTR + push.MAINNET_EXIT_ROOT_PTR + + # => [mainnet_exit_root_ptr, smt_proof_local_exit_root_ptr, leaf_index, LEAF_VALUE[8]] + movdn.10 + # => [smt_proof_local_exit_root_ptr, leaf_index, LEAF_VALUE[8], mainnet_exit_root_ptr] + # delegate verification to crypto_utils::verify_merkle_proof (stubbed for now) + exec.crypto_utils::verify_merkle_proof +end + +#! Assert the global index is valid for a mainnet deposit. +#! +#! Inputs: [GLOBAL_INDEX[8]] +#! Outputs: [leaf_index] +#! +#! Invocation: exec +pub proc process_global_index_mainnet +# for v0.1, let's only implement the mainnet branch + # the top 191 bits of the global index are zero + repeat.5 assertz.err=ERR_LEADING_BITS_NON_ZERO end + + # the next element is a u32 mainnet flag bit + # enforce that this limb is one + # => [mainnet_flag, GLOBAL_INDEX[6..8], LEAF_VALUE[8]] + assert.err=ERR_BRIDGE_NOT_MAINNET + + # the next element is a u32 rollup index, must be zero for a mainnet deposit + assertz.err=ERR_ROLLUP_INDEX_NON_ZERO + + # finally, the leaf index = lowest 32 bits = last limb + # => [leaf_index] end diff --git a/crates/miden-agglayer/asm/bridge/crypto_utils.masm b/crates/miden-agglayer/asm/bridge/crypto_utils.masm index 7796c1f94..1e53cf83a 100644 --- a/crates/miden-agglayer/asm/bridge/crypto_utils.masm +++ b/crates/miden-agglayer/asm/bridge/crypto_utils.masm @@ -2,32 +2,23 @@ use miden::core::crypto::hashes::keccak256 #! Given the leaf data returns the leaf value. #! -#! Inputs: [leaf_type, origin_network, ORIGIN_ADDRESS, destination_network, DESTINATION_ADDRESS, amount, METADATA_HASH] -#! Outputs: [LEAF_VALUE] -#! -#! Where: -#! - leaf_type is the leaf type: [0] transfer Ether / ERC20 tokens, [1] message. -#! - origin_network is the origin network identifier. -#! - ORIGIN_ADDRESS is the origin token address (5 elements) -#! - destination_network is the destination network identifier. -#! - DESTINATION_ADDRESS is the destination address (5 elements). -#! - amount is the amount: [0] Amount of tokens/ether, [1] Amount of ether. -#! - METADATA_HASH is the hash of the metadata (8 elements). -#! - LEAF_VALUE is the computed leaf value (8 elements). -#! -#! This function computes the keccak256 hash of the abi.encodePacked data. +#! Inputs: +#! Operand stack: [LEAF_DATA_KEY] +#! Advice map: { +#! LEAF_DATA_KEY => [ +#! originNetwork[1], // Origin network identifier (1 felt, uint32) +#! originTokenAddress[5], // Origin token address (5 felts, address as 5 u32 felts) +#! destinationNetwork[1], // Destination network identifier (1 felt, uint32) +#! destinationAddress[5], // Destination address (5 felts, address as 5 u32 felts) +#! amount[8], // Amount of tokens (8 felts, uint256 as 8 u32 felts) +#! metadata[8], // ABI encoded metadata (8 felts, fixed size) +#! ], +#! } +#! Outputs: [LEAF_VALUE[8]] #! #! Invocation: exec pub proc get_leaf_value - # TODO: implement getLeafValue() - # https://github.com/agglayer/agglayer-contracts/blob/e468f9b0967334403069aa650d9f1164b1731ebb/contracts/v2/lib/DepositContractV2.sol#L22 - - # stubbed out: - push.1.1.1.1 - push.1.1.1.1 - - # exec.keccak256::hash_bytes - # => [LEAF_VALUE[8]] + padw end #! Verify leaf and checks that it has not been claimed. @@ -80,3 +71,29 @@ pub proc verify_claim_proof dropw dropw dropw dropw push.1 end + +#! Computes the Global Exit Tree (GET) root from the mainnet and rollup exit roots. +#! +#! Inputs: [exit_roots_ptr] +#! Outputs: [GER_ROOT[8]] +#! +#! Invocation: exec +pub proc compute_ger + push.64 swap + # => [exit_roots_ptr, len_bytes] + exec.keccak256::hash_bytes + # => [GER_ROOT[8]] +end + +#! Verifies a Merkle proof for a leaf value against a root. +#! +#! Inputs: [smt_proof_ptr, leaf_index, LEAF_VALUE[8], root_ptr] +#! Outputs: [] +#! +pub proc verify_merkle_proof + # TODO pending https://github.com/0xMiden/miden-base/issues/2278 + drop + drop + dropw dropw + drop +end diff --git a/crates/miden-agglayer/src/errors/agglayer.rs b/crates/miden-agglayer/src/errors/agglayer.rs index efa9275de..3b167c7fe 100644 --- a/crates/miden-agglayer/src/errors/agglayer.rs +++ b/crates/miden-agglayer/src/errors/agglayer.rs @@ -17,6 +17,9 @@ pub const ERR_B2AGG_WRONG_NUMBER_OF_ASSETS: MasmError = MasmError::from_static_s /// Error Message: "B2AGG script expects exactly 6 note inputs" pub const ERR_B2AGG_WRONG_NUMBER_OF_INPUTS: MasmError = MasmError::from_static_str("B2AGG script expects exactly 6 note inputs"); +/// Error Message: "bridge not mainnet" +pub const ERR_BRIDGE_NOT_MAINNET: MasmError = MasmError::from_static_str("bridge not mainnet"); + /// Error Message: "CLAIM's target account address and transaction address do not match" pub const ERR_CLAIM_TARGET_ACCT_MISMATCH: MasmError = MasmError::from_static_str("CLAIM's target account address and transaction address do not match"); @@ -26,8 +29,14 @@ pub const ERR_FELT_OUT_OF_FIELD: MasmError = MasmError::from_static_str("combine /// Error Message: "invalid claim proof" pub const ERR_INVALID_CLAIM_PROOF: MasmError = MasmError::from_static_str("invalid claim proof"); +/// Error Message: "leading bits of global index must be zero" +pub const ERR_LEADING_BITS_NON_ZERO: MasmError = MasmError::from_static_str("leading bits of global index must be zero"); + /// Error Message: "address limb is not u32" pub const ERR_NOT_U32: MasmError = MasmError::from_static_str("address limb is not u32"); +/// Error Message: "rollup index must be zero for a mainnet deposit" +pub const ERR_ROLLUP_INDEX_NON_ZERO: MasmError = MasmError::from_static_str("rollup index must be zero for a mainnet deposit"); + /// Error Message: "maximum scaling factor is 18" pub const ERR_SCALE_AMOUNT_EXCEEDED_LIMIT: MasmError = MasmError::from_static_str("maximum scaling factor is 18"); diff --git a/crates/miden-agglayer/src/lib.rs b/crates/miden-agglayer/src/lib.rs index 72d50ab7b..262f9dbfc 100644 --- a/crates/miden-agglayer/src/lib.rs +++ b/crates/miden-agglayer/src/lib.rs @@ -545,14 +545,14 @@ pub fn claim_note_test_inputs( let smt_proof_local_exit_root = vec![Felt::new(0); 256]; let smt_proof_rollup_exit_root = vec![Felt::new(0); 256]; let global_index = [ - Felt::new(12345), - Felt::new(0), Felt::new(0), Felt::new(0), Felt::new(0), Felt::new(0), Felt::new(0), + Felt::new(1), // mainnet flag Felt::new(0), + Felt::new(2), // leaf index (mainnet) ]; let mainnet_exit_root: [u8; 32] = [ diff --git a/crates/miden-testing/tests/agglayer/global_index.rs b/crates/miden-testing/tests/agglayer/global_index.rs new file mode 100644 index 000000000..77515a4e5 --- /dev/null +++ b/crates/miden-testing/tests/agglayer/global_index.rs @@ -0,0 +1,82 @@ +extern crate alloc; + +use alloc::sync::Arc; + +use miden_agglayer::agglayer_library; +use miden_agglayer::errors::{ + ERR_BRIDGE_NOT_MAINNET, + ERR_LEADING_BITS_NON_ZERO, + ERR_ROLLUP_INDEX_NON_ZERO, +}; +use miden_assembly::{Assembler, DefaultSourceManager}; +use miden_core_lib::CoreLibrary; +use miden_processor::Program; +use miden_testing::assert_execution_error; + +use crate::agglayer::test_utils::execute_program_with_default_host; + +fn assemble_process_global_index_program(global_index_be_u32_limbs: [u32; 8]) -> Program { + let [g0, g1, g2, g3, g4, g5, g6, g7] = global_index_be_u32_limbs; + + let script_code = format!( + r#" + use miden::core::sys + use miden::agglayer::bridge_in + + begin + push.{g7}.{g6}.{g5}.{g4}.{g3}.{g2}.{g1}.{g0} + exec.bridge_in::process_global_index_mainnet + exec.sys::truncate_stack + end + "# + ); + + Assembler::new(Arc::new(DefaultSourceManager::default())) + .with_dynamic_library(CoreLibrary::default()) + .unwrap() + .with_dynamic_library(agglayer_library()) + .unwrap() + .assemble_program(&script_code) + .unwrap() +} + +#[tokio::test] +async fn test_process_global_index_mainnet_returns_leaf_index() -> anyhow::Result<()> { + // 256-bit globalIndex encoded as 8 u32 limbs (big-endian): + // [top 191 bits = 0, mainnet flag = 1, rollup_index = 0, leaf_index = 2] + let global_index = [0, 0, 0, 0, 0, 1, 0, 2]; + let program = assemble_process_global_index_program(global_index); + + let exec_output = execute_program_with_default_host(program, None).await?; + + assert_eq!(exec_output.stack[0].as_int(), 2); + Ok(()) +} + +#[tokio::test] +async fn test_process_global_index_mainnet_rejects_non_zero_leading_bits() { + let global_index = [1, 0, 0, 0, 0, 1, 0, 2]; + let program = assemble_process_global_index_program(global_index); + + let err = execute_program_with_default_host(program, None).await; + assert_execution_error!(err, ERR_LEADING_BITS_NON_ZERO); +} + +#[tokio::test] +async fn test_process_global_index_mainnet_rejects_flag_limb_upper_bits() { + // limb5 is the mainent flag; only the lowest bit is allowed + let global_index = [0, 0, 0, 0, 0, 3, 0, 2]; + let program = assemble_process_global_index_program(global_index); + + let err = execute_program_with_default_host(program, None).await; + assert_execution_error!(err, ERR_BRIDGE_NOT_MAINNET); +} + +#[tokio::test] +async fn test_process_global_index_mainnet_rejects_non_zero_rollup_index() { + let global_index = [0, 0, 0, 0, 0, 1, 7, 2]; + let program = assemble_process_global_index_program(global_index); + + let err = execute_program_with_default_host(program, None).await; + assert_execution_error!(err, ERR_ROLLUP_INDEX_NON_ZERO); +} diff --git a/crates/miden-testing/tests/agglayer/mod.rs b/crates/miden-testing/tests/agglayer/mod.rs index 2a6d344c6..32cd65af6 100644 --- a/crates/miden-testing/tests/agglayer/mod.rs +++ b/crates/miden-testing/tests/agglayer/mod.rs @@ -1,4 +1,6 @@ pub mod asset_conversion; mod bridge_in; mod bridge_out; +mod global_index; mod solidity_miden_address_conversion; +mod test_utils; diff --git a/crates/miden-testing/tests/agglayer/test_utils.rs b/crates/miden-testing/tests/agglayer/test_utils.rs new file mode 100644 index 000000000..21739e39e --- /dev/null +++ b/crates/miden-testing/tests/agglayer/test_utils.rs @@ -0,0 +1,35 @@ +extern crate alloc; + +use miden_agglayer::agglayer_library; +use miden_core_lib::CoreLibrary; +use miden_processor::fast::{ExecutionOutput, FastProcessor}; +use miden_processor::{AdviceInputs, DefaultHost, ExecutionError, Program, StackInputs}; +use miden_protocol::transaction::TransactionKernel; + +/// Execute a program with default host and optional advice inputs +pub async fn execute_program_with_default_host( + program: Program, + advice_inputs: Option, +) -> Result { + let mut host = DefaultHost::default(); + + let test_lib = TransactionKernel::library(); + host.load_library(test_lib.mast_forest()).unwrap(); + + let std_lib = CoreLibrary::default(); + host.load_library(std_lib.mast_forest()).unwrap(); + + // Register handlers from std_lib + for (event_name, handler) in std_lib.handlers() { + host.register_handler(event_name, handler)?; + } + + let agglayer_lib = agglayer_library(); + host.load_library(agglayer_lib.mast_forest()).unwrap(); + + let stack_inputs = StackInputs::new(vec![]).unwrap(); + let advice_inputs = advice_inputs.unwrap_or_default(); + + let processor = FastProcessor::new_debug(stack_inputs.as_slice(), advice_inputs); + processor.execute(&program, &mut host).await +}