Skip to content
Open
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
26 changes: 25 additions & 1 deletion crates/miden-agglayer/asm/note_scripts/B2AGG.masm
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: let's add NOTE SCRIPT section header above this line.

#!
Expand All @@ -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
Expand All @@ -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)
Comment on lines +65 to +76
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I would move this into a helper procedure. This way, once #2338 land, we can just replace the procedure name here. Although, it may make sense to merge this one after #2338 lands (or based this PR on #2338).


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)]
Expand Down
25 changes: 25 additions & 0 deletions crates/miden-agglayer/asm/note_scripts/UPDATE_GER.masm
Original file line number Diff line number Diff line change
@@ -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
# =================================================================================================
Expand All @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: let's add NOTE SCRIPT section header above this line.

#!
Expand All @@ -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)]

Expand Down
121 changes: 121 additions & 0 deletions crates/miden-agglayer/src/bridge_out.rs
Original file line number Diff line number Diff line change
@@ -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)]
Comment on lines +33 to +35
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: comment wrapping.

pub struct B2AggNoteStorage {
/// Destination network identifier (AggLayer-assigned network ID)
pub destination_network: u32,
/// Destination Ethereum address (20 bytes)
pub destination_address: EthAddressFormat,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit (and not related to this PR): I think we should call it just EthAddress rather than 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<Felt> {
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<B2AggNoteStorage> for NoteStorage {
type Error = NoteError;

fn try_from(storage: B2AggNoteStorage) -> Result<Self, Self::Error> {
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
Comment on lines +84 to +90
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need note_type here? I think B2AGG notes would have to always be public (otherwise, the bridge can't process them).

///
/// # Errors
/// Returns an error if note creation fails.
pub fn create_b2agg_note<R: FeltRng>(
storage: B2AggNoteStorage,
assets: NoteAssets,
target_account_id: AccountId,
sender_account_id: AccountId,
note_type: NoteType,
rng: &mut R,
) -> Result<Note, NoteError> {
let note_storage = NoteStorage::try_from(storage)?;

let tag = NoteTag::new(0);

let attachment = NoteAttachment::from(
NetworkAccountTarget::new(target_account_id, NoteExecutionHint::None)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should use NoteExecutionHint::Always here because B2AGG notes are always consumable.

.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))
}
4 changes: 4 additions & 0 deletions crates/miden-agglayer/src/errors/agglayer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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");
15 changes: 11 additions & 4 deletions crates/miden-agglayer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe not for this PR, but we should normalize storage slot naming. For example, this should be miden::agglayer::bridge::let to be consistent with miden::agglayer::bridge::ger_upper and miden::agglayer::bridge::ger_lower (which I think should be just one slot miden::agglayer::bridge::ger).

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)
}
Expand Down
16 changes: 13 additions & 3 deletions crates/miden-agglayer/src/update_ger_note.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -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<R: FeltRng>(
ger: ExitRoot,
sender_account_id: miden_protocol::account::AccountId,
sender_account_id: AccountId,
target_account_id: AccountId,
rng: &mut R,
) -> Result<Note, NoteError> {
let update_ger_script = update_ger_script();
Expand All @@ -36,9 +42,13 @@ pub fn create_update_ger_note<R: FeltRng>(

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![])?;
Expand Down
Loading
Loading