diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm index 2e2d80da89..3c65c11b3e 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm @@ -9,6 +9,7 @@ use miden::protocol::native_account const ERR_GER_NOT_FOUND = "GER not found in storage" const ERR_FAUCET_NOT_REGISTERED="faucet is not registered in the bridge's faucet registry" +const ERR_TOKEN_NOT_REGISTERED="token address is not registered in the bridge's token registry" const ERR_SENDER_NOT_BRIDGE_ADMIN="note sender is not the bridge admin" const ERR_SENDER_NOT_GER_MANAGER="note sender is not the global exit root manager" @@ -20,6 +21,10 @@ const BRIDGE_ADMIN_SLOT=word("miden::agglayer::bridge::admin") const GER_MANAGER_SLOT=word("miden::agglayer::bridge::ger_manager") const GER_STORAGE_SLOT=word("miden::agglayer::bridge::ger") const FAUCET_REGISTRY_SLOT=word("miden::agglayer::bridge::faucet_registry") +const TOKEN_REGISTRY_SLOT=word("miden::agglayer::bridge::token_registry") + +# Memory pointer for hashing token address +const TOKEN_ADDR_HASH_PTR=900 # Flags const GER_KNOWN_FLAG=1 @@ -96,40 +101,69 @@ pub proc assert_valid_ger # => [] end -#! Registers a faucet in the bridge's faucet registry. -#! -#! Writes `KEY -> [1, 0, 0, 0]` into the `faucet_registry` map, where -#! `KEY = [faucet_id_prefix, faucet_id_suffix, 0, 0]`. +#! Registers a faucet in the bridge's faucet registry and token registry. #! -#! The sentinel value `[1, 0, 0, 0]` distinguishes registered faucets from -#! non-existent entries (SMTs return EMPTY_WORD for missing keys). +#! 1. Writes `KEY -> [1, 0, 0, 0]` into the `faucet_registry` map, where +#! `KEY = [faucet_id_prefix, faucet_id_suffix, 0, 0]`. +#! 2. Writes `hash(tokenAddress[5]) -> [faucet_id_prefix, faucet_id_suffix, 0, 0]` +#! into the `token_registry` map. #! #! Panics if the note sender is not the bridge admin. #! -#! Inputs: [faucet_id_prefix, faucet_id_suffix, pad(14)] +#! Inputs: [origin_token_addr(5), faucet_id_prefix, faucet_id_suffix, pad(9)] #! Outputs: [pad(16)] #! #! Invocation: call pub proc register_faucet # assert the note sender is the bridge admin. exec.assert_sender_is_bridge_admin - # => [faucet_id_prefix, faucet_id_suffix, pad(14)] + # => [origin_token_addr(5), faucet_id_prefix, faucet_id_suffix, pad(9)] + + # Save faucet ID for later use in token_registry + dup.6 dup.6 + # => [faucet_id_prefix, faucet_id_suffix, origin_token_addr(5), faucet_id_prefix, faucet_id_suffix, pad(9)] + # --- 1. Register faucet in faucet_registry --- # set_map_item expects [slot_id(2), KEY(4), VALUE(4)] and returns [OLD_VALUE(4)]. - push.IS_FAUCET_REGISTERED_FLAG - # => [IS_FAUCET_REGISTERED_FLAG, faucet_id_prefix, faucet_id_suffix, pad(14)] - movdn.7 - # => [[faucet_id_prefix, faucet_id_suffix, 0, 0], [0, 0, 0, IS_FAUCET_REGISTERED_FLAG], pad(9)] + push.0.0 movdn.3 movdn.2 + # => [[faucet_id_prefix, faucet_id_suffix, 0, 0], origin_token_addr(5), faucet_id_prefix, faucet_id_suffix, pad(9)] + + push.IS_FAUCET_REGISTERED_FLAG.0.0.0 + # => [[0, 0, 0, 1], [faucet_id_prefix, faucet_id_suffix, 0, 0], origin_token_addr(5), faucet_id_prefix, faucet_id_suffix, pad(9)] + + swapw + # => [[faucet_id_prefix, faucet_id_suffix, 0, 0], [0, 0, 0, 1], origin_token_addr(5), faucet_id_prefix, faucet_id_suffix, pad(9)] - # Place slot ID on top push.FAUCET_REGISTRY_SLOT[0..2] - # Stack: [slot0, slot1, [prefix, suffix, 0, 0], [0, 0, 0, 1], pad(9)] + exec.native_account::set_map_item + # => [OLD_VALUE, origin_token_addr(5), faucet_id_prefix, faucet_id_suffix, pad(9)] + + dropw + # => [origin_token_addr(5), faucet_id_prefix, faucet_id_suffix, pad(9)] + + # --- 2. Register token address → faucet ID in token_registry --- + + # Hash the token address + exec.hash_token_address + # => [TOKEN_ADDR_HASH, faucet_id_prefix, faucet_id_suffix, pad(10)] + # Build VALUE = [faucet_id_prefix, faucet_id_suffix, 0, 0] + movup.5 movup.5 + # => [faucet_id_prefix, faucet_id_suffix, TOKEN_ADDR_HASH, pad(9)] + + push.0.0 movup.3 movup.3 + # => [faucet_id_prefix, faucet_id_suffix, 0, 0, TOKEN_ADDR_HASH, pad(9)] + + swapw + # => [TOKEN_ADDR_HASH, faucet_id_prefix, faucet_id_suffix, 0, 0, pad(9)] + + push.TOKEN_REGISTRY_SLOT[0..2] exec.native_account::set_map_item - # => [OLD_VALUE(4), pad(9)] + # => [OLD_VALUE, pad(12)] dropw + # => [pad(16)] end #! Asserts that a faucet is registered in the bridge's faucet registry. @@ -160,6 +194,61 @@ pub proc assert_faucet_registered # => [] end +#! Looks up the faucet account ID for a given origin token address. +#! +#! Hashes the origin token address (5 felts) using RPO256 and looks up the result +#! in the token_registry map. Returns the faucet account ID. +#! +#! Inputs: [origin_token_addr(5)] +#! Outputs: [faucet_id_prefix, faucet_id_suffix] +#! +#! Panics if: +#! - the token address is not registered in the token registry. +#! +#! Invocation: exec +pub proc lookup_faucet_by_token_address + # Hash the token address + exec.hash_token_address + # => [TOKEN_ADDR_HASH] + + push.TOKEN_REGISTRY_SLOT[0..2] + exec.active_account::get_map_item + # => [faucet_id_prefix, faucet_id_suffix, 0, 0] + + # Assert the token is registered: faucet_id_prefix is always non-zero for valid account IDs. + dup.1 dup.1 movup.5 movup.5 + # => [0, 0, faucet_id_prefix, faucet_id_suffix, faucet_id_prefix, faucet_id_suffix] + + exec.account_id::is_equal + # => [is_id_zero, faucet_id_prefix, faucet_id_suffix] + + assertz.err=ERR_TOKEN_NOT_REGISTERED + # => [faucet_id_prefix, faucet_id_suffix] +end + +# HELPER PROCEDURES +# ================================================================================================= + +#! Hashes a 5-felt origin token address using RPO256. +#! +#! Writes the 5 felts to memory and computes the RPO hash. +#! +#! Inputs: [origin_token_addr(5)] +#! Outputs: [TOKEN_ADDR_HASH] +#! +#! Invocation: exec +proc hash_token_address + # Write origin_token_addr[5] to memory for hashing + mem_storew_le.TOKEN_ADDR_HASH_PTR dropw + push.TOKEN_ADDR_HASH_PTR add.4 mem_store + # => [] + + # Hash the token address: rpo256::hash_elements(num_elements=5, start_ptr) + push.5 push.TOKEN_ADDR_HASH_PTR + exec.rpo256::hash_elements + # => [TOKEN_ADDR_HASH] +end + #! Asserts that the note sender matches the bridge admin stored in account storage. #! #! Reads the bridge admin account ID from BRIDGE_ADMIN_SLOT and compares it against diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_in.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_in.masm index 53a17ded37..09c52ac01a 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_in.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_in.masm @@ -1,9 +1,17 @@ use miden::agglayer::bridge::bridge_config use miden::agglayer::bridge::leaf_utils use miden::agglayer::common::utils +use miden::agglayer::common::asset_conversion +use miden::agglayer::common::eth_address use miden::core::crypto::hashes::keccak256 +use miden::core::crypto::hashes::rpo256 use miden::core::mem use miden::core::word +use miden::protocol::note +use miden::protocol::output_note +use miden::standards::note_tag +use miden::standards::attachments::network_account_target +use miden::standards::note::execution_hint::ALWAYS # TYPE ALIASES # ================================================================================================= @@ -19,11 +27,38 @@ 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" const ERR_SMT_ROOT_VERIFICATION_FAILED = "merkle proof verification failed: provided SMT root does not match the computed root" +const ERR_INVALID_CLAIM_PROOF = "invalid claim proof" # CONSTANTS # ================================================================================================= -# Memory pointers for proof data layout +# Data sizes +# ------------------------------------------------------------------------------------------------- + +const PROOF_DATA_WORD_LEN = 134 +# the number of words (4 felts each) in the advice map leaf data +const LEAF_DATA_NUM_WORDS = 8 +const CLAIM_PROOF_DATA_WORD_LEN = 134 +const CLAIM_LEAF_DATA_WORD_LEN = 8 + +# MINT note storage layout (public mode, 18 items): +# [0]: tag, [1]: amount, [2]: attachment_kind, [3]: attachment_scheme, +# [4-7]: ATTACHMENT, [8-11]: P2ID_SCRIPT_ROOT, [12-15]: SERIAL_NUM, +# [16]: account_id_suffix, [17]: account_id_prefix +const MINT_NOTE_NUM_STORAGE_ITEMS = 18 + +# P2ID output note constants +const P2ID_NOTE_NUM_STORAGE_ITEMS = 2 +const OUTPUT_NOTE_TYPE_PUBLIC = 1 + +# P2ID attachment constants (the P2ID note created by the faucet has no attachment) +const P2ID_ATTACHMENT_KIND_NONE = 0 +const P2ID_ATTACHMENT_SCHEME_NONE = 0 + +# Global memory pointers +# ------------------------------------------------------------------------------------------------- + +# Memory pointers for proof data layout (used by verify_leaf / get_leaf_value) const PROOF_DATA_PTR = 0 const SMT_PROOF_LOCAL_EXIT_ROOT_PTR = 0 # local SMT proof is first const GLOBAL_INDEX_PTR = PROOF_DATA_PTR + 2 * 256 # 512 @@ -33,18 +68,60 @@ const MAINNET_EXIT_ROOT_PTR = EXIT_ROOTS_PTR # it's the first exit root # the memory address where leaf data is stored for get_leaf_value const LEAF_DATA_START_PTR = 0 -# The offset of the first half of the current Keccak256 hash value in the local memory of the -# `calculate_root` procedure. -const CUR_HASH_LO_LOCAL = 0 - -# The offset of the second half of the current Keccak256 hash value in the local memory of the -# `calculate_root` procedure. -const CUR_HASH_HI_LOCAL = 4 - -# Data sizes -const PROOF_DATA_WORD_LEN = 134 -# the number of words (4 felts each) in the advice map leaf data -const LEAF_DATA_NUM_WORDS = 8 +# Memory pointers for piped advice map data (used by claim procedure) +const CLAIM_PROOF_DATA_START_PTR = 0 +const CLAIM_LEAF_DATA_START_PTR = 536 +const CLAIM_OUTPUT_NOTE_FAUCET_AMOUNT = 568 + +# Memory addresses for stored keys (used by claim procedure) +const CLAIM_PROOF_DATA_KEY_MEM_ADDR = 700 +const CLAIM_LEAF_DATA_KEY_MEM_ADDR = 704 + +# Memory addresses for leaf data fields (derived from leaf data layout at CLAIM_LEAF_DATA_START_PTR=536) +const ORIGIN_TOKEN_ADDRESS_0 = 538 +const ORIGIN_TOKEN_ADDRESS_1 = 539 +const ORIGIN_TOKEN_ADDRESS_2 = 540 +const ORIGIN_TOKEN_ADDRESS_3 = 541 +const ORIGIN_TOKEN_ADDRESS_4 = 542 +const DESTINATION_ADDRESS_0 = 544 +const DESTINATION_ADDRESS_1 = 545 +const DESTINATION_ADDRESS_2 = 546 +const DESTINATION_ADDRESS_3 = 547 +const DESTINATION_ADDRESS_4 = 548 +const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_0 = 549 +const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_1 = 550 +const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_2 = 551 +const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_3 = 552 +const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_4 = 553 +const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_5 = 554 +const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_6 = 555 +const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_7 = 556 + +# Memory addresses for MINT note output construction +const MINT_NOTE_STORAGE_MEM_ADDR_0 = 800 +const MINT_NOTE_STORAGE_DEST_TAG = 800 +const MINT_NOTE_STORAGE_NATIVE_AMOUNT = 801 +const MINT_NOTE_STORAGE_ATTACHMENT_KIND = 802 +const MINT_NOTE_STORAGE_ATTACHMENT_SCHEME = 803 +const MINT_NOTE_STORAGE_ATTACHMENT = 804 +const MINT_NOTE_STORAGE_OUTPUT_SCRIPT_ROOT = 808 +const MINT_NOTE_STORAGE_OUTPUT_SERIAL_NUM = 812 +const MINT_NOTE_STORAGE_OUTPUT_NOTE_INPUT = 816 + +# Local memory offsets +# ------------------------------------------------------------------------------------------------- + +# Offsets in the local memory of the `calculate_root` procedure +const CUR_HASH_LO_LOCAL = 0 # first half of the current Keccak256 hash value +const CUR_HASH_HI_LOCAL = 4 # second half of the current Keccak256 hash value + +# Offsets in the local memory of the `claim` procedure +const CLAIM_PREFIX_MEM_LOC = 0 +const CLAIM_SUFFIX_MEM_LOC = 1 + +# Offsets in the local memory of the `create_mint_note_with_attachment` procedure +const CREATE_MINT_FAUCET_PREFIX_LOC = 0 +const CREATE_MINT_FAUCET_SUFFIX_LOC = 1 # PUBLIC INTERFACE # ================================================================================================= @@ -189,6 +266,76 @@ pub proc verify_merkle_proof( # => [verification_flag] end +#! Validates a claim against the AggLayer bridge and creates a MINT note for the aggfaucet. +#! +#! This procedure is called by the CLAIM note script. It validates the Merkle proof and then +#! looks up the faucet account ID from the token registry using the origin token address from +#! the leaf data, and creates a MINT note targeting the aggfaucet. +#! +#! The MINT note uses the standard MINT note pattern (public mode) with 18 storage items. +#! See `write_mint_note_storage` for the full storage layout. +#! +#! Inputs: [PROOF_DATA_KEY, LEAF_DATA_KEY, faucet_mint_amount, pad(7)] +#! Outputs: [pad(16)] +#! +#! Advice map: { +#! PROOF_DATA_KEY => [ +#! smtProofLocalExitRoot[256], // SMT proof for local exit root (256 felts) +#! smtProofRollupExitRoot[256], // SMT proof for rollup exit root (256 felts) +#! globalIndex[8], // Global index (8 felts, uint256 as 8 u32 felts) +#! mainnetExitRoot[8], // Mainnet exit root hash (8 felts) +#! rollupExitRoot[8], // Rollup exit root hash (8 felts) +#! ], +#! LEAF_DATA_KEY => [ +#! leafType[1], // Leaf type (1 felt, uint32) +#! originNetwork[1], // Origin network identifier (1 felt, uint32) +#! originTokenAddress[5], // Origin token address (5 felts) +#! destinationNetwork[1], // Destination network identifier (1 felt, uint32) +#! destinationAddress[5], // Destination address (5 felts) +#! amount[8], // Amount of tokens (8 felts, uint256 as 8 u32 felts) +#! metadata[8], // ABI encoded metadata (8 felts, fixed size) +#! padding[3], // padding (3 felts) +#! ], +#! } +#! +#! Panics if: +#! - the Merkle proof validation fails. +#! - the origin token address is not registered in the bridge's token registry. +#! +#! Invocation: call +@locals(2) # 0: dest_prefix, 1: dest_suffix +pub proc claim + # Write output note faucet amount to memory + movup.8 mem_store.CLAIM_OUTPUT_NOTE_FAUCET_AMOUNT + # => [PROOF_DATA_KEY, LEAF_DATA_KEY, pad(8)] + + # Check AdviceMap values hash to keys & write CLAIM inputs & DATA_KEYs to global memory + exec.claim_batch_pipe_double_words + # => [pad(16)] + + exec.get_destination_account_id_data + loc_store.CLAIM_PREFIX_MEM_LOC loc_store.CLAIM_SUFFIX_MEM_LOC + # => [pad(7)] + + # VALIDATE CLAIM - call verify_leaf_bridge directly (bridge is the native account) + mem_loadw_be.CLAIM_PROOF_DATA_KEY_MEM_ADDR + # => [PROOF_DATA_KEY, pad(7)] + + swapw mem_loadw_be.CLAIM_LEAF_DATA_KEY_MEM_ADDR + # => [LEAF_DATA_KEY, PROOF_DATA_KEY, pad(7)] + + # Call verify_leaf_bridge directly (no FPI needed - bridge is the native account) + exec.verify_leaf_bridge + # => [pad(16)] + + # Build MINT output note targeting the aggfaucet + loc_load.CLAIM_SUFFIX_MEM_LOC loc_load.CLAIM_PREFIX_MEM_LOC + # => [prefix, suffix, pad(16)] + + exec.build_mint_output_note + # => [pad(16)] +end + # HELPER PROCEDURES # ================================================================================================= @@ -286,6 +433,267 @@ proc verify_leaf # => [] end +# Inputs: [PROOF_DATA_KEY, LEAF_DATA_KEY] +# Outputs: [] +proc claim_batch_pipe_double_words + # 1) Verify PROOF_DATA_KEY + mem_storew_be.CLAIM_PROOF_DATA_KEY_MEM_ADDR + adv.push_mapval + # => [PROOF_DATA_KEY] + + push.CLAIM_PROOF_DATA_START_PTR push.CLAIM_PROOF_DATA_WORD_LEN + exec.mem::pipe_double_words_preimage_to_memory drop + + # 2) Verify LEAF_DATA_KEY + mem_storew_be.CLAIM_LEAF_DATA_KEY_MEM_ADDR + adv.push_mapval + # => [LEAF_DATA_KEY] + + push.CLAIM_LEAF_DATA_START_PTR push.CLAIM_LEAF_DATA_WORD_LEN + exec.mem::pipe_double_words_preimage_to_memory drop +end + +#! Extracts the destination account ID as address[5] from memory. +#! +#! This procedure reads the destination address from the leaf data and converts it from +#! Ethereum address format to AccountId format (prefix, suffix). +#! +#! Inputs: [] +#! Outputs: [prefix, suffix] +#! +#! Invocation: exec +proc get_destination_account_id_data + mem_load.DESTINATION_ADDRESS_4 + mem_load.DESTINATION_ADDRESS_3 + mem_load.DESTINATION_ADDRESS_2 + mem_load.DESTINATION_ADDRESS_1 + mem_load.DESTINATION_ADDRESS_0 + # => [address[5]] + + exec.eth_address::to_account_id + # => [prefix, suffix] +end + +# Inputs: [] +# Outputs: [U256[0], U256[1]] +proc get_raw_claim_amount + mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_7 + mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_6 + mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_5 + mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_4 + mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_3 + mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_2 + mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_1 + mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_0 +end + +#! Reads the origin token address (5 felts) from the leaf data in memory. +#! +#! Inputs: [] +#! Outputs: [origin_token_addr(5)] +#! +#! Invocation: exec +proc get_origin_token_address + mem_load.ORIGIN_TOKEN_ADDRESS_4 + mem_load.ORIGIN_TOKEN_ADDRESS_3 + mem_load.ORIGIN_TOKEN_ADDRESS_2 + mem_load.ORIGIN_TOKEN_ADDRESS_1 + mem_load.ORIGIN_TOKEN_ADDRESS_0 + # => [origin_token_addr(5)] +end + +#! Builds a PUBLIC MINT output note targeting the aggfaucet. +#! +#! The MINT note uses public mode (18 storage items) so the AggFaucet creates +#! a PUBLIC P2ID note on consumption. This procedure orchestrates three steps: +#! 1. Write all 18 MINT note storage items to global memory. +#! 2. Build the MINT note recipient digest from the storage. +#! 3. Look up the faucet, create the output note, and set the attachment. +#! +#! Inputs: [prefix, suffix] +#! Outputs: [] +#! +#! Where: +#! - prefix, suffix: destination account ID +#! +#! Invocation: exec +proc build_mint_output_note + # Step 1: Write all 18 MINT note storage items to global memory + exec.write_mint_note_storage + # => [] + + # Step 2: Build the MINT note recipient digest + exec.build_mint_recipient + # => [MINT_RECIPIENT] + + # Step 3: Create the output note and set the faucet attachment + exec.create_mint_note_with_attachment + # => [] +end + +#! Writes all 18 MINT note storage items to global memory. +#! +#! Storage layout: +#! - [0]: tag (note tag for the P2ID output note, targeting the destination account) +#! - [1]: amount (the scaled-down Miden amount to mint) +#! - [2]: attachment_kind (0 = no attachment) +#! - [3]: attachment_scheme (0 = no attachment) +#! - [4-7]: ATTACHMENT ([0, 0, 0, 0]) +#! - [8-11]: P2ID_SCRIPT_ROOT (script root of the P2ID note) +#! - [12-15]: SERIAL_NUM (serial number for the P2ID note, derived from PROOF_DATA_KEY) +#! - [16]: account_id_suffix (destination account suffix) +#! - [17]: account_id_prefix (destination account prefix) +#! +#! Inputs: [prefix, suffix] +#! Outputs: [] +#! +#! Invocation: exec +proc write_mint_note_storage + # Write P2ID storage items first (before prefix is consumed): [16..17] + # Write account_id_suffix [16] + dup.1 mem_store.MINT_NOTE_STORAGE_OUTPUT_NOTE_INPUT + # => [prefix, suffix] + + # Write account_id_prefix [17] + dup push.MINT_NOTE_STORAGE_OUTPUT_NOTE_INPUT add.1 mem_store + # => [prefix, suffix] + + swap drop + # => [prefix] + + # Get the native amount from the pre-computed miden_claim_amount + mem_load.CLAIM_OUTPUT_NOTE_FAUCET_AMOUNT + # => [native_amount, prefix] + + # Compute the note tag for the destination account (consumes prefix) + swap + # => [prefix, native_amount] + + exec.note_tag::create_account_target + # => [dest_tag, native_amount] + + # Write tag to MINT note storage [0] + mem_store.MINT_NOTE_STORAGE_DEST_TAG + # => [native_amount] + + # Write amount to MINT note storage [1] + mem_store.MINT_NOTE_STORAGE_NATIVE_AMOUNT + # => [] + + # Write P2ID attachment fields (the P2ID note has no attachment) + # attachment_kind = NONE [2] + push.P2ID_ATTACHMENT_KIND_NONE mem_store.MINT_NOTE_STORAGE_ATTACHMENT_KIND + # => [] + + # attachment_scheme = NONE [3] + push.P2ID_ATTACHMENT_SCHEME_NONE mem_store.MINT_NOTE_STORAGE_ATTACHMENT_SCHEME + # => [] + + # ATTACHMENT = empty word [4..7] + padw mem_storew_be.MINT_NOTE_STORAGE_ATTACHMENT dropw + # => [] + + # Write P2ID_SCRIPT_ROOT to MINT note storage [8..11] + procref.::miden::standards::notes::p2id::main + # => [P2ID_SCRIPT_ROOT] + + mem_storew_be.MINT_NOTE_STORAGE_OUTPUT_SCRIPT_ROOT dropw + # => [] + + # Write SERIAL_NUM (PROOF_DATA_KEY) to MINT note storage [12..15] + mem_loadw_be.CLAIM_PROOF_DATA_KEY_MEM_ADDR + # => [SERIAL_NUM] + + mem_storew_be.MINT_NOTE_STORAGE_OUTPUT_SERIAL_NUM dropw + # => [] +end + +#! Builds the MINT note recipient digest from the storage items already written to global memory. +#! +#! Uses the MINT note script root and PROOF_DATA_KEY as serial number, then calls +#! `note::build_recipient` with the storage pointer and item count. +#! +#! Inputs: [] +#! Outputs: [MINT_RECIPIENT] +#! +#! Invocation: exec +proc build_mint_recipient + # Get the MINT note script root + procref.::miden::standards::notes::mint::main + # => [MINT_SCRIPT_ROOT] + + # Generate a serial number for the MINT note (use PROOF_DATA_KEY) + swapw mem_loadw_be.CLAIM_PROOF_DATA_KEY_MEM_ADDR + # => [MINT_SERIAL_NUM, MINT_SCRIPT_ROOT] + + # Build the MINT note recipient + push.MINT_NOTE_NUM_STORAGE_ITEMS + # => [num_storage_items, MINT_SERIAL_NUM, MINT_SCRIPT_ROOT] + + push.MINT_NOTE_STORAGE_MEM_ADDR_0 + # => [storage_ptr, num_storage_items, MINT_SERIAL_NUM, MINT_SCRIPT_ROOT] + + exec.note::build_recipient + # => [MINT_RECIPIENT] +end + +#! Creates the MINT output note and sets the NetworkAccountTarget attachment on it. +#! +#! Looks up the faucet account ID from the origin token address in the leaf data, +#! creates a public output note with no assets, and sets the attachment so only the +#! target faucet can consume the note. +#! +#! Inputs: [MINT_RECIPIENT] +#! Outputs: [] +#! +#! Invocation: exec +@locals(2) # 0: faucet_prefix, 1: faucet_suffix +proc create_mint_note_with_attachment + # Create the MINT output note targeting the faucet + push.OUTPUT_NOTE_TYPE_PUBLIC + # => [note_type, MINT_RECIPIENT] + + # Look up the faucet account ID from the origin token address in the leaf data + exec.get_origin_token_address + # => [origin_token_addr(5), note_type, MINT_RECIPIENT] + + exec.bridge_config::lookup_faucet_by_token_address + # => [faucet_id_prefix, faucet_id_suffix, note_type, MINT_RECIPIENT] + + loc_store.CREATE_MINT_FAUCET_PREFIX_LOC loc_store.CREATE_MINT_FAUCET_SUFFIX_LOC + # => [note_type, MINT_RECIPIENT] + + # Compute note tag targeting the faucet account + loc_load.CREATE_MINT_FAUCET_PREFIX_LOC + # => [faucet_prefix, note_type, MINT_RECIPIENT] + + exec.note_tag::create_account_target + # => [faucet_tag, note_type, MINT_RECIPIENT] + + # Create the output note (no assets - MINT notes carry no assets) + exec.output_note::create + # => [note_idx] + + # Set the attachment on the MINT note to target the faucet account + # NetworkAccountTarget attachment: targets the faucet so only it can consume the note + # network_account_target::new expects [prefix, suffix, exec_hint] + # and returns [attachment_scheme, attachment_kind, ATTACHMENT] + push.ALWAYS # exec_hint = ALWAYS + loc_load.CREATE_MINT_FAUCET_SUFFIX_LOC + loc_load.CREATE_MINT_FAUCET_PREFIX_LOC + # => [faucet_prefix, faucet_suffix, exec_hint, note_idx] + + exec.network_account_target::new + # => [attachment_scheme, attachment_kind, ATTACHMENT, note_idx] + + # Rearrange for set_attachment: [note_idx, attachment_scheme, attachment_kind, ATTACHMENT] + movup.6 + # => [note_idx, attachment_scheme, attachment_kind, ATTACHMENT(4)] + + exec.output_note::set_attachment + # => [] +end + #! Computes the root of the SMT based on the provided Merkle path, leaf value and leaf index. #! #! Inputs: [LEAF_VALUE_LO, LEAF_VALUE_HI, merkle_path_ptr, leaf_idx] diff --git a/crates/miden-agglayer/asm/agglayer/faucet/mod.masm b/crates/miden-agglayer/asm/agglayer/faucet/mod.masm index d3b2913627..5fec0122db 100644 --- a/crates/miden-agglayer/asm/agglayer/faucet/mod.masm +++ b/crates/miden-agglayer/asm/agglayer/faucet/mod.masm @@ -1,4 +1,3 @@ -use miden::agglayer::bridge::bridge_in use miden::core::sys use miden::agglayer::common::utils use miden::agglayer::common::asset_conversion @@ -6,78 +5,19 @@ use miden::agglayer::common::eth_address use miden::protocol::active_account use miden::protocol::active_note use miden::standards::faucets -use miden::standards::note_tag -use miden::protocol::note -use miden::protocol::tx -use miden::core::mem +use miden::standards::faucets::network_fungible +use miden::standards::access::ownable use miden::core::word -# ERRORS -# ================================================================================================= - -const ERR_INVALID_CLAIM_PROOF = "invalid claim proof" - # CONSTANTS # ================================================================================================= -# Storage slots -# The slot in this component's storage layout where the bridge account ID is stored. -const BRIDGE_ID_SLOT = word("miden::agglayer::faucet") # Storage slots for conversion metadata. # Slot 1: [addr_felt0, addr_felt1, addr_felt2, addr_felt3] — first 4 felts of origin token address const CONVERSION_INFO_1_SLOT = word("miden::agglayer::faucet::conversion_info_1") # Slot 2: [addr_felt4, origin_network, scale, 0] — remaining address felt + origin network + scale const CONVERSION_INFO_2_SLOT = word("miden::agglayer::faucet::conversion_info_2") -# Memory pointers for piped advice map data -const PROOF_DATA_START_PTR = 0 -const LEAF_DATA_START_PTR = 536 -const OUTPUT_NOTE_DATA_START_PTR = 568 - -# Memory addresses for stored keys -const PROOF_DATA_KEY_MEM_ADDR = 700 -const LEAF_DATA_KEY_MEM_ADDR = 704 -const OUTPUT_NOTE_DATA_MEM_ADDR = 708 -const CLAIM_NOTE_DATA_MEM_ADDR = 712 - -# Memory addresses for output note fields (derived from leaf data layout) -const DESTINATION_ADDRESS_0 = 544 -const DESTINATION_ADDRESS_1 = 545 -const DESTINATION_ADDRESS_2 = 546 -const DESTINATION_ADDRESS_3 = 547 -const DESTINATION_ADDRESS_4 = 548 -const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_0 = 549 -const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_1 = 550 -const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_2 = 551 -const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_3 = 552 -const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_4 = 553 -const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_5 = 554 -const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_6 = 555 -const OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_7 = 556 -const OUTPUT_NOTE_STORAGE_MEM_ADDR = 0 -const OUTPUT_NOTE_FAUCET_AMOUNT = 568 - -# Memory locals in claim -const CLAIM_PREFIX_MEM_LOC = 8 -const CLAIM_SUFFIX_MEM_LOC = 9 -const CLAIM_AMOUNT_MEM_LOC_0 = 0 -const CLAIM_AMOUNT_MEM_LOC_1 = 4 - -# Memory locals in build_p2id_output_note -const BUILD_P2ID_AMOUNT_MEM_LOC_0 = 0 -const BUILD_P2ID_AMOUNT_MEM_LOC_1 = 4 -const BUILD_P2ID_PREFIX_MEM_LOC = 8 - -# Data sizes -const PROOF_DATA_WORD_LEN = 134 -const LEAF_DATA_WORD_LEN = 8 -const OUTPUT_NOTE_DATA_WORD_LEN = 2 - -# P2ID output note constants -const P2ID_NOTE_NUM_STORAGE_ITEMS = 2 -const OUTPUT_NOTE_TYPE_PUBLIC = 1 -const OUTPUT_NOTE_AUX = 0 - # PUBLIC INTERFACE # ================================================================================================= @@ -190,253 +130,6 @@ pub proc asset_to_origin_asset exec.sys::truncate_stack end -# CLAIM PROCEDURES -# ================================================================================================= - -#! Inputs: [LEAF_DATA_KEY, PROOF_DATA_KEY] -#! Outputs: [] -#! -#! Panics if: -#! - the bridge account ID is not properly configured in storage. -#! - the foreign procedure invocation fails. -#! - the claim proof validation fails. -#! -#! Invocation: exec -proc validate_claim - # get bridge_in::verify_leaf_bridge procedure MAST root - procref.bridge_in::verify_leaf_bridge - # => [BRIDGE_PROC_MAST_ROOT, LEAF_DATA_KEY, PROOF_DATA_KEY] - - push.BRIDGE_ID_SLOT[0..2] - # => [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, LEAF_DATA_KEY, PROOF_DATA_KEY] - - movup.2 drop movup.2 drop - # => [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 - # => [] -end - -# Inputs: [] -# Outputs: [U256[0], U256[1]] -proc get_raw_claim_amount - mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_7 - mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_6 - mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_5 - mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_4 - mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_3 - mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_2 - mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_1 - mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_0 -end - -# Inputs: [U256[0], U256[1]] -# Outputs: [amount] -proc scale_down_amount - repeat.7 drop end -end - -# Inputs: [PROOF_DATA_KEY, LEAF_DATA_KEY] -# Outputs: [] -proc batch_pipe_double_words - # 1) Verify PROOF_DATA_KEY - mem_storew_be.PROOF_DATA_KEY_MEM_ADDR - adv.push_mapval - # => [PROOF_DATA_KEY] - - push.PROOF_DATA_START_PTR push.PROOF_DATA_WORD_LEN - exec.mem::pipe_double_words_preimage_to_memory drop - - # 2) Verify LEAF_DATA_KEY - mem_storew_be.LEAF_DATA_KEY_MEM_ADDR - adv.push_mapval - # => [LEAF_DATA_KEY] - - push.LEAF_DATA_START_PTR push.LEAF_DATA_WORD_LEN - exec.mem::pipe_double_words_preimage_to_memory drop -end - -#! Extracts the destination account ID as address[5] from memory. -#! -#! This procedure reads the destination address from the leaf data and converts it from -#! Ethereum address format to AccountId format (prefix, suffix). -#! -#! Inputs: [] -#! Outputs: [prefix, suffix] -#! -#! Invocation: exec -proc get_destination_account_id_data - mem_load.DESTINATION_ADDRESS_4 - mem_load.DESTINATION_ADDRESS_3 - mem_load.DESTINATION_ADDRESS_2 - mem_load.DESTINATION_ADDRESS_1 - mem_load.DESTINATION_ADDRESS_0 - # => [address[5]] - - exec.eth_address::to_account_id - # => [prefix, suffix] -end - -#! Builds a P2ID output note for the claim recipient. -#! -#! This procedure expects the claim data to be already written to memory via batch_pipe_double_words. -#! It reads the destination account ID, amount, and other note parameters from memory to construct -#! the output note. -#! -#! Inputs: [prefix, suffix, AMOUNT[0], AMOUNT[1]] -#! Outputs: [] -#! -#! WARNING: This procedure currently assumes the claim amount fits within 128 bits (i.e. AMOUNT[1] -#! is all zeros). This assumption holds for all practical token amounts but is not explicitly -#! enforced here. See the TODO below. -#! -#! TODO: Add an explicit assertion that AMOUNT[1] is zero. -#! -#! Note: This procedure will be refactored in a follow-up to use leaf data to build the output note. -@locals(9) -proc build_p2id_output_note - # save prefix to local memory for later use in note tag computation - dup loc_store.BUILD_P2ID_PREFIX_MEM_LOC - - # write destination account id into memory for use in note::build_recipient - push.OUTPUT_NOTE_STORAGE_MEM_ADDR add.1 mem_store mem_store.OUTPUT_NOTE_STORAGE_MEM_ADDR - - # store amount in memory locals for use in faucets::distribute - loc_storew_be.BUILD_P2ID_AMOUNT_MEM_LOC_0 dropw loc_storew_be.BUILD_P2ID_AMOUNT_MEM_LOC_1 dropw - # => [pad(16)] - - # Build P2ID output note - procref.::miden::standards::notes::p2id::main - # => [SCRIPT_ROOT] - - # Use PROOF_DATA_KEY as the P2ID serial number - swapw mem_loadw_be.PROOF_DATA_KEY_MEM_ADDR - # => [SERIAL_NUM, SCRIPT_ROOT] - - push.P2ID_NOTE_NUM_STORAGE_ITEMS - # => [note_num_storage_items, SERIAL_NUM, SCRIPT_ROOT] - - push.OUTPUT_NOTE_STORAGE_MEM_ADDR - # => [storage_ptr, note_num_storage_items, SERIAL_NUM, SCRIPT_ROOT] - - exec.note::build_recipient - # => [RECIPIENT] - - push.OUTPUT_NOTE_TYPE_PUBLIC - # => [note_type, RECIPIENT] - - # Compute note tag from destination account prefix (read from local memory) - loc_load.BUILD_P2ID_PREFIX_MEM_LOC - # => [account_id_prefix, note_type, RECIPIENT] - - exec.note_tag::create_account_target - # => [tag, note_type, RECIPIENT] - - padw loc_loadw_be.BUILD_P2ID_AMOUNT_MEM_LOC_1 padw loc_loadw_be.BUILD_P2ID_AMOUNT_MEM_LOC_0 - # => [AMOUNT[0], AMOUNT[1], tag, note_type, RECIPIENT] - - mem_load.OUTPUT_NOTE_FAUCET_AMOUNT movdn.8 - # => [AMOUNT[0], AMOUNT[1], native_amount, tag, note_type, RECIPIENT] - - exec.get_scale movdn.8 - # => [AMOUNT[0], AMOUNT[1], scale, native_amount, tag, note_type, RECIPIENT] - - exec.asset_conversion::verify_u256_to_native_amount_conversion - # => [amount, tag, note_type, RECIPIENT] - - exec.faucets::distribute - # => [pad(16)] -end - -#! Validates a claim against the AggLayer bridge and mints the corresponding asset to the recipient. -#! -#! This procedure validates the rollup exit root Merkle Proof via FPI against the agglayer bridge, -#! and if validation passes, mints the asset and creates an output note for the recipient. -#! -#! WARNING: The EVM claim asset amount is currently assumed to fit within 128 bits. See the WARNING in -#! build_p2id_output_note for details. -#! -#! TODO: Expand this description to cover the double-spend protection mechanism in detail. -#! Double-spend can be prevented in two ways: -#! 1) While it's possible to create two identical P2ID notes, only one can actually be consumed. -#! If the claim note is consumed twice, only one P2ID output note will be successfully consumed. -#! 2) We can have a mapping in the bridge or in the faucet that stores consumed claim proofs -#! as a hash -> bool value (similar to how it's done in the agglayer solidity contract). -#! -#! Inputs: [PROOF_DATA_KEY, LEAF_DATA_KEY, faucet_mint_amount, pad(7)] -#! Outputs: [pad(16)] -#! -#! Advice map: { -#! PROOF_DATA_KEY => [ -#! smtProofLocalExitRoot[256], // SMT proof for local exit root (256 felts, bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH]) -#! smtProofRollupExitRoot[256], // SMT proof for rollup exit root (256 felts, bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH]) -#! globalIndex[8], // Global index (8 felts, uint256 as 8 u32 felts) -#! mainnetExitRoot[8], // Mainnet exit root hash (8 felts, bytes32 as 8 u32 felts) -#! rollupExitRoot[8], // Rollup exit root hash (8 felts, bytes32 as 8 u32 felts) -#! ], -#! LEAF_DATA_KEY => [ -#! leafType[1], // Leaf type (1 felt, uint8) -#! 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) -#! ], -#! } -#! -#! Panics if: -#! - the rollup exit root Merkle Proof validation via FPI fails. -#! - any of the validations in faucets::distribute fail. -#! -#! Invocation: call -@locals(10) # 2 for prefix and suffix, 8 for amount -pub proc claim - # Write output note faucet amount to memory - movup.8 mem_store.OUTPUT_NOTE_FAUCET_AMOUNT - # => [PROOF_DATA_KEY, LEAF_DATA_KEY, pad(7)] - - # Check AdviceMap values hash to keys & write CLAIM inputs & DATA_KEYs to global memory - exec.batch_pipe_double_words - # => [pad(7)] - - # validate_claim will overwrite memory in-place, so we need to load the account and amount - # before calling validate_claim and store it in memory locals - exec.get_destination_account_id_data - loc_store.CLAIM_PREFIX_MEM_LOC loc_store.CLAIM_SUFFIX_MEM_LOC - # => [pad(7)] - - exec.get_raw_claim_amount - loc_storew_be.CLAIM_AMOUNT_MEM_LOC_0 dropw loc_storew_be.CLAIM_AMOUNT_MEM_LOC_1 dropw - # => [pad(7)] - - # VALIDATE CLAIM - mem_loadw_be.PROOF_DATA_KEY_MEM_ADDR - # => [PROOF_DATA_KEY, pad(7)] - - swapw mem_loadw_be.LEAF_DATA_KEY_MEM_ADDR - # => [LEAF_DATA_KEY, PROOF_DATA_KEY, pad(7)] - - # Errors on invalid proof - exec.validate_claim - # => [pad(16)] - - # Create P2ID output note - loc_loadw_be.CLAIM_AMOUNT_MEM_LOC_1 swapw loc_loadw_be.CLAIM_AMOUNT_MEM_LOC_0 - # => [AMOUNT[0], AMOUNT[1], pad(8)] - - loc_load.CLAIM_SUFFIX_MEM_LOC loc_load.CLAIM_PREFIX_MEM_LOC - # => [prefix, suffix, AMOUNT[0], AMOUNT[1], pad(8)] - - exec.build_p2id_output_note - # => [pad(16)] -end - #! Burns the fungible asset from the active note. #! #! This procedure retrieves the asset from the active note and burns it. The note must contain @@ -454,3 +147,15 @@ end #! #! Invocation: call pub use ::miden::standards::faucets::basic_fungible::burn + +#! Re-export the network fungible faucet's distribute procedure. +#! +#! This procedure first checks if the note sender is the owner of the faucet, and then +#! mints the asset and creates an output note with that asset for the recipient. +#! Used by the standard MINT note to mint assets on the aggfaucet. +#! +#! Inputs: [amount, tag, note_type, RECIPIENT, pad(9)] +#! Outputs: [note_idx, pad(15)] +#! +#! Invocation: call +pub use ::miden::standards::faucets::network_fungible::distribute diff --git a/crates/miden-agglayer/asm/components/bridge.masm b/crates/miden-agglayer/asm/components/bridge.masm index 98c8287576..a17b426b91 100644 --- a/crates/miden-agglayer/asm/components/bridge.masm +++ b/crates/miden-agglayer/asm/components/bridge.masm @@ -6,4 +6,5 @@ pub use ::miden::agglayer::bridge::bridge_config::register_faucet pub use ::miden::agglayer::bridge::bridge_config::update_ger pub use ::miden::agglayer::bridge::bridge_in::verify_leaf_bridge +pub use ::miden::agglayer::bridge::bridge_in::claim pub use ::miden::agglayer::bridge::bridge_out::bridge_out diff --git a/crates/miden-agglayer/asm/components/faucet.masm b/crates/miden-agglayer/asm/components/faucet.masm index 641b6089e7..d2ac674a48 100644 --- a/crates/miden-agglayer/asm/components/faucet.masm +++ b/crates/miden-agglayer/asm/components/faucet.masm @@ -1,10 +1,11 @@ # The MASM code of the AggLayer Faucet Account Component. # # This is a thin wrapper that re-exports faucet-related procedures from the -# agglayer library. Only procedures relevant to faucet accounts are exposed -# here, so that bridge-specific procedures (like `bridge_out`) are not -# available on faucet accounts. +# agglayer library. The faucet exposes: +# - `distribute` from the network fungible faucet (for MINT note consumption, with owner verification) +# - `asset_to_origin_asset` for bridge-out FPI +# - `burn` for bridge-out -pub use ::miden::agglayer::faucet::claim +pub use ::miden::agglayer::faucet::distribute pub use ::miden::agglayer::faucet::asset_to_origin_asset pub use ::miden::agglayer::faucet::burn diff --git a/crates/miden-agglayer/asm/note_scripts/CLAIM.masm b/crates/miden-agglayer/asm/note_scripts/CLAIM.masm index 8e420ff285..0ae7df90f3 100644 --- a/crates/miden-agglayer/asm/note_scripts/CLAIM.masm +++ b/crates/miden-agglayer/asm/note_scripts/CLAIM.masm @@ -1,4 +1,4 @@ -use miden::agglayer::faucet -> agg_faucet +use miden::agglayer::bridge::bridge_in -> bridge use miden::protocol::active_note use miden::protocol::note use miden::core::crypto::hashes::keccak256 @@ -22,7 +22,7 @@ const FAUCET_MINT_AMOUNT = 568 const ERR_CLAIM_TARGET_ACCT_MISMATCH = "CLAIM note attachment target account does not match consuming account" -#! Reads claim data from memory and inserts it into the advice map under three separate keys. +#! Reads claim data from memory and inserts it into the advice map under two separate keys. #! #! This procedure organizes the claim note data into three logical groups and inserts them #! into the advice map under separate keys for easier access. @@ -84,13 +84,15 @@ proc write_claim_data_into_advice_map_by_key # => [PROOF_DATA_KEY, LEAF_DATA_KEY] end -#! Agglayer Faucet CLAIM script: claims assets by calling the agglayer faucet's claim function. +#! Agglayer Bridge CLAIM script: claims assets by calling the bridge's claim function. #! -#! This note can only be consumed by the specific agglayer faucet account whose ID is provided -#! in the note attachment (NetworkAccountTarget). Upon consumption, it will create a P2ID note. +#! This note is consumed by the agglayer bridge account whose ID is provided +#! in the note attachment (NetworkAccountTarget). Upon consumption, the bridge validates +#! the Merkle proof, looks up the faucet from the token registry, and creates a MINT note +#! targeting the aggfaucet. #! #! Requires that the account exposes: -#! - agglayer::agglayer_faucet::claim procedure. +#! - agglayer::bridge::bridge_in::claim procedure. #! #! Inputs: [ARGS, pad(12)] #! Outputs: [pad(16)] @@ -138,7 +140,7 @@ begin dropw # => [pad(16)] - # Ensure note attachment targets the consuming faucet account. + # Ensure note attachment targets the consuming bridge account. exec.network_account_target::active_account_matches_target_account assert.err=ERR_CLAIM_TARGET_ACCT_MISMATCH # => [pad(16)] @@ -156,11 +158,12 @@ begin movdn.8 # => [PROOF_DATA_KEY, LEAF_DATA_KEY, faucet_mint_amount, pad(16)] - # call the Aggfaucet Claim procedure - call.agg_faucet::claim + # call the Bridge Claim procedure + call.bridge::claim # => [pad(16), pad(9)] # a call invocation consumes and returns 16 elements, but we had trailing padding - dropw dropw drop + dropw dropw drop + # => [pad(16)] end diff --git a/crates/miden-agglayer/asm/note_scripts/CONFIG_AGG_BRIDGE.masm b/crates/miden-agglayer/asm/note_scripts/CONFIG_AGG_BRIDGE.masm index 8201c9ae5d..e9d9f1f967 100644 --- a/crates/miden-agglayer/asm/note_scripts/CONFIG_AGG_BRIDGE.masm +++ b/crates/miden-agglayer/asm/note_scripts/CONFIG_AGG_BRIDGE.masm @@ -8,20 +8,23 @@ use miden::standards::attachments::network_account_target # ================================================================================================= const STORAGE_START_PTR = 0 -const CONFIG_AGG_BRIDGE_NUM_STORAGE_ITEMS = 2 +const CONFIG_AGG_BRIDGE_NUM_STORAGE_ITEMS = 7 + +const FAUCET_ID_SUFFIX = 6 +const FAUCET_ID_PREFIX = 5 +const ORIGIN_TOKEN_ADDR_4 = 4 # ERRORS # ================================================================================================= -const ERR_CONFIG_AGG_BRIDGE_UNEXPECTED_STORAGE_ITEMS = "CONFIG_AGG_BRIDGE expects exactly 2 note storage items" +const ERR_CONFIG_AGG_BRIDGE_UNEXPECTED_STORAGE_ITEMS = "CONFIG_AGG_BRIDGE expects exactly 7 note storage items" const ERR_CONFIG_AGG_BRIDGE_TARGET_ACCOUNT_MISMATCH = "CONFIG_AGG_BRIDGE note attachment target account does not match consuming account" -#! Registers a faucet in the bridge's faucet registry. +#! Registers a faucet in the bridge's faucet registry and token registry. #! #! This note can only be consumed by the Agglayer Bridge account that is targeted by the note #! attachment, and only if the note was sent by the bridge admin. -#! Upon consumption, it registers the faucet ID from note storage in the bridge's -#! faucet registry. +#! Upon consumption, it registers the faucet ID and origin token address mapping in the bridge. #! #! Requires that the account exposes: #! - agglayer::bridge_config::register_faucet procedure. @@ -29,19 +32,19 @@ const ERR_CONFIG_AGG_BRIDGE_TARGET_ACCOUNT_MISMATCH = "CONFIG_AGG_BRIDGE note at #! Inputs: [ARGS, pad(12)] #! Outputs: [pad(16)] #! -#! NoteStorage layout (2 felts total): -#! - faucet_id_prefix [0]: 1 felt -#! - faucet_id_suffix [1]: 1 felt -#! -#! Where: -#! - faucet_id_prefix: Prefix felt of the faucet account ID to register. -#! - faucet_id_suffix: Suffix felt of the faucet account ID to register. +#! NoteStorage layout (7 felts total): +#! - origin_token_addr_0 [0]: 1 felt +#! - origin_token_addr_1 [1]: 1 felt +#! - origin_token_addr_2 [2]: 1 felt +#! - origin_token_addr_3 [3]: 1 felt +#! - origin_token_addr_4 [4]: 1 felt +#! - faucet_id_prefix [5]: 1 felt +#! - faucet_id_suffix [6]: 1 felt #! #! Panics if: #! - The note attachment target account does not match the consuming bridge account. -#! - The note does not contain exactly 2 storage items. +#! - The note does not contain exactly 7 storage items. #! - The account does not expose the register_faucet procedure. -#! begin dropw # => [pad(16)] @@ -58,11 +61,19 @@ begin push.CONFIG_AGG_BRIDGE_NUM_STORAGE_ITEMS assert_eq.err=ERR_CONFIG_AGG_BRIDGE_UNEXPECTED_STORAGE_ITEMS drop # => [pad(16)] - # Load the faucet ID from memory, replacing the top 4 zeros - mem_loadw_le.STORAGE_START_PTR - # => [faucet_id_prefix, faucet_id_suffix, pad(14)] + # Load origin_token_addr(5) and faucet_id from memory + # register_faucet expects: [origin_token_addr(5), faucet_id_prefix, faucet_id_suffix, pad(9)] - # Register the faucet in the bridge's faucet registry + # Load all 7 values individually in the correct order + mem_load.FAUCET_ID_SUFFIX mem_load.FAUCET_ID_PREFIX mem_load.ORIGIN_TOKEN_ADDR_4 + # => [addr4, faucet_id_prefix, faucet_id_suffix, pad(12)] + + padw mem_loadw_le.STORAGE_START_PTR + # => [addr4, addr3, addr2, addr1, addr0, faucet_id_prefix, faucet_id_suffix, pad(12)] + + # Register the faucet in the bridge call.bridge_config::register_faucet # => [pad(16)] + + dropw dropw end diff --git a/crates/miden-agglayer/src/claim_note.rs b/crates/miden-agglayer/src/claim_note.rs index 699deefdd6..2aae062be6 100644 --- a/crates/miden-agglayer/src/claim_note.rs +++ b/crates/miden-agglayer/src/claim_note.rs @@ -195,12 +195,13 @@ impl TryFrom for NoteStorage { // CLAIM NOTE CREATION // ================================================================================================ -/// Generates a CLAIM note - a note that instructs an agglayer faucet to validate and mint assets. +/// Generates a CLAIM note - a note that instructs the bridge to validate a claim and create +/// a MINT note for the aggfaucet. /// /// # Parameters /// - `storage`: The core storage for creating the CLAIM note -/// - `target_faucet_id`: The account ID of the agglayer faucet that should consume this note. -/// Encoded as a `NetworkAccountTarget` attachment on the note metadata. +/// - `target_bridge_id`: The account ID of the bridge that should consume this note. Encoded as a +/// `NetworkAccountTarget` attachment on the note metadata. /// - `sender_account_id`: The account ID of the CLAIM note creator /// - `rng`: Random number generator for creating the CLAIM note serial number /// @@ -208,13 +209,13 @@ impl TryFrom for NoteStorage { /// Returns an error if note creation fails. pub fn create_claim_note( storage: ClaimNoteStorage, - target_faucet_id: AccountId, + target_bridge_id: AccountId, sender_account_id: AccountId, rng: &mut R, ) -> Result { let note_storage = NoteStorage::try_from(storage.clone())?; - let attachment = NetworkAccountTarget::new(target_faucet_id, NoteExecutionHint::Always) + let attachment = NetworkAccountTarget::new(target_bridge_id, NoteExecutionHint::Always) .map_err(|e| NoteError::other(e.to_string()))? .into(); diff --git a/crates/miden-agglayer/src/config_note.rs b/crates/miden-agglayer/src/config_note.rs index 9a323424fb..8f1b75a2ae 100644 --- a/crates/miden-agglayer/src/config_note.rs +++ b/crates/miden-agglayer/src/config_note.rs @@ -7,9 +7,10 @@ extern crate alloc; use alloc::string::ToString; use alloc::vec; +use alloc::vec::Vec; use miden_assembly::utils::Deserializable; -use miden_core::{Program, Word}; +use miden_core::{Felt, Program, Word}; use miden_protocol::account::AccountId; use miden_protocol::crypto::rand::FeltRng; use miden_protocol::errors::NoteError; @@ -26,6 +27,8 @@ use miden_protocol::note::{ use miden_standards::note::{NetworkAccountTarget, NoteExecutionHint}; use miden_utils_sync::LazyLock; +use crate::EthAddressFormat; + // NOTE SCRIPT // ================================================================================================ @@ -43,8 +46,8 @@ static CONFIG_AGG_BRIDGE_SCRIPT: LazyLock = LazyLock::new(|| { /// CONFIG_AGG_BRIDGE note. /// -/// This note is used to register a faucet in the bridge's faucet registry. -/// It carries the faucet account ID and is always public. +/// This note is used to register a faucet in the bridge's faucet and token registries. +/// It carries the origin token address and faucet account ID, and is always public. pub struct ConfigAggBridgeNote; impl ConfigAggBridgeNote { @@ -52,7 +55,8 @@ impl ConfigAggBridgeNote { // -------------------------------------------------------------------------------------------- /// Expected number of storage items for a CONFIG_AGG_BRIDGE note. - pub const NUM_STORAGE_ITEMS: usize = 2; + /// Layout: [origin_token_addr(5), faucet_id_prefix, faucet_id_suffix] + pub const NUM_STORAGE_ITEMS: usize = 7; // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- @@ -72,12 +76,14 @@ impl ConfigAggBridgeNote { /// Creates a CONFIG_AGG_BRIDGE note to register a faucet in the bridge's registry. /// - /// The note storage contains 2 felts: + /// The note storage contains 7 felts: + /// - `origin_token_addr[0..4]`: The 5 u32 felts of the origin EVM token address /// - `faucet_id_prefix`: The prefix of the faucet account ID /// - `faucet_id_suffix`: The suffix of the faucet account ID /// /// # Parameters /// - `faucet_account_id`: The account ID of the faucet to register + /// - `origin_token_address`: The origin EVM token address for the token registry /// - `sender_account_id`: The account ID of the note creator /// - `target_account_id`: The bridge account ID that will consume this note /// - `rng`: Random number generator for creating the note serial number @@ -86,12 +92,17 @@ impl ConfigAggBridgeNote { /// Returns an error if note creation fails. pub fn create( faucet_account_id: AccountId, + origin_token_address: &EthAddressFormat, sender_account_id: AccountId, target_account_id: AccountId, rng: &mut R, ) -> Result { - // Create note storage with 2 felts: [faucet_id_prefix, faucet_id_suffix] - let storage_values = vec![faucet_account_id.prefix().as_felt(), faucet_account_id.suffix()]; + // Create note storage with 7 felts: [origin_token_addr(5), faucet_id_prefix, + // faucet_id_suffix] + let addr_elements = origin_token_address.to_elements(); + let mut storage_values: Vec = addr_elements; + storage_values.push(faucet_account_id.prefix().as_felt()); + storage_values.push(faucet_account_id.suffix()); let note_storage = NoteStorage::new(storage_values)?; diff --git a/crates/miden-agglayer/src/errors/agglayer.rs b/crates/miden-agglayer/src/errors/agglayer.rs index 91e98d3725..3a8feda3fe 100644 --- a/crates/miden-agglayer/src/errors/agglayer.rs +++ b/crates/miden-agglayer/src/errors/agglayer.rs @@ -24,8 +24,8 @@ pub const ERR_CLAIM_TARGET_ACCT_MISMATCH: MasmError = MasmError::from_static_str /// Error Message: "CONFIG_AGG_BRIDGE note attachment target account does not match consuming account" pub const ERR_CONFIG_AGG_BRIDGE_TARGET_ACCOUNT_MISMATCH: MasmError = MasmError::from_static_str("CONFIG_AGG_BRIDGE note attachment target account does not match consuming account"); -/// Error Message: "CONFIG_AGG_BRIDGE expects exactly 2 note storage items" -pub const ERR_CONFIG_AGG_BRIDGE_UNEXPECTED_STORAGE_ITEMS: MasmError = MasmError::from_static_str("CONFIG_AGG_BRIDGE expects exactly 2 note storage items"); +/// Error Message: "CONFIG_AGG_BRIDGE expects exactly 7 note storage items" +pub const ERR_CONFIG_AGG_BRIDGE_UNEXPECTED_STORAGE_ITEMS: MasmError = MasmError::from_static_str("CONFIG_AGG_BRIDGE expects exactly 7 note storage items"); /// Error Message: "faucet is not registered in the bridge's faucet registry" pub const ERR_FAUCET_NOT_REGISTERED: MasmError = MasmError::from_static_str("faucet is not registered in the bridge's faucet registry"); @@ -68,6 +68,9 @@ pub const ERR_SENDER_NOT_GER_MANAGER: MasmError = MasmError::from_static_str("no /// Error Message: "merkle proof verification failed: provided SMT root does not match the computed root" pub const ERR_SMT_ROOT_VERIFICATION_FAILED: MasmError = MasmError::from_static_str("merkle proof verification failed: provided SMT root does not match the computed root"); +/// Error Message: "token address is not registered in the bridge's token registry" +pub const ERR_TOKEN_NOT_REGISTERED: MasmError = MasmError::from_static_str("token address is not registered in the bridge's token registry"); + /// Error Message: "x < y*10^s (underflow detected)" pub const ERR_UNDERFLOW: MasmError = MasmError::from_static_str("x < y*10^s (underflow detected)"); diff --git a/crates/miden-agglayer/src/lib.rs b/crates/miden-agglayer/src/lib.rs index 21070e557b..8918fc0eb0 100644 --- a/crates/miden-agglayer/src/lib.rs +++ b/crates/miden-agglayer/src/lib.rs @@ -131,6 +131,10 @@ static FAUCET_REGISTRY_SLOT_NAME: LazyLock = LazyLock::new(|| { StorageSlotName::new("miden::agglayer::bridge::faucet_registry") .expect("faucet registry storage slot name should be valid") }); +static TOKEN_REGISTRY_SLOT_NAME: LazyLock = LazyLock::new(|| { + StorageSlotName::new("miden::agglayer::bridge::token_registry") + .expect("token registry storage slot name should be valid") +}); static BRIDGE_ADMIN_SLOT_NAME: LazyLock = LazyLock::new(|| { StorageSlotName::new("miden::agglayer::bridge::admin") .expect("bridge admin storage slot name should be valid") @@ -160,6 +164,7 @@ static GER_MANAGER_SLOT_NAME: LazyLock = LazyLock::new(|| { /// - [`Self::ler_hi_slot_name`]: Stores the upper 32 bits of the LET root. /// - [`Self::let_num_leaves_slot_name`]: Stores the number of leaves in the LET frontier. /// - [`Self::faucet_registry_slot_name`]: Stores the faucet registry map. +/// - [`Self::token_registry_slot_name`]: Stores the token address → faucet ID map. /// - [`Self::bridge_admin_slot_name`]: Stores the bridge admin account ID. /// - [`Self::ger_manager_slot_name`]: Stores the GER manager account ID. /// @@ -207,6 +212,11 @@ impl AggLayerBridge { &FAUCET_REGISTRY_SLOT_NAME } + /// Storage slot name for the token registry map. + pub fn token_registry_slot_name() -> &'static StorageSlotName { + &TOKEN_REGISTRY_SLOT_NAME + } + /// Storage slot name for the bridge admin account ID. pub fn bridge_admin_slot_name() -> &'static StorageSlotName { &BRIDGE_ADMIN_SLOT_NAME @@ -240,6 +250,7 @@ impl From for AccountComponent { StorageSlot::with_value(LET_ROOT_HI_SLOT_NAME.clone(), Word::empty()), StorageSlot::with_value(LET_NUM_LEAVES_SLOT_NAME.clone(), Word::empty()), StorageSlot::with_empty_map(FAUCET_REGISTRY_SLOT_NAME.clone()), + StorageSlot::with_empty_map(TOKEN_REGISTRY_SLOT_NAME.clone()), StorageSlot::with_value(BRIDGE_ADMIN_SLOT_NAME.clone(), bridge_admin_word), StorageSlot::with_value(GER_MANAGER_SLOT_NAME.clone(), ger_manager_word), ]; @@ -311,13 +322,17 @@ static CONVERSION_INFO_2_SLOT_NAME: LazyLock = LazyLock::new(|| StorageSlotName::new("miden::agglayer::faucet::conversion_info_2") .expect("conversion info 2 storage slot name should be valid") }); +static OWNER_CONFIG_SLOT_NAME: LazyLock = LazyLock::new(|| { + StorageSlotName::new("miden::standards::access::ownable::owner_config") + .expect("owner config storage slot name should be valid") +}); /// An [`AccountComponent`] implementing the AggLayer Faucet. /// /// It reexports the procedures from `miden::agglayer::faucet`. When linking against this /// component, the `agglayer` library must be available to the assembler. /// The procedures of this component are: -/// - `claim`, which validates a CLAIM note against one of the stored GERs in the bridge. +/// - `distribute`, which mints assets and creates output notes (with owner verification). /// - `asset_to_origin_asset`, which converts an asset to the origin asset (used in FPI from /// bridge). /// - `burn`, which burns an asset. @@ -329,6 +344,7 @@ static CONVERSION_INFO_2_SLOT_NAME: LazyLock = LazyLock::new(|| /// - [`Self::conversion_info_1_slot`]: Stores the first 4 felts of the origin token address. /// - [`Self::conversion_info_2_slot`]: Stores the remaining 5th felt of the origin token address + /// origin network + scale. +/// - [`Self::owner_config_slot`]: Stores the owner account ID (bridge) for MINT note authorization. #[derive(Debug, Clone)] pub struct AggLayerFaucet { metadata: TokenMetadata, @@ -394,6 +410,11 @@ impl AggLayerFaucet { pub fn conversion_info_2_slot() -> &'static StorageSlotName { &CONVERSION_INFO_2_SLOT_NAME } + + /// Storage slot name for the owner account ID (bridge) used by `ownable::verify_owner`. + pub fn owner_config_slot() -> &'static StorageSlotName { + &OWNER_CONFIG_SLOT_NAME + } } impl From for AccountComponent { @@ -419,8 +440,17 @@ impl From for AccountComponent { let conversion_slot2 = StorageSlot::with_value(CONVERSION_INFO_2_SLOT_NAME.clone(), conversion_slot2_word); + // Owner config slot: bridge account ID as the owner for MINT note authorization + let owner_word = Word::new([ + Felt::ZERO, + Felt::ZERO, + faucet.bridge_account_id.suffix(), + faucet.bridge_account_id.prefix().as_felt(), + ]); + let owner_slot = StorageSlot::with_value(OWNER_CONFIG_SLOT_NAME.clone(), owner_word); + let agglayer_storage_slots = - vec![metadata_slot, bridge_slot, conversion_slot1, conversion_slot2]; + vec![metadata_slot, bridge_slot, conversion_slot1, conversion_slot2, owner_slot]; agglayer_faucet_component(agglayer_storage_slots) } } diff --git a/crates/miden-testing/tests/agglayer/bridge_in.rs b/crates/miden-testing/tests/agglayer/bridge_in.rs index 1d579d3218..c3ca8a28e7 100644 --- a/crates/miden-testing/tests/agglayer/bridge_in.rs +++ b/crates/miden-testing/tests/agglayer/bridge_in.rs @@ -7,6 +7,7 @@ use anyhow::Context; use miden_agglayer::claim_note::Keccak256Output; use miden_agglayer::{ ClaimNoteStorage, + ConfigAggBridgeNote, ExitRoot, SmtNode, UpdateGerNote, @@ -25,6 +26,7 @@ use miden_protocol::transaction::OutputNote; use miden_protocol::{Felt, FieldElement}; use miden_standards::account::wallets::BasicWallet; use miden_standards::code_builder::CodeBuilder; +use miden_standards::note::P2idNote; use miden_standards::testing::account_component::IncrNonceAuthComponent; use miden_testing::utils::create_p2id_note_exact; use miden_testing::{AccountState, Auth, MockChain, TransactionContextBuilder}; @@ -91,7 +93,13 @@ fn merkle_proof_verification_code( ) } -/// Tests the bridge-in flow: CLAIM note -> Aggfaucet (FPI to Bridge) -> P2ID note created. +/// Tests the bridge-in flow with the new 2-transaction architecture: +/// +/// TX0: CONFIG_AGG_BRIDGE → bridge (registers faucet + token address in registries) +/// TX1: UPDATE_GER → bridge (stores GER) +/// TX2: CLAIM → bridge (validates proof, creates MINT note) +/// TX3: MINT → aggfaucet (mints asset, creates P2ID note) +/// TX4: P2ID → destination (simulated case only) /// /// Parameterized over two claim data sources: /// - [`ClaimDataSource::Real`]: uses real [`ProofData`] and [`LeafData`] from @@ -99,9 +107,6 @@ fn merkle_proof_verification_code( /// - [`ClaimDataSource::Simulated`]: uses locally generated [`ProofData`] and [`LeafData`] from /// `claim_asset_vectors_local_tx.json`, produced by simulating a `bridgeAsset()` call. /// -/// In both cases the claim note is processed against the agglayer faucet, which validates the -/// Merkle proof and creates a P2ID note for the destination address. -/// /// Note: Modifying anything in the real test vectors would invalidate the Merkle proof, /// as the proof was computed for the original leaf data including the original destination. #[rstest::rstest] @@ -113,7 +118,7 @@ async fn test_bridge_in_claim_to_p2id(#[case] data_source: ClaimDataSource) -> a let mut builder = MockChain::builder(); - // CREATE BRIDGE ADMIN ACCOUNT (not used in this test, but distinct from GER manager) + // CREATE BRIDGE ADMIN ACCOUNT (sends CONFIG_AGG_BRIDGE notes) // -------------------------------------------------------------------------------------------- let bridge_admin = builder.add_existing_wallet(Auth::BasicAuth { auth_scheme: AuthScheme::Falcon512Rpo })?; @@ -196,7 +201,7 @@ async fn test_bridge_in_claim_to_p2id(#[case] data_source: ClaimDataSource) -> a AccountState::Exists, )?; - // CREATE CLAIM NOTE + // CREATE CLAIM NOTE (now targets the bridge, not the faucet) // -------------------------------------------------------------------------------------------- // The P2ID serial number is derived from the PROOF_DATA_KEY (RPO hash of proof data) @@ -216,7 +221,7 @@ async fn test_bridge_in_claim_to_p2id(#[case] data_source: ClaimDataSource) -> a let claim_note = create_claim_note( claim_inputs, - agglayer_faucet.id(), + bridge_account.id(), // Target the bridge, not the faucet sender_account.id(), builder.rng_mut(), )?; @@ -224,6 +229,17 @@ async fn test_bridge_in_claim_to_p2id(#[case] data_source: ClaimDataSource) -> a // Add the claim note to the builder before building the mock chain builder.add_output_note(OutputNote::Full(claim_note.clone())); + // CREATE CONFIG_AGG_BRIDGE NOTE (registers faucet + token address in bridge) + // -------------------------------------------------------------------------------------------- + let config_note = ConfigAggBridgeNote::create( + agglayer_faucet.id(), + &origin_token_address, + bridge_admin.id(), + bridge_account.id(), + builder.rng_mut(), + )?; + builder.add_output_note(OutputNote::Full(config_note.clone())); + // CREATE UPDATE_GER NOTE WITH GLOBAL EXIT ROOT // -------------------------------------------------------------------------------------------- let update_ger_note = @@ -234,7 +250,17 @@ async fn test_bridge_in_claim_to_p2id(#[case] data_source: ClaimDataSource) -> a // -------------------------------------------------------------------------------------------- let mut mock_chain = builder.clone().build()?; - // EXECUTE UPDATE_GER NOTE TO STORE GER IN BRIDGE ACCOUNT + // TX0: EXECUTE CONFIG_AGG_BRIDGE NOTE TO REGISTER FAUCET IN BRIDGE + // -------------------------------------------------------------------------------------------- + let config_tx_context = mock_chain + .build_tx_context(bridge_account.id(), &[config_note.id()], &[])? + .build()?; + let config_executed = config_tx_context.execute().await?; + + mock_chain.add_pending_executed_transaction(&config_executed)?; + mock_chain.prove_next_block()?; + + // TX1: EXECUTE UPDATE_GER NOTE TO STORE GER IN BRIDGE ACCOUNT // -------------------------------------------------------------------------------------------- let update_ger_tx_context = mock_chain .build_tx_context(bridge_account.id(), &[update_ger_note.id()], &[])? @@ -244,23 +270,41 @@ async fn test_bridge_in_claim_to_p2id(#[case] data_source: ClaimDataSource) -> a mock_chain.add_pending_executed_transaction(&update_ger_executed)?; mock_chain.prove_next_block()?; - // EXECUTE CLAIM NOTE AGAINST AGGLAYER FAUCET (with FPI to Bridge) + // TX2: EXECUTE CLAIM NOTE AGAINST BRIDGE (validates proof, creates MINT note) // -------------------------------------------------------------------------------------------- - let foreign_account_inputs = mock_chain.get_foreign_account_inputs(bridge_account.id())?; + let claim_tx_context = + mock_chain.build_tx_context(bridge_account.id(), &[], &[claim_note])?.build()?; + + let claim_executed = claim_tx_context.execute().await?; - let tx_context = mock_chain - .build_tx_context(agglayer_faucet.id(), &[], &[claim_note])? - .foreign_accounts(vec![foreign_account_inputs]) + // VERIFY MINT NOTE WAS CREATED BY THE BRIDGE + // -------------------------------------------------------------------------------------------- + assert_eq!(claim_executed.output_notes().num_notes(), 1); + let mint_output_note = claim_executed.output_notes().get_note(0); + + // Verify the MINT note was sent by the bridge + assert_eq!(mint_output_note.metadata().sender(), bridge_account.id()); + assert_eq!(mint_output_note.metadata().note_type(), NoteType::Public); + + // Commit the CLAIM transaction and prove the block so the MINT note can be consumed + mock_chain.add_pending_executed_transaction(&claim_executed)?; + mock_chain.prove_next_block()?; + + // TX3: EXECUTE MINT NOTE AGAINST AGGFAUCET (mints asset, creates P2ID note) + // -------------------------------------------------------------------------------------------- + let mint_tx_context = mock_chain + .build_tx_context(agglayer_faucet.id(), &[mint_output_note.id()], &[])? + .add_note_script(P2idNote::script()) .build()?; - let executed_transaction = tx_context.execute().await?; + let mint_executed = mint_tx_context.execute().await?; - // VERIFY P2ID NOTE WAS CREATED + // VERIFY P2ID NOTE WAS CREATED BY THE FAUCET // -------------------------------------------------------------------------------------------- // Check that exactly one P2ID note was created by the faucet - assert_eq!(executed_transaction.output_notes().num_notes(), 1); - let output_note = executed_transaction.output_notes().get_note(0); + assert_eq!(mint_executed.output_notes().num_notes(), 1); + let output_note = mint_executed.output_notes().get_note(0); // Verify note metadata properties assert_eq!(output_note.metadata().sender(), agglayer_faucet.id()); @@ -302,14 +346,14 @@ async fn test_bridge_in_claim_to_p2id(#[case] data_source: ClaimDataSource) -> a assert_eq!(OutputNote::Full(expected_output_p2id_note.clone()), *output_note); - // CONSUME THE P2ID NOTE WITH THE DESTINATION ACCOUNT (simulated case only) + // TX4: CONSUME THE P2ID NOTE WITH THE DESTINATION ACCOUNT (simulated case only) // -------------------------------------------------------------------------------------------- // For the simulated case, we control the destination account and can verify the full // end-to-end flow including P2ID consumption and balance updates. if let Some(destination_account) = destination_account { // Add the faucet transaction to the chain and prove the next block so the P2ID note is // committed and can be consumed. - mock_chain.add_pending_executed_transaction(&executed_transaction)?; + mock_chain.add_pending_executed_transaction(&mint_executed)?; mock_chain.prove_next_block()?; // Execute the consume transaction for the destination account diff --git a/crates/miden-testing/tests/agglayer/bridge_out.rs b/crates/miden-testing/tests/agglayer/bridge_out.rs index 8b40e17584..a1f6b02e09 100644 --- a/crates/miden-testing/tests/agglayer/bridge_out.rs +++ b/crates/miden-testing/tests/agglayer/bridge_out.rs @@ -146,6 +146,7 @@ async fn bridge_out_consecutive() -> anyhow::Result<()> { // CONFIG_AGG_BRIDGE note to register the faucet in the bridge (sent by bridge admin) let config_note = ConfigAggBridgeNote::create( faucet.id(), + &origin_token_address, bridge_admin.id(), bridge_account.id(), builder.rng_mut(), diff --git a/crates/miden-testing/tests/agglayer/config_bridge.rs b/crates/miden-testing/tests/agglayer/config_bridge.rs index b9f7dcfbbc..3fc9311ba5 100644 --- a/crates/miden-testing/tests/agglayer/config_bridge.rs +++ b/crates/miden-testing/tests/agglayer/config_bridge.rs @@ -3,6 +3,7 @@ extern crate alloc; use miden_agglayer::{ AggLayerBridge, ConfigAggBridgeNote, + EthAddressFormat, create_existing_bridge_account, faucet_registry_key, }; @@ -60,8 +61,12 @@ async fn test_config_agg_bridge_registers_faucet() -> anyhow::Result<()> { ); // CREATE CONFIG_AGG_BRIDGE NOTE + // Use a dummy origin token address for this test + let origin_token_address = + EthAddressFormat::from_hex("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); let config_note = ConfigAggBridgeNote::create( faucet_to_register, + &origin_token_address, bridge_admin.id(), bridge_account.id(), builder.rng_mut(),