Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions crates/miden-agglayer/asm/bridge/bridge_in.masm
Original file line number Diff line number Diff line change
@@ -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")
Comment on lines +7 to +8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of comments on these:

  • Do we actually need to store the full GER? it seems like, the claim note inputs can be used to compute the GER and then, we just need to check if the GER computed from the note matches the GER in the bride. For this, we just need to store GER commitment (i.e., RPO hash). This would mean we need only one storage slot.
  • Maybe we should make the storage slot into a map: hash(ger) |-> 1/0. This way, given a GER, we can compute its hash to reduce it to a single word, and then do a lookup to check if the GER is known or not.
  • It feels a bit odd that GER update functionality is in the bridge_in module. It probably would make sense to have a separate bridge_operator module for things like this.


# 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]]
Expand Down
50 changes: 50 additions & 0 deletions crates/miden-agglayer/asm/note_scripts/UPDATE_GER.masm
Original file line number Diff line number Diff line change
@@ -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).
Comment on lines +16 to +17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not currently enforced, right? If so, might be good to add a comment about this.

#!
#! 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
3 changes: 3 additions & 0 deletions crates/miden-agglayer/src/errors/agglayer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
46 changes: 30 additions & 16 deletions crates/miden-agglayer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -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
// ================================================================================================
Expand Down Expand Up @@ -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<NoteScript> = 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
// ================================================================================================

Expand Down Expand Up @@ -191,20 +206,6 @@ pub fn asset_conversion_component(storage_slots: Vec<StorageSlot>) -> 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:
Expand Down Expand Up @@ -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.
Expand Down
47 changes: 47 additions & 0 deletions crates/miden-agglayer/src/update_ger_note.rs
Original file line number Diff line number Diff line change
@@ -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<R: FeltRng>(
ger: ExitRoot,
sender_account_id: miden_protocol::account::AccountId,
rng: &mut R,
) -> Result<Note, NoteError> {
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))
}
1 change: 1 addition & 0 deletions crates/miden-testing/tests/agglayer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ mod crypto_utils;
mod mmr_frontier;
mod solidity_miden_address_conversion;
pub mod test_utils;
mod update_ger;
58 changes: 58 additions & 0 deletions crates/miden-testing/tests/agglayer/update_ger.rs
Original file line number Diff line number Diff line change
@@ -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(())
}