diff --git a/crates/miden-agglayer/asm/note_scripts/B2AGG.masm b/crates/miden-agglayer/asm/note_scripts/B2AGG.masm index a62e213da..06eb9e9c8 100644 --- a/crates/miden-agglayer/asm/note_scripts/B2AGG.masm +++ b/crates/miden-agglayer/asm/note_scripts/B2AGG.masm @@ -12,8 +12,9 @@ const B2AGG_NOTE_NUM_STORAGE_ITEMS=6 # ERRORS # ================================================================================================= const ERR_B2AGG_WRONG_NUMBER_OF_ASSETS="B2AGG script requires exactly 1 note asset" - const ERR_B2AGG_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS="B2AGG script expects exactly 6 note storage items" +const ERR_B2AGG_TARGET_ACCOUNT_MISMATCH="B2AGG note attachment target account does not match consuming account" + #! Bridge-to-AggLayer (B2AGG) note script: bridges assets from Miden to an AggLayer-connected chain. #! @@ -34,10 +35,13 @@ const ERR_B2AGG_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS="B2AGG script expects exactly #! - destination_address_2: bytes 8-11 #! - destination_address_3: bytes 12-15 #! - destination_address_4: bytes 16-19 +#! Note attachment is constructed from a NetworkAccountTarget standard: +#! - [0, 0, target_id_prefix, target_id_suffix] #! #! Panics if: #! - The note does not contain exactly 6 storage items. #! - The note does not contain exactly 1 asset. +#! - The note attachment does not target the consuming account. #! begin dropw @@ -58,6 +62,26 @@ begin exec.basic_wallet::add_assets_to_account # => [pad(16)] else + # Ensure note attachment targets the consuming bridge account. + exec.active_note::get_metadata + # => [NOTE_ATTACHMENT, METADATA_HEADER, pad(8)] + + # TODO simplify once https://github.com/0xMiden/miden-base/pull/2338 lands + + swapw dropw + # => [NOTE_ATTACHMENT, pad(12)] + + # Reorder attachment word to [target_id_prefix, target_id_suffix]. + drop drop + # => [target_id_prefix, target_id_suffix, pad(14)] (auto-padding) + + exec.active_account::get_id + # => [account_id_prefix, account_id_suffix, target_id_prefix, target_id_suffix, pad(14)] + + exec.account_id::is_equal + assert.err=ERR_B2AGG_TARGET_ACCOUNT_MISMATCH + # => [pad(16)] + # Store note storage -> mem[8..14] push.8 exec.active_note::get_storage # => [num_storage_items, dest_ptr, pad(16)] diff --git a/crates/miden-agglayer/asm/note_scripts/UPDATE_GER.masm b/crates/miden-agglayer/asm/note_scripts/UPDATE_GER.masm index baa8ebae6..22a5b276b 100644 --- a/crates/miden-agglayer/asm/note_scripts/UPDATE_GER.masm +++ b/crates/miden-agglayer/asm/note_scripts/UPDATE_GER.masm @@ -1,5 +1,7 @@ use miden::agglayer::bridge_in use miden::protocol::active_note +use miden::protocol::active_account +use miden::protocol::account_id # CONSTANTS # ================================================================================================= @@ -10,6 +12,7 @@ const STORAGE_PTR_GER_UPPER = 4 # ERRORS # ================================================================================================= const ERR_UPDATE_GER_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS = "UPDATE_GER script expects exactly 8 note storage items" +const ERR_UPDATE_GER_TARGET_ACCOUNT_MISMATCH = "UPDATE_GER note attachment target account does not match consuming account" #! Agglayer Bridge UPDATE_GER script: updates the GER by calling the bridge_in::update_ger function. #! @@ -33,6 +36,28 @@ begin dropw # => [pad(16)] + # Ensure note attachment targets the consuming bridge account. + exec.active_note::get_metadata + # => [NOTE_ATTACHMENT, METADATA_HEADER, pad(8)] + + # TODO simplify once https://github.com/0xMiden/miden-base/pull/2338 lands + + swapw dropw + # => [NOTE_ATTACHMENT, pad(12)] + + # Reorder attachment word to [target_id_prefix, target_id_suffix]. + drop drop + # => [target_id_prefix, target_id_suffix, pad(14)] (auto-padding) + + exec.active_account::get_id + # => [account_id_prefix, account_id_suffix, target_id_prefix, target_id_suffix, pad(14)] + + exec.account_id::is_equal + assert.err=ERR_UPDATE_GER_TARGET_ACCOUNT_MISMATCH + # => [pad(16)] + + # proceed with the GER update logic + push.STORAGE_PTR_GER_LOWER exec.active_note::get_storage # => [num_storage_items, dest_ptr, pad(16)] diff --git a/crates/miden-agglayer/src/bridge_out.rs b/crates/miden-agglayer/src/bridge_out.rs new file mode 100644 index 000000000..783038feb --- /dev/null +++ b/crates/miden-agglayer/src/bridge_out.rs @@ -0,0 +1,121 @@ +//! Bridge Out note creation utilities. +//! +//! This module provides helpers for creating B2AGG (Bridge to AggLayer) notes, +//! which are used to bridge assets out from Miden to the AggLayer network. + +use alloc::string::ToString; +use alloc::vec::Vec; + +use miden_core::Felt; +use miden_protocol::account::AccountId; +use miden_protocol::crypto::rand::FeltRng; +use miden_protocol::errors::NoteError; +use miden_protocol::note::{ + Note, + NoteAssets, + NoteAttachment, + NoteExecutionHint, + NoteMetadata, + NoteRecipient, + NoteStorage, + NoteTag, + NoteType, +}; +use miden_standards::note::NetworkAccountTarget; + +use crate::{EthAddressFormat, b2agg_script}; + +// B2AGG NOTE STRUCTURES +// ================================================================================================ + +/// Storage data for B2AGG note creation. +/// +/// Contains the destination network and address information required +/// for bridging assets to the AggLayer network. +#[derive(Debug, Clone)] +pub struct B2AggNoteStorage { + /// Destination network identifier (AggLayer-assigned network ID) + pub destination_network: u32, + /// Destination Ethereum address (20 bytes) + pub destination_address: EthAddressFormat, +} + +impl B2AggNoteStorage { + /// Creates a new B2AGG note storage with the specified destination. + pub fn new(destination_network: u32, destination_address: EthAddressFormat) -> Self { + Self { destination_network, destination_address } + } + + /// Converts the storage data to a vector of field elements for note storage. + /// + /// The layout is: + /// - 1 felt: destination_network + /// - 5 felts: destination_address (20 bytes as 5 u32 values) + pub fn to_elements(&self) -> Vec { + let mut elements = Vec::with_capacity(6); + + // Destination network + elements.push(Felt::new(self.destination_network as u64)); + + // Destination address (5 u32 felts) + elements.extend(self.destination_address.to_elements()); + + elements + } +} + +impl TryFrom for NoteStorage { + type Error = NoteError; + + fn try_from(storage: B2AggNoteStorage) -> Result { + NoteStorage::new(storage.to_elements()) + } +} + +// B2AGG NOTE CREATION +// ================================================================================================ + +/// Generates a B2AGG (Bridge to AggLayer) note. +/// +/// This note is used to bridge assets from Miden to another network via the AggLayer. +/// When consumed by a bridge account, the assets are burned and a corresponding +/// claim can be made on the destination network. +/// +/// # Parameters +/// - `storage`: The destination network and address information +/// - `assets`: The assets to bridge (must be fungible assets from a network faucet) +/// - `target_account_id`: The account ID that will consume this note (bridge account) +/// - `sender_account_id`: The account ID of the note creator +/// - `note_type`: The type of note (Public or Private) +/// - `rng`: Random number generator for creating the note serial number +/// +/// # Errors +/// Returns an error if note creation fails. +pub fn create_b2agg_note( + storage: B2AggNoteStorage, + assets: NoteAssets, + target_account_id: AccountId, + sender_account_id: AccountId, + note_type: NoteType, + rng: &mut R, +) -> Result { + let note_storage = NoteStorage::try_from(storage)?; + + let tag = NoteTag::new(0); + + let attachment = NoteAttachment::from( + NetworkAccountTarget::new(target_account_id, NoteExecutionHint::None) + .map_err(|e| NoteError::other(e.to_string()))?, + ); + + let metadata = NoteMetadata::new(sender_account_id, note_type, tag).with_attachment(attachment); + + let b2agg_script = b2agg_script(); + let recipient = NoteRecipient::new( + rng.draw_word(), + miden_protocol::note::NoteScript::new(b2agg_script), + note_storage, + ); + + Ok(Note::new(assets, metadata, recipient)) +} diff --git a/crates/miden-agglayer/src/errors/agglayer.rs b/crates/miden-agglayer/src/errors/agglayer.rs index 38c7f11b6..d087bd79e 100644 --- a/crates/miden-agglayer/src/errors/agglayer.rs +++ b/crates/miden-agglayer/src/errors/agglayer.rs @@ -12,6 +12,8 @@ use miden_protocol::errors::MasmError; /// Error Message: "most-significant 4 bytes (addr4) must be zero" pub const ERR_ADDR4_NONZERO: MasmError = MasmError::from_static_str("most-significant 4 bytes (addr4) must be zero"); +/// Error Message: "B2AGG note attachment target account does not match consuming account" +pub const ERR_B2AGG_TARGET_ACCOUNT_MISMATCH: MasmError = MasmError::from_static_str("B2AGG note attachment target account does not match consuming account"); /// Error Message: "B2AGG script expects exactly 6 note storage items" pub const ERR_B2AGG_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS: MasmError = MasmError::from_static_str("B2AGG script expects exactly 6 note storage items"); /// Error Message: "B2AGG script requires exactly 1 note asset" @@ -35,5 +37,7 @@ pub const ERR_NOT_U32: MasmError = MasmError::from_static_str("address limb is n /// Error Message: "maximum scaling factor is 18" pub const ERR_SCALE_AMOUNT_EXCEEDED_LIMIT: MasmError = MasmError::from_static_str("maximum scaling factor is 18"); +/// Error Message: "UPDATE_GER note attachment target account does not match consuming account" +pub const ERR_UPDATE_GER_TARGET_ACCOUNT_MISMATCH: MasmError = MasmError::from_static_str("UPDATE_GER note attachment target account does not match consuming account"); /// Error Message: "UPDATE_GER script expects exactly 8 note storage items" pub const ERR_UPDATE_GER_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS: MasmError = MasmError::from_static_str("UPDATE_GER script expects exactly 8 note storage items"); diff --git a/crates/miden-agglayer/src/lib.rs b/crates/miden-agglayer/src/lib.rs index 535c4f3cb..9ef02c26e 100644 --- a/crates/miden-agglayer/src/lib.rs +++ b/crates/miden-agglayer/src/lib.rs @@ -24,12 +24,14 @@ use miden_standards::account::auth::NoAuth; use miden_standards::account::faucets::NetworkFungibleFaucet; use miden_utils_sync::LazyLock; +pub mod bridge_out; pub mod claim_note; pub mod errors; pub mod eth_types; pub mod update_ger_note; pub mod utils; +pub use bridge_out::{B2AggNoteStorage, create_b2agg_note}; pub use claim_note::{ ClaimNoteStorage, ExitRoot, @@ -254,21 +256,26 @@ pub fn create_agglayer_faucet_component( /// Creates a complete bridge account builder with the standard configuration. pub fn create_bridge_account_builder(seed: Word) -> AccountBuilder { + // Create the "bridge_in" component let ger_upper_storage_slot_name = StorageSlotName::new("miden::agglayer::bridge::ger_upper") .expect("Bridge storage slot name should be valid"); let ger_lower_storage_slot_name = StorageSlotName::new("miden::agglayer::bridge::ger_lower") .expect("Bridge storage slot name should be valid"); - let bridge_storage_slots = vec![ + let bridge_in_storage_slots = vec![ StorageSlot::with_value(ger_upper_storage_slot_name, Word::empty()), StorageSlot::with_value(ger_lower_storage_slot_name, Word::empty()), ]; - let bridge_in_component = bridge_in_component(bridge_storage_slots); + let bridge_in_component = bridge_in_component(bridge_in_storage_slots); - let bridge_out_component = bridge_out_component(vec![]); + // Create the "bridge_out" component + let let_storage_slot_name = StorageSlotName::new("miden::agglayer::let").unwrap(); + let bridge_out_storage_slots = vec![StorageSlot::with_empty_map(let_storage_slot_name)]; + let bridge_out_component = bridge_out_component(bridge_out_storage_slots); + // Combine the components into a single account(builder) Account::builder(seed.into()) - .storage_mode(AccountStorageMode::Public) + .storage_mode(AccountStorageMode::Network) .with_component(bridge_out_component) .with_component(bridge_in_component) } diff --git a/crates/miden-agglayer/src/update_ger_note.rs b/crates/miden-agglayer/src/update_ger_note.rs index 86dd46450..00761f875 100644 --- a/crates/miden-agglayer/src/update_ger_note.rs +++ b/crates/miden-agglayer/src/update_ger_note.rs @@ -1,18 +1,23 @@ extern crate alloc; +use alloc::string::ToString; use alloc::vec; +use miden_protocol::account::AccountId; use miden_protocol::crypto::rand::FeltRng; use miden_protocol::errors::NoteError; use miden_protocol::note::{ Note, NoteAssets, + NoteAttachment, + NoteExecutionHint, NoteMetadata, NoteRecipient, NoteStorage, NoteTag, NoteType, }; +use miden_standards::note::NetworkAccountTarget; use crate::{ExitRoot, update_ger_script}; @@ -21,7 +26,8 @@ use crate::{ExitRoot, update_ger_script}; /// The note storage contains 8 felts: GER[0..7] pub fn create_update_ger_note( ger: ExitRoot, - sender_account_id: miden_protocol::account::AccountId, + sender_account_id: AccountId, + target_account_id: AccountId, rng: &mut R, ) -> Result { let update_ger_script = update_ger_script(); @@ -36,9 +42,13 @@ pub fn create_update_ger_note( let recipient = NoteRecipient::new(serial_num, update_ger_script, note_storage); - // Create note metadata - use a simple public tag + let attachment = NoteAttachment::from( + NetworkAccountTarget::new(target_account_id, NoteExecutionHint::None) + .map_err(|e| NoteError::other(e.to_string()))?, + ); let note_tag = NoteTag::new(0); - let metadata = NoteMetadata::new(sender_account_id, NoteType::Public, note_tag); + let metadata = NoteMetadata::new(sender_account_id, NoteType::Public, note_tag) + .with_attachment(attachment); // UPDATE_GER notes don't carry assets let assets = NoteAssets::new(vec![])?; diff --git a/crates/miden-testing/tests/agglayer/bridge_out.rs b/crates/miden-testing/tests/agglayer/bridge_out.rs index e7e921785..66f78dcab 100644 --- a/crates/miden-testing/tests/agglayer/bridge_out.rs +++ b/crates/miden-testing/tests/agglayer/bridge_out.rs @@ -1,32 +1,21 @@ extern crate alloc; -use miden_agglayer::{EthAddressFormat, b2agg_script, bridge_out_component}; -use miden_protocol::account::{ - Account, - AccountId, - AccountIdVersion, - AccountStorageMode, - AccountType, - StorageSlot, - StorageSlotName, +use miden_agglayer::errors::ERR_B2AGG_TARGET_ACCOUNT_MISMATCH; +use miden_agglayer::{ + B2AggNoteStorage, + EthAddressFormat, + create_b2agg_note, + create_existing_bridge_account, }; +use miden_crypto::rand::FeltRng; +use miden_protocol::Felt; +use miden_protocol::account::{AccountId, AccountIdVersion, AccountStorageMode, AccountType}; use miden_protocol::asset::{Asset, FungibleAsset}; -use miden_protocol::note::{ - Note, - NoteAssets, - NoteMetadata, - NoteRecipient, - NoteScript, - NoteStorage, - NoteTag, - NoteType, -}; +use miden_protocol::note::{NoteAssets, NoteScript, NoteTag, NoteType}; use miden_protocol::transaction::OutputNote; -use miden_protocol::{Felt, Word}; use miden_standards::account::faucets::FungibleFaucetExt; use miden_standards::note::WellKnownNote; -use miden_testing::{AccountState, Auth, MockChain}; -use rand::Rng; +use miden_testing::{Auth, MockChain, assert_transaction_executor_error}; /// Tests the B2AGG (Bridge to AggLayer) note script with bridge_out account component. /// @@ -52,50 +41,34 @@ async fn test_bridge_out_consumes_b2agg_note() -> anyhow::Result<()> { let faucet = builder.add_existing_network_faucet("AGG", 1000, faucet_owner_account_id, Some(100))?; - // Create a bridge account with the bridge_out component using network (public) storage - // Add a storage map for the bridge component to store MMR frontier data - let storage_slot_name = StorageSlotName::new("miden::agglayer::let").unwrap(); - let storage_slots = vec![StorageSlot::with_empty_map(storage_slot_name)]; - let bridge_component = bridge_out_component(storage_slots); - let account_builder = Account::builder(builder.rng_mut().random()) - .storage_mode(AccountStorageMode::Public) - .with_component(bridge_component); - let mut bridge_account = - builder.add_account_from_builder(Auth::IncrNonce, account_builder, AccountState::Exists)?; + // Create a bridge account (includes a `bridge_out` component tested here) + let mut bridge_account = create_existing_bridge_account(builder.rng_mut().draw_word()); + builder.add_account(bridge_account.clone())?; // CREATE B2AGG NOTE WITH ASSETS // -------------------------------------------------------------------------------------------- let amount = Felt::new(100); let bridge_asset: Asset = FungibleAsset::new(faucet.id(), amount.into()).unwrap().into(); - let tag = NoteTag::new(0); - let note_type = NoteType::Public; // Use Public note type for network transaction - - // Get the B2AGG note script - let b2agg_script = b2agg_script(); // Create note storage with destination network and address - // destination_network: u32 (AggLayer-assigned network ID) - // destination_address: 20 bytes (Ethereum address) split into 5 u32 values - let destination_network = Felt::new(1); // Example network ID + let destination_network = 1u32; // Example network ID let destination_address = "0x1234567890abcdef1122334455667788990011aa"; let eth_address = EthAddressFormat::from_hex(destination_address).expect("Valid Ethereum address"); - let address_felts = eth_address.to_elements().to_vec(); - // Combine network ID and address felts into note storage (6 felts total) - let mut input_felts = vec![destination_network]; - input_felts.extend(address_felts); + let storage = B2AggNoteStorage::new(destination_network, eth_address); + let assets = NoteAssets::new(vec![bridge_asset])?; - let inputs = NoteStorage::new(input_felts.clone())?; - - // Create the B2AGG note with assets from the faucet - let b2agg_note_metadata = NoteMetadata::new(faucet.id(), note_type, tag); - let b2agg_note_assets = NoteAssets::new(vec![bridge_asset])?; - let serial_num = Word::from([1, 2, 3, 4u32]); - let b2agg_note_script = NoteScript::new(b2agg_script); - let b2agg_note_recipient = NoteRecipient::new(serial_num, b2agg_note_script, inputs); - let b2agg_note = Note::new(b2agg_note_assets, b2agg_note_metadata, b2agg_note_recipient); + // Create the B2AGG note using the helper + let b2agg_note = create_b2agg_note( + storage, + assets, + bridge_account.id(), + faucet.id(), + NoteType::Public, + builder.rng_mut(), + )?; // Add the B2AGG note to the mock chain builder.add_output_note(OutputNote::Full(b2agg_note.clone())); @@ -218,6 +191,10 @@ async fn test_b2agg_note_reclaim_scenario() -> anyhow::Result<()> { let faucet = builder.add_existing_network_faucet("AGG", 1000, faucet_owner_account_id, Some(100))?; + // Create a bridge account (includes a `bridge_out` component tested here) + let bridge_account = create_existing_bridge_account(builder.rng_mut().draw_word()); + builder.add_account(bridge_account.clone())?; + // Create a user account that will create and consume the B2AGG note let mut user_account = builder.add_existing_wallet(Auth::BasicAuth)?; @@ -226,33 +203,26 @@ async fn test_b2agg_note_reclaim_scenario() -> anyhow::Result<()> { let amount = Felt::new(50); let bridge_asset: Asset = FungibleAsset::new(faucet.id(), amount.into()).unwrap().into(); - let tag = NoteTag::new(0); - let note_type = NoteType::Public; - - // Get the B2AGG note script - let b2agg_script = b2agg_script(); // Create note storage with destination network and address - let destination_network = Felt::new(1); + let destination_network = 1u32; let destination_address = "0x1234567890abcdef1122334455667788990011aa"; let eth_address = EthAddressFormat::from_hex(destination_address).expect("Valid Ethereum address"); - let address_felts = eth_address.to_elements().to_vec(); - - // Combine network ID and address felts into note storage (6 felts total) - let mut input_felts = vec![destination_network]; - input_felts.extend(address_felts); - let inputs = NoteStorage::new(input_felts.clone())?; + let storage = B2AggNoteStorage::new(destination_network, eth_address); + let assets = NoteAssets::new(vec![bridge_asset])?; // Create the B2AGG note with the USER ACCOUNT as the sender // This is the key difference - the note sender will be the same as the consuming account - let b2agg_note_metadata = NoteMetadata::new(user_account.id(), note_type, tag); - let b2agg_note_assets = NoteAssets::new(vec![bridge_asset])?; - let serial_num = Word::from([1, 2, 3, 4u32]); - let b2agg_note_script = NoteScript::new(b2agg_script); - let b2agg_note_recipient = NoteRecipient::new(serial_num, b2agg_note_script, inputs); - let b2agg_note = Note::new(b2agg_note_assets, b2agg_note_metadata, b2agg_note_recipient); + let b2agg_note = create_b2agg_note( + storage, + assets, + bridge_account.id(), + user_account.id(), + NoteType::Public, + builder.rng_mut(), + )?; // Add the B2AGG note to the mock chain builder.add_output_note(OutputNote::Full(b2agg_note.clone())); @@ -296,3 +266,85 @@ async fn test_b2agg_note_reclaim_scenario() -> anyhow::Result<()> { Ok(()) } + +/// Tests that a non-target account cannot consume a B2AGG note (non-reclaim branch). +/// +/// This test covers the security check in the B2AGG note script that ensures only the +/// designated target account (specified in the note attachment) can consume the note +/// when not in reclaim mode. +/// +/// Test flow: +/// 1. Creates a network faucet to provide assets +/// 2. Creates a bridge account as the designated target for the B2AGG note +/// 3. Creates a user account as the sender (creator) of the B2AGG note +/// 4. Creates a "malicious" account with a bridge interface +/// 5. Attempts to consume the B2AGG note with the malicious account +/// 6. Verifies that the transaction fails with ERR_B2AGG_TARGET_ACCOUNT_MISMATCH +#[tokio::test] +async fn test_b2agg_note_non_target_account_cannot_consume() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + + // Create a network faucet owner account + let faucet_owner_account_id = AccountId::dummy( + [1; 15], + AccountIdVersion::Version0, + AccountType::RegularAccountImmutableCode, + AccountStorageMode::Private, + ); + + // Create a network faucet to provide assets for the B2AGG note + let faucet = + builder.add_existing_network_faucet("AGG", 1000, faucet_owner_account_id, Some(100))?; + + // Create a bridge account as the designated TARGET for the B2AGG note + let bridge_account = create_existing_bridge_account(builder.rng_mut().draw_word()); + builder.add_account(bridge_account.clone())?; + + // Create a user account as the SENDER of the B2AGG note + let sender_account = builder.add_existing_wallet(Auth::BasicAuth)?; + + // Create a "malicious" account with a bridge interface + let malicious_account = create_existing_bridge_account(builder.rng_mut().draw_word()); + builder.add_account(malicious_account.clone())?; + + // CREATE B2AGG NOTE + // -------------------------------------------------------------------------------------------- + + let amount = Felt::new(50); + let bridge_asset: Asset = FungibleAsset::new(faucet.id(), amount.into()).unwrap().into(); + + // Create note storage with destination network and address + let destination_network = 1u32; + let destination_address = "0x1234567890abcdef1122334455667788990011aa"; + let eth_address = + EthAddressFormat::from_hex(destination_address).expect("Valid Ethereum address"); + + let storage = B2AggNoteStorage::new(destination_network, eth_address); + let assets = NoteAssets::new(vec![bridge_asset])?; + + // Create the B2AGG note + let b2agg_note = create_b2agg_note( + storage, + assets, + bridge_account.id(), // target + sender_account.id(), // sender + NoteType::Public, + builder.rng_mut(), + )?; + + // Add the B2AGG note to the mock chain + builder.add_output_note(OutputNote::Full(b2agg_note.clone())); + let mock_chain = builder.build()?; + + // ATTEMPT TO CONSUME B2AGG NOTE WITH MALICIOUS ACCOUNT (SHOULD FAIL) + // -------------------------------------------------------------------------------------------- + let result = mock_chain + .build_tx_context(malicious_account.id(), &[], &[b2agg_note])? + .build()? + .execute() + .await; + + assert_transaction_executor_error!(result, ERR_B2AGG_TARGET_ACCOUNT_MISMATCH); + + Ok(()) +} diff --git a/crates/miden-testing/tests/agglayer/update_ger.rs b/crates/miden-testing/tests/agglayer/update_ger.rs index 06c35ad6e..aa5ac4f0f 100644 --- a/crates/miden-testing/tests/agglayer/update_ger.rs +++ b/crates/miden-testing/tests/agglayer/update_ger.rs @@ -3,7 +3,7 @@ use miden_protocol::Word; use miden_protocol::account::StorageSlotName; use miden_protocol::crypto::rand::FeltRng; use miden_protocol::transaction::OutputNote; -use miden_testing::MockChain; +use miden_testing::{Auth, MockChain}; #[tokio::test] async fn test_update_ger_note_updates_storage() -> anyhow::Result<()> { @@ -15,7 +15,12 @@ async fn test_update_ger_note_updates_storage() -> anyhow::Result<()> { let bridge_account = create_existing_bridge_account(bridge_seed); builder.add_account(bridge_account.clone())?; - // CREATE UPDATE_GER NOTE WITH 8 STORAGE ITEMS + // CREATE USER ACCOUNT (NOTE SENDER) + // -------------------------------------------------------------------------------------------- + let user_account = builder.add_existing_wallet(Auth::BasicAuth)?; + builder.add_account(user_account.clone())?; + + // CREATE UPDATE_GER NOTE WITH 8 STORAGE ITEMS (NEW GER AS TWO WORDS) // -------------------------------------------------------------------------------------------- let ger_bytes: [u8; 32] = [ @@ -24,7 +29,8 @@ async fn test_update_ger_note_updates_storage() -> anyhow::Result<()> { 0x77, 0x88, ]; let ger = ExitRoot::from(ger_bytes); - let update_ger_note = create_update_ger_note(ger, bridge_account.id(), builder.rng_mut())?; + let update_ger_note = + create_update_ger_note(ger, user_account.id(), bridge_account.id(), builder.rng_mut())?; builder.add_output_note(OutputNote::Full(update_ger_note.clone())); let mock_chain = builder.build()?;