Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -36,6 +36,7 @@
- [BREAKING] Narrowed the block header version field from 32 to 8 bits and set it to the only supported version instead of taking it as a `BlockHeader::new` parameter ([#3695](https://github.com/0xMiden/protocol/pull/3695)).
- [BREAKING] Serialize the version in `Account`, `AccountHeader`, `PartialNoteMetadata` and `AssetId` ([#3697](https://github.com/0xMiden/protocol/pull/3697)).
- [BREAKING] Moved the account delta and patch domain separators into the hasher capacity word. Added a version to their commitments ([#3698](https://github.com/0xMiden/protocol/pull/3698)).
- [BREAKING] Replaced the dedicated AggLayer faucet account component with the standard `FungibleFaucet`: the `AggLayerFaucet` / `AgglayerFaucetError` types and the `miden-agglayer-faucet` MASM package were removed, `AggLayerFaucet::account_builder` / `AggLayerFaucet::allowed_notes` became the free functions `agglayer_faucet_account_builder` / `agglayer_faucet_allowed_notes`, and both they and `create_existing_agglayer_faucet` now take a token name. The faucet stores the real token name instead of a copy of its symbol, so the AggLayer metadata hash preimage `abi.encode(name, symbol, decimals)` is recoverable from faucet storage; the faucet code commitment changes ([#2585](https://github.com/0xMiden/protocol/issues/2585)).

### Fixes

Expand Down
8 changes: 7 additions & 1 deletion bin/bench-transaction/src/context_setups/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use miden_protocol::testing::account_id::{ACCOUNT_ID_FEE_FAUCET, ACCOUNT_ID_SEND
use miden_protocol::transaction::RawOutputNote;
use miden_protocol::{Felt, Word};
use miden_standards::account::auth::SponsorshipPolicy;
use miden_standards::account::faucets::FungibleFaucet;
use miden_standards::account::fees::{BasicConstantFeePolicy, FeePolicyManager};
use miden_standards::code_builder::CodeBuilder;
use miden_standards::interop::eth::EthAddress;
Expand Down Expand Up @@ -498,6 +499,7 @@ pub async fn tx_consume_claim_note(
let (proof_data, leaf_data, ger, _cgi_chain_hash) = data_source.get_data();

// CREATE AGGLAYER FAUCET ACCOUNT
let token_name = "AggLayer Token";
let token_symbol = "AGG";
let decimals = 8u8;
let max_supply: Felt = FungibleAsset::MAX_AMOUNT.into();
Expand All @@ -509,6 +511,7 @@ pub async fn tx_consume_claim_note(

let agglayer_faucet = create_existing_agglayer_faucet(
agglayer_faucet_seed,
token_name,
token_symbol,
decimals,
max_supply,
Expand Down Expand Up @@ -706,6 +709,7 @@ pub async fn tx_consume_b2agg_note(

let faucet = create_existing_agglayer_faucet(
builder.rng_mut().draw_word(),
"AggLayer Token",
"AGG",
8,
FungibleAsset::MAX_AMOUNT.into(),
Expand All @@ -716,7 +720,8 @@ pub async fn tx_consume_b2agg_note(
builder.add_account(faucet.clone())?;

// CREATE CONFIG_AGG_BRIDGE NOTE (registers faucet + token address in bridge)
let metadata_hash = MetadataHash::from_token_info("AGG", "AGG", 8);
// Derive the registered hash from the faucet's own stored metadata so the two cannot drift.
Comment thread
Fumuran marked this conversation as resolved.
Outdated
let metadata_hash = MetadataHash::from_fungible_faucet(&FungibleFaucet::try_from(&faucet)?);
let config_note = ConfigAggBridgeNote::create(
ConversionMetadata {
faucet_account_id: faucet.id(),
Expand Down Expand Up @@ -791,6 +796,7 @@ fn setup_faucet_registration(

let agglayer_faucet = create_existing_agglayer_faucet(
builder.rng_mut().draw_word(),
"AggLayer Token",
"AGG",
8,
FungibleAsset::MAX_AMOUNT.into(),
Expand Down
57 changes: 39 additions & 18 deletions crates/miden-agglayer/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ implementation are called out inline with `TODO (Future)` markers.
|--------|-------------|--------------|
| **User** | End-user Miden account that holds assets and initiates bridge-out deposits, or receives assets from a bridge-in claim. | Any account with `basic_wallet` component |
| **AggLayer Bridge** | Onchain bridge account that manages the Local Exit Tree (LET), faucet registry, and GER state. Consumes B2AGG, CONFIG, and UPDATE_GER notes. | Network-mode account with a single `bridge` component |
| **AggLayer Faucet** | Fungible faucet that represents a single bridged token. Mints on bridge-in claims, burns on bridge-out. Each foreign token has its own faucet instance. | `FungibleFaucet`, network-mode, with `agglayer_faucet` component |
| **AggLayer Faucet** | Fungible faucet that represents a single bridged token. Mints on bridge-in claims, burns on bridge-out. Each foreign token has its own faucet instance. | `FungibleFaucet`, network-mode, owned by the bridge |
| **Integration Service** (offchain) | Observes L1 events (deposits, GER updates) and creates UPDATE_GER and CLAIM notes on Miden. Trusted to provide correct proofs and data. | Creates notes targeting bridge/faucet |
| **Bridge Operator** (offchain) | Deploys bridge and faucet accounts. Creates CONFIG_AGG_BRIDGE notes to register faucets. Must hold the `FAUCET_MNGR` role. | Creates config notes |
| **Bridge Admin (`BRIDGE_ADMIN`)** (offchain) | Holds the bridge account's built-in `ADMIN` role. Manages bridge roles, restores a paused bridge, and controls allowlists and fee-policy selection. | Creates RBAC_CONFIG, PAUSE_CONFIG, and NETWORK_ACCOUNT_CONFIG notes |
Expand Down Expand Up @@ -480,19 +480,28 @@ holders are seeded into the access-control components at account creation time.

### 3.2 Faucet Account Component
Comment thread
Fumuran marked this conversation as resolved.
Outdated

The faucet account has the `agglayer_faucet` component (`components/faucet.masm`),
which is a thin wrapper, on top of `Ownable2Step` + the RBAC access-control stack
(`RoleBasedAccessControl` + `Authority::RbacControlled`), that re-exports the
standard Miden fungible-faucet procedures:
The faucet account carries no AggLayer-specific component. It is the standard `FungibleFaucet`
component (`miden-standards/asm/components/faucets/fungible_faucet/fungible_faucet.masm`),
installed on top of `Ownable2Step` + the RBAC access-control stack (`RoleBasedAccessControl` +
`Authority::RbacControlled`), so the AggLayer faucet is an ordinary network fungible faucet whose
owner happens to be the bridge.

- `mint_and_send` (from `miden::standards::faucets::fungible::mint_and_send`)
- `receive_and_burn` (from `miden::standards::faucets::fungible::receive_and_burn`)
- `has_procedure` (from the `CodeInspection` component, so the unified MINT/BURN note scripts
can reflectively detect the faucet kind)
The procedures the bridge protocol relies on are:

The underlying library code lives in `asm/agglayer/faucet/mod.masm`.
- `mint_and_send` - bridge-in, gated by the active mint policy (`owner_only`)
- `receive_and_burn` - bridge-out, gated by the active burn policy
- `has_procedure` - so the unified MINT/BURN note scripts can reflectively detect the faucet kind

#### `agglayer_faucet::mint_and_send`
The component additionally exposes the standard token-config and metadata accessors -
`get_name`, `get_token_symbol`, `get_decimals`, `get_token_supply`, `get_max_supply`,
`get_token_config`, `is_max_supply_mutable`, `get_mutability_config`, the `is_*_mutable` views,
and the `set_max_supply` / `set_description` / `set_logo_uri` / `set_external_link` setters. The
getters are what allow the bridge to recompute `keccak256(abi.encode(name, symbol, decimals))`
from faucet storage via FPI. The setters are authority-gated (so they fall to `FAUCET_ADMIN`) and,
with the faucet's note allowlist limited to MINT and BURN, unreachable until the admin explicitly
allowlists a note that calls them.

#### `fungible::mint_and_send`

| | |
|-|-|
Expand All @@ -502,7 +511,7 @@ The underlying library code lives in `asm/agglayer/faucet/mod.masm`.
| **Context** | Consuming a `MINT` note on the faucet account |
| **Panics** | Faucet owner verification fails; minting exceeds supply; the asset stored in the MINT note does not belong to the consuming faucet |

Re-export of `miden::standards::faucets::fungible::mint_and_send`. Mints the asset
Mints the asset
identified by `ASSET_ID` / `ASSET_VALUE` and creates an output note with the given
recipient. Requires the faucet's owner (the bridge account) to be the creator of this note
(the bridge is stored in `Ownable2Step` storage slot as the owner; the faucet's
Expand All @@ -511,9 +520,9 @@ recipient. Requires the faucet's owner (the bridge account) to be the creator of
for the active faucet and panics if the stored `ASSET_ID` does not belong to that faucet,
which binds the MINT note to its resolved faucet (see §4.10).

#### `agglayer_faucet::receive_and_burn`
#### `fungible::receive_and_burn`

This is a re-export of `miden::standards::faucets::fungible::receive_and_burn`. It burns the fungible asset from the active note, decreasing the faucet's token supply.
Burns the fungible asset from the active note, decreasing the faucet's token supply.

| | |
|-|-|
Expand All @@ -525,9 +534,20 @@ This is a re-export of `miden::standards::faucets::fungible::receive_and_burn`.

#### Faucet Account Storage

The faucet contributes the full standard `FungibleFaucet` slot set (25 value slots):

| Slot name | Slot type | Value encoding | Purpose |
|-----------|-----------|----------------|---------|
| Faucet metadata (standard) | Value | `[token_supply, max_supply, decimals, token_symbol]` | Standard `NetworkFungibleFaucet` metadata |
| `token_config` | Value | `[token_supply, max_supply, decimals, token_symbol]` | Standard fungible faucet token configuration |
| `token_name_0` .. `token_name_1` | Value | Fixed-width string chunks | Token name, up to 32 UTF-8 bytes. Part of the AggLayer metadata hash preimage |
| `mutability_config` | Value | Boolean flags | Whether description / logo URI / external link / max supply may be updated. All `false` for AggLayer faucets |
| `token_description_0` .. `_6` | Value | Fixed-width string chunks | Optional description; empty for AggLayer faucets |
| `logo_uri_0` .. `_6` | Value | Fixed-width string chunks | Optional logo URI; empty for AggLayer faucets |
| `external_link_0` .. `_6` | Value | Fixed-width string chunks | Optional external link; empty for AggLayer faucets |

Conversion metadata (origin token address, origin network, scale, metadata hash) is **not** stored
here. It lives on the bridge in `faucet_metadata_map`, written at registration time (see
[Section 7](#7-faucet-registry)).

**Companion component storage slots:**

Expand Down Expand Up @@ -1420,9 +1440,10 @@ be referenced by a `B2AGG` (bridge-out) or `CLAIM` (bridge-in) note. Registratio
same flow for both kinds; the `is_native` flag in the `CONFIG_AGG_BRIDGE` note storage tells
the bridge which dispatch path to take for each future bridge operation against that faucet.

The `AggLayerFaucet` Rust struct (`src/faucet.rs`) holds only
token metadata — symbol, decimals, max supply, and token supply
(TODO Missing token name ([#2585](https://github.com/0xMiden/protocol/issues/2585))).
The faucet holds only token metadata — name, symbol, decimals, max supply, and token supply.
Because the name is stored alongside the symbol and decimals, the full metadata hash preimage
`abi.encode(name, symbol, decimals)` is recoverable from faucet storage, which is what makes
registration-time verification possible.
Conversion metadata (origin address, origin network, scale, and metadata hash) is
*not* stored on the faucet; it is carried by the `CONFIG_AGG_BRIDGE` note at registration
time and written directly into the bridge's `faucet_metadata_map`. The metadata hash is
Expand Down
6 changes: 0 additions & 6 deletions crates/miden-agglayer/asm/agglayer/faucet/mod.masm

This file was deleted.

1 change: 0 additions & 1 deletion crates/miden-agglayer/asm/agglayer/mod.masm
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
pub mod bridge
pub mod faucet
pub mod notes
14 changes: 0 additions & 14 deletions crates/miden-agglayer/asm/components/faucet/faucet.masm

This file was deleted.

14 changes: 0 additions & 14 deletions crates/miden-agglayer/asm/components/faucet/miden-project.toml

This file was deleted.

2 changes: 1 addition & 1 deletion crates/miden-agglayer/asm/components/miden-project.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["bridge", "faucet"]
members = ["bridge"]

[workspace.package]
version = "0.16.0"
Expand Down
34 changes: 7 additions & 27 deletions crates/miden-agglayer/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,6 @@ use miden_standards::account::fees::{
ConstantFeeManager,
FeePolicyManager,
};
use miden_standards::account::policies::{
BurnPolicy,
MintPolicy,
TokenPolicyManager,
TransferPolicy,
};

// CONSTANTS
// ================================================================================================
Expand Down Expand Up @@ -152,7 +146,11 @@ fn compile_agglayer_package(
///
/// This file contains:
/// - AggLayer Bridge code commitment.
/// - AggLayer Faucet code commitment.
///
/// The AggLayer faucet is the standard [`FungibleFaucet`] and so carries no bespoke commitment;
/// callers identify it through its interface instead (see `FungibleFaucet::try_from`).
///
/// [`FungibleFaucet`]: miden_standards::account::faucets::FungibleFaucet
Comment thread
Fumuran marked this conversation as resolved.
Outdated
fn generate_agglayer_constants(
target_file: impl AsRef<Path>,
component_packages: Vec<Arc<Package>>,
Expand All @@ -177,9 +175,9 @@ fn generate_agglayer_constants(
// code commitment, so it doesn't matter what does this metadata holds.
let dummy_metadata = AccountComponentMetadata::new("dummy");

// iterate over the AggLayer Bridge and AggLayer Faucet packages
// iterate over the AggLayer component packages (currently only the bridge)
Comment thread
Fumuran marked this conversation as resolved.
Outdated
for package in component_packages {
// Derive the short component name (e.g. "bridge" / "faucet") from the package name
// Derive the short component name (e.g. "bridge") from the package name
// (e.g. "miden-agglayer-bridge").
let component_name = package
.name
Expand Down Expand Up @@ -217,24 +215,6 @@ fn generate_agglayer_constants(
components.push(AccountComponent::from(PausableManager));
components
.push(AccountComponent::from(ConstantFeeManager::for_basic_constant_fee_policy()));
} else if component_name == "faucet" {
components.push(AccountComponent::from(
miden_standards::account::access::Ownable2Step::new(dummy_account_id),
));
components.extend(AccessControl::Rbac {
admin: dummy_account_id,
procedure_roles: std::collections::BTreeMap::new(),
});
let token_policy_manager = TokenPolicyManager::builder()
.active_mint_policy(MintPolicy::owner_only())
.active_burn_policy(BurnPolicy::owner_only())
.active_send_policy(TransferPolicy::allow_all())
.active_receive_policy(TransferPolicy::allow_all())
.build();

components.extend(token_policy_manager);
components
.push(AccountComponent::from(ConstantFeeManager::for_basic_constant_fee_policy()));
}

// use `AccountCode` to merge codes of agglayer and authentication components
Expand Down
33 changes: 33 additions & 0 deletions crates/miden-agglayer/src/eth_types/metadata_hash.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use alloc::string::ToString;
use alloc::vec::Vec;

use alloy_sol_types::{SolValue, sol};
use miden_core::utils::bytes_to_packed_u32_elements;
use miden_crypto::hash::keccak::Keccak256;
use miden_protocol::Felt;
use miden_standards::account::faucets::FungibleFaucet;

// ================================================================================================
// METADATA HASH
Expand Down Expand Up @@ -42,6 +44,18 @@ impl MetadataHash {
Self::from_abi_encoded(&encoded)
}

/// Computes the metadata hash from a faucet's own token metadata.
///
/// Preferred over [`Self::from_token_info`] when the faucet exists, since it derives the hash
/// registered on the bridge from the very values held in faucet storage: the two cannot drift.
Comment thread
Fumuran marked this conversation as resolved.
Outdated
pub fn from_fungible_faucet(faucet: &FungibleFaucet) -> Self {
Self::from_token_info(
faucet.token_name().as_str(),
&faucet.symbol().to_string(),
faucet.decimals(),
)
}

/// Returns the raw 32-byte array.
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.0
Expand Down Expand Up @@ -124,6 +138,25 @@ mod tests {
assert_eq!(hash, hash_from_info, "from_abi_encoded and from_token_info must agree");
}

/// A faucet's own metadata yields the same hash as passing its name, symbol and decimals
/// individually. This is the invariant that lets the bridge recompute the registered hash
/// from faucet storage (see issue #2586).
#[test]
fn test_metadata_hash_from_fungible_faucet_matches_token_info() {
Comment thread
Fumuran marked this conversation as resolved.
Outdated
let faucet = crate::agglayer_faucet_metadata(
"Test Token",
"TEST",
12,
Felt::from(1000u32),
Felt::ZERO,
);

assert_eq!(
MetadataHash::from_fungible_faucet(&faucet),
MetadataHash::from_token_info("Test Token", "TEST", 12),
);
}

fn hex_to_vec(hex: &str) -> std::vec::Vec<u8> {
(0..hex.len())
.step_by(2)
Expand Down
Loading
Loading