Skip to content
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

### Changes

- Encapsulated agglayer faucet and bridge creation in `AggLayerFaucet` and `AggLayerBridge` structs ([#2379](https://github.com/0xMiden/miden-base/pull/2379)).
- Made kernel procedure offset constants public and replaced accessor procedures with direct constant usage ([#2375](https://github.com/0xMiden/miden-base/pull/2375)).
- [BREAKING] Made `AccountComponentMetadata` a required parameter of `AccountComponent::new()`; removed `with_supported_type`, `with_supports_all_types`, and `with_metadata` methods from `AccountComponent`; simplified `AccountComponentMetadata::new()` to take just `name`; renamed `AccountComponentTemplateError` to `ComponentMetadataError` ([#2373](https://github.com/0xMiden/miden-base/pull/2373), [#2395](https://github.com/0xMiden/miden-base/pull/2395)).
- Fixed MASM inline comment casing to adhere to commenting conventions ([#2398](https://github.com/0xMiden/miden-base/pull/2398)).
Expand Down
237 changes: 183 additions & 54 deletions crates/miden-agglayer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,180 @@ pub use claim_note::{
pub use eth_types::{EthAddressFormat, EthAmount, EthAmountError};
pub use update_ger_note::UpdateGerNote;

// STORAGE SLOT NAMES
// ================================================================================================

static AGGLAYER_FAUCET_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
StorageSlotName::new("miden::agglayer::faucet")
.expect("agglayer faucet storage slot name should be valid")
});

static AGGLAYER_BRIDGE_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
StorageSlotName::new("miden::agglayer::bridge")
.expect("agglayer bridge storage slot name should be valid")
});

// AGGLAYER FAUCET
// ================================================================================================

/// An [`AccountComponent`] implementing an AggLayer faucet.
///
/// This component combines network faucet functionality with bridge validation
/// via Foreign Procedure Invocation (FPI). It provides a "claim" procedure that
/// validates CLAIM notes against a bridge MMR account before minting assets.
///
/// ## Storage Layout
///
/// - [`NetworkFungibleFaucet::metadata_slot`]: Fungible faucet metadata (max_supply, decimals,
/// token_symbol).
/// - [`Self::bridge_slot`]: The bridge account ID for FPI validation.
pub struct AggLayerFaucet {
token_symbol: TokenSymbol,
decimals: u8,
max_supply: Felt,
bridge_account_id: AccountId,
}

impl AggLayerFaucet {
// CONSTRUCTORS
// --------------------------------------------------------------------------------------------

/// Creates a new [`AggLayerFaucet`] component from the given configuration.
///
/// # Parameters
/// - `token_symbol`: The symbol for the fungible token (e.g., "AGG")
/// - `decimals`: Number of decimal places for the token
/// - `max_supply`: Maximum supply of the token
/// - `bridge_account_id`: The account ID of the bridge account for validation
pub fn new(
token_symbol: TokenSymbol,
decimals: u8,
max_supply: Felt,
bridge_account_id: AccountId,
) -> Self {
Self {
token_symbol,
decimals,
max_supply,
bridge_account_id,
}
}

// PUBLIC ACCESSORS
// --------------------------------------------------------------------------------------------

/// Returns the [`StorageSlotName`] where the bridge account ID is stored.
pub fn bridge_slot() -> &'static StorageSlotName {
&AGGLAYER_FAUCET_SLOT_NAME
}

/// Returns the token symbol of the faucet.
pub fn token_symbol(&self) -> TokenSymbol {
self.token_symbol
}

/// Returns the number of decimals for the token.
pub fn decimals(&self) -> u8 {
self.decimals
}

/// Returns the maximum supply of the token.
pub fn max_supply(&self) -> Felt {
self.max_supply
}

/// Returns the bridge account ID used for FPI validation.
pub fn bridge_account_id(&self) -> AccountId {
self.bridge_account_id
}
}

impl From<AggLayerFaucet> for AccountComponent {
fn from(faucet: AggLayerFaucet) -> Self {
// Create network faucet metadata slot: [token_supply, max_supply, decimals, symbol]
let metadata_word = Word::new([
FieldElement::ZERO, // token_supply starts at 0
faucet.max_supply,
Felt::from(faucet.decimals),
faucet.token_symbol.into(),
]);
let metadata_slot =
StorageSlot::with_value(NetworkFungibleFaucet::metadata_slot().clone(), metadata_word);
Comment on lines 136 to 146
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's update this once #2380 is merged.


// Create agglayer-specific bridge storage slot
// Storage format: [0, 0, suffix, prefix]
let bridge_account_id_word = Word::new([
Felt::new(0),
Felt::new(0),
faucet.bridge_account_id.suffix(),
faucet.bridge_account_id.prefix().as_felt(),
]);
let bridge_slot =
StorageSlot::with_value(AggLayerFaucet::bridge_slot().clone(), bridge_account_id_word);

// Combine all storage slots for the agglayer faucet component
let storage_slots = vec![metadata_slot, bridge_slot];

let metadata = AccountComponentMetadata::new("agglayer::faucet")
.with_description("AggLayer faucet component with bridge validation")
.with_supports_all_types();

AccountComponent::new(agglayer_faucet_library(), storage_slots, metadata)
.expect("agglayer faucet component should satisfy the requirements of a valid account component")
}
}

// AGGLAYER BRIDGE
// ================================================================================================

/// An [`AccountComponent`] implementing an AggLayer bridge.
///
/// This component provides bridge functionality for managing the MMR (Merkle Mountain Range)
/// that tracks bridged assets. It uses an empty map storage slot for the bridge state.
///
/// ## Storage Layout
///
/// - [`Self::bridge_slot`]: An empty map for bridge state management.
pub struct AggLayerBridge;

impl AggLayerBridge {
// CONSTRUCTORS
// --------------------------------------------------------------------------------------------

/// Creates a new [`AggLayerBridge`] component.
pub fn new() -> Self {
Self
}

// PUBLIC ACCESSORS
// --------------------------------------------------------------------------------------------

/// Returns the [`StorageSlotName`] where the bridge state map is stored.
pub fn bridge_slot() -> &'static StorageSlotName {
&AGGLAYER_BRIDGE_SLOT_NAME
}
}

impl Default for AggLayerBridge {
fn default() -> Self {
Self::new()
}
}

impl From<AggLayerBridge> for AccountComponent {
fn from(_bridge: AggLayerBridge) -> Self {
let bridge_storage_slots =
vec![StorageSlot::with_empty_map(AggLayerBridge::bridge_slot().clone())];

let metadata = AccountComponentMetadata::new("agglayer::bridge")
.with_description("AggLayer bridge component for MMR validation")
.with_supports_all_types();

AccountComponent::new(bridge_out_library(), bridge_storage_slots, metadata)
.expect("bridge component should satisfy the requirements of a valid account component")
}
}

// AGGLAYER NOTE SCRIPTS
// ================================================================================================

Expand Down Expand Up @@ -200,62 +374,13 @@ pub fn asset_conversion_component(storage_slots: Vec<StorageSlot>) -> AccountCom

/// 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.
/// This is a convenience function that creates an [`AggLayerBridge`] and converts it
/// to an [`AccountComponent`].
///
/// # 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:
/// - Network faucet metadata slot (max_supply, decimals, token_symbol)
/// - Bridge account reference slot for FPI validation
///
/// # Parameters
/// - `token_symbol`: The symbol for the fungible token (e.g., "AGG")
/// - `decimals`: Number of decimal places for the token
/// - `max_supply`: Maximum supply of the token
/// - `bridge_account_id`: The account ID of the bridge account for validation
///
/// # Returns
/// Returns an [`AccountComponent`] configured for agglayer faucet operations.
///
/// # Panics
/// Panics if the token symbol is invalid or storage slot names are malformed.
pub fn create_agglayer_faucet_component(
token_symbol: &str,
decimals: u8,
max_supply: Felt,
bridge_account_id: AccountId,
) -> AccountComponent {
// Create network faucet metadata slot: [0, max_supply, decimals, token_symbol]
let token_symbol = TokenSymbol::new(token_symbol).expect("Token symbol should be valid");
let metadata_word =
Word::new([FieldElement::ZERO, max_supply, Felt::from(decimals), token_symbol.into()]);
let metadata_slot =
StorageSlot::with_value(NetworkFungibleFaucet::metadata_slot().clone(), metadata_word);

// Create agglayer-specific bridge storage slot
let bridge_account_id_word = Word::new([
Felt::new(0),
Felt::new(0),
bridge_account_id.suffix(),
bridge_account_id.prefix().as_felt(),
]);
let agglayer_storage_slot_name = StorageSlotName::new("miden::agglayer::faucet")
.expect("Agglayer faucet storage slot name should be valid");
let bridge_slot = StorageSlot::with_value(agglayer_storage_slot_name, bridge_account_id_word);

// Combine all storage slots for the agglayer faucet component
let agglayer_storage_slots = vec![metadata_slot, bridge_slot];
agglayer_faucet_component(agglayer_storage_slots)
AggLayerBridge::new().into()
}

/// Creates a complete bridge account builder with the standard configuration.
Expand Down Expand Up @@ -306,15 +431,19 @@ pub fn create_existing_bridge_account(seed: Word) -> Account {
}

/// Creates a complete agglayer faucet account builder with the specified configuration.
///
/// # Panics
/// Panics if the token symbol is invalid.
pub fn create_agglayer_faucet_builder(
seed: Word,
token_symbol: &str,
decimals: u8,
max_supply: Felt,
bridge_account_id: AccountId,
) -> AccountBuilder {
let agglayer_component =
create_agglayer_faucet_component(token_symbol, decimals, max_supply, bridge_account_id);
let token_symbol = TokenSymbol::new(token_symbol).expect("Token symbol should be valid");
let agglayer_component: AccountComponent =
AggLayerFaucet::new(token_symbol, decimals, max_supply, bridge_account_id).into();

Account::builder(seed.into())
.account_type(AccountType::FungibleFaucet)
Expand Down