diff --git a/crates/miden-agglayer/asm/bridge/bridge_in.masm b/crates/miden-agglayer/asm/bridge/bridge_in.masm index 3c86a53f3..75e06d464 100644 --- a/crates/miden-agglayer/asm/bridge/bridge_in.masm +++ b/crates/miden-agglayer/asm/bridge/bridge_in.masm @@ -1,4 +1,33 @@ use miden::agglayer::crypto_utils +use miden::protocol::active_account +use miden::protocol::native_account + +# CONSTANTS +# ================================================================================================= +const GER_UPPER_STORAGE_SLOT=word("miden::agglayer::bridge::ger_upper") +const GER_LOWER_STORAGE_SLOT=word("miden::agglayer::bridge::ger_lower") + +# Inputs: [GER_LOWER[4], GER_UPPER[4]] +# Outputs: [] +pub proc update_ger + push.GER_LOWER_STORAGE_SLOT[0..2] + # => [slot_id_prefix, slot_id_suffix, GER_LOWER[4], GER_UPPER[4]] + + exec.native_account::set_item + # => [OLD_VALUE, GER_UPPER[4]] + + dropw + # => [GER_UPPER[4]] + + push.GER_UPPER_STORAGE_SLOT[0..2] + # => [slot_id_prefix, slot_id_suffix, GER_UPPER[4]] + + exec.native_account::set_item + # => [OLD_VALUE] + + dropw + # => [] +end # Inputs: [] # Output: [GER_ROOT[8]] diff --git a/crates/miden-agglayer/asm/note_scripts/UPDATE_GER.masm b/crates/miden-agglayer/asm/note_scripts/UPDATE_GER.masm new file mode 100644 index 000000000..baa8ebae6 --- /dev/null +++ b/crates/miden-agglayer/asm/note_scripts/UPDATE_GER.masm @@ -0,0 +1,50 @@ +use miden::agglayer::bridge_in +use miden::protocol::active_note + +# CONSTANTS +# ================================================================================================= +const UPDATE_GER_NOTE_NUM_STORAGE_ITEMS = 8 +const STORAGE_PTR_GER_LOWER = 0 +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" + +#! Agglayer Bridge UPDATE_GER script: updates the GER by calling the bridge_in::update_ger function. +#! +#! This note can only be consumed by the specific agglayer bridge account whose ID is provided +#! in the note attachment (target_account_id). +#! +#! Requires that the account exposes: +#! - agglayer::bridge_in::update_ger procedure. +#! +#! Inputs: [ARGS, pad(12)] +#! Outputs: [pad(16)] +#! NoteStorage layout (8 felts total): +#! - GER_LOWER [0..3] +#! - GER_UPPER [4..7] +#! +#! Panics if: +#! - account does not expose update_ger procedure. +#! - target account ID does not match the consuming account ID. +#! - number of note storage items is not exactly 8. +begin + dropw + # => [pad(16)] + + push.STORAGE_PTR_GER_LOWER exec.active_note::get_storage + # => [num_storage_items, dest_ptr, pad(16)] + + push.UPDATE_GER_NOTE_NUM_STORAGE_ITEMS assert_eq.err=ERR_UPDATE_GER_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS drop + # => [pad(16)] + + # Load GER_LOWER and GER_UPPER from note storage + mem_loadw_be.STORAGE_PTR_GER_UPPER + swapw mem_loadw_be.STORAGE_PTR_GER_LOWER + # => [GER_LOWER[4], GER_UPPER[4]] + + call.bridge_in::update_ger + # => [] + +end \ No newline at end of file diff --git a/crates/miden-agglayer/src/errors/agglayer.rs b/crates/miden-agglayer/src/errors/agglayer.rs index 61f70e3f0..38c7f11b6 100644 --- a/crates/miden-agglayer/src/errors/agglayer.rs +++ b/crates/miden-agglayer/src/errors/agglayer.rs @@ -34,3 +34,6 @@ 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 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 4860295f9..535c4f3cb 100644 --- a/crates/miden-agglayer/src/lib.rs +++ b/crates/miden-agglayer/src/lib.rs @@ -27,6 +27,7 @@ use miden_utils_sync::LazyLock; pub mod claim_note; pub mod errors; pub mod eth_types; +pub mod update_ger_note; pub mod utils; pub use claim_note::{ @@ -39,6 +40,7 @@ pub use claim_note::{ create_claim_note, }; pub use eth_types::{EthAddressFormat, EthAmount, EthAmountError}; +pub use update_ger_note::create_update_ger_note; // AGGLAYER NOTE SCRIPTS // ================================================================================================ @@ -66,6 +68,19 @@ pub fn claim_script() -> NoteScript { CLAIM_SCRIPT.clone() } +// Initialize the UPDATE_GER note script only once +static UPDATE_GER_SCRIPT: LazyLock = LazyLock::new(|| { + let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/UPDATE_GER.masb")); + let program = + Program::read_from_bytes(bytes).expect("Shipped UPDATE_GER script is well-formed"); + NoteScript::new(program) +}); + +/// Returns the UPDATE_GER note script. +pub fn update_ger_script() -> NoteScript { + UPDATE_GER_SCRIPT.clone() +} + // AGGLAYER ACCOUNT COMPONENTS // ================================================================================================ @@ -191,20 +206,6 @@ pub fn asset_conversion_component(storage_slots: Vec) -> AccountCom // AGGLAYER ACCOUNT CREATION HELPERS // ================================================================================================ -/// Creates a bridge account component with the standard bridge storage slot. -/// -/// This is a convenience function that creates the bridge storage slot with the standard -/// name "miden::agglayer::bridge" and returns the bridge_out component. -/// -/// # Returns -/// Returns an [`AccountComponent`] configured for bridge operations with MMR validation. -pub fn create_bridge_account_component() -> AccountComponent { - let bridge_storage_slot_name = StorageSlotName::new("miden::agglayer::bridge") - .expect("Bridge storage slot name should be valid"); - let bridge_storage_slots = vec![StorageSlot::with_empty_map(bridge_storage_slot_name)]; - bridge_out_component(bridge_storage_slots) -} - /// Creates an agglayer faucet account component with the specified configuration. /// /// This function creates all the necessary storage slots for an agglayer faucet: @@ -253,10 +254,23 @@ 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 { - let bridge_component = create_bridge_account_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![ + 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_out_component = bridge_out_component(vec![]); + Account::builder(seed.into()) .storage_mode(AccountStorageMode::Public) - .with_component(bridge_component) + .with_component(bridge_out_component) + .with_component(bridge_in_component) } /// Creates a new bridge account with the standard configuration. diff --git a/crates/miden-agglayer/src/update_ger_note.rs b/crates/miden-agglayer/src/update_ger_note.rs new file mode 100644 index 000000000..86dd46450 --- /dev/null +++ b/crates/miden-agglayer/src/update_ger_note.rs @@ -0,0 +1,47 @@ +extern crate alloc; + +use alloc::vec; + +use miden_protocol::crypto::rand::FeltRng; +use miden_protocol::errors::NoteError; +use miden_protocol::note::{ + Note, + NoteAssets, + NoteMetadata, + NoteRecipient, + NoteStorage, + NoteTag, + NoteType, +}; + +use crate::{ExitRoot, update_ger_script}; + +/// Creates an UPDATE_GER note with the given GER (Global Exit Root) data. +/// +/// The note storage contains 8 felts: GER[0..7] +pub fn create_update_ger_note( + ger: ExitRoot, + sender_account_id: miden_protocol::account::AccountId, + rng: &mut R, +) -> Result { + let update_ger_script = update_ger_script(); + + // Create note storage with 8 felts: GER[0..7] + let storage_values = ger.to_elements().to_vec(); + + let note_storage = NoteStorage::new(storage_values)?; + + // Generate a serial number for the note + let serial_num = rng.draw_word(); + + let recipient = NoteRecipient::new(serial_num, update_ger_script, note_storage); + + // Create note metadata - use a simple public tag + let note_tag = NoteTag::new(0); + let metadata = NoteMetadata::new(sender_account_id, NoteType::Public, note_tag); + + // UPDATE_GER notes don't carry assets + let assets = NoteAssets::new(vec![])?; + + Ok(Note::new(assets, metadata, recipient)) +} diff --git a/crates/miden-testing/tests/agglayer/mod.rs b/crates/miden-testing/tests/agglayer/mod.rs index 44e687a15..dc47af1bb 100644 --- a/crates/miden-testing/tests/agglayer/mod.rs +++ b/crates/miden-testing/tests/agglayer/mod.rs @@ -5,3 +5,4 @@ mod crypto_utils; mod mmr_frontier; mod solidity_miden_address_conversion; pub mod test_utils; +mod update_ger; diff --git a/crates/miden-testing/tests/agglayer/update_ger.rs b/crates/miden-testing/tests/agglayer/update_ger.rs new file mode 100644 index 000000000..06c35ad6e --- /dev/null +++ b/crates/miden-testing/tests/agglayer/update_ger.rs @@ -0,0 +1,58 @@ +use miden_agglayer::{ExitRoot, create_existing_bridge_account, create_update_ger_note}; +use miden_protocol::Word; +use miden_protocol::account::StorageSlotName; +use miden_protocol::crypto::rand::FeltRng; +use miden_protocol::transaction::OutputNote; +use miden_testing::MockChain; + +#[tokio::test] +async fn test_update_ger_note_updates_storage() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + + // CREATE BRIDGE ACCOUNT + // -------------------------------------------------------------------------------------------- + let bridge_seed = builder.rng_mut().draw_word(); + let bridge_account = create_existing_bridge_account(bridge_seed); + builder.add_account(bridge_account.clone())?; + + // CREATE UPDATE_GER NOTE WITH 8 STORAGE ITEMS + // -------------------------------------------------------------------------------------------- + + let ger_bytes: [u8; 32] = [ + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, + 0x77, 0x88, + ]; + let ger = ExitRoot::from(ger_bytes); + let update_ger_note = create_update_ger_note(ger, bridge_account.id(), builder.rng_mut())?; + + builder.add_output_note(OutputNote::Full(update_ger_note.clone())); + let mock_chain = builder.build()?; + + // EXECUTE UPDATE_GER NOTE AGAINST BRIDGE ACCOUNT + // -------------------------------------------------------------------------------------------- + let tx_context = mock_chain + .build_tx_context(bridge_account.id(), &[update_ger_note.id()], &[])? + .build()?; + let executed_transaction = tx_context.execute().await?; + + // VERIFY GER WAS UPDATED IN STORAGE + // -------------------------------------------------------------------------------------------- + let mut updated_bridge_account = bridge_account.clone(); + updated_bridge_account.apply_delta(executed_transaction.account_delta())?; + + let ger_upper = updated_bridge_account + .storage() + .get_item(&StorageSlotName::new("miden::agglayer::bridge::ger_upper")?) + .unwrap(); + let ger_lower = updated_bridge_account + .storage() + .get_item(&StorageSlotName::new("miden::agglayer::bridge::ger_lower")?) + .unwrap(); + let expected_lower: Word = ger.to_elements()[0..4].try_into().unwrap(); + let expected_upper: Word = ger.to_elements()[4..8].try_into().unwrap(); + assert_eq!(ger_upper, expected_upper); + assert_eq!(ger_lower, expected_lower); + + Ok(()) +}