diff --git a/Makefile b/Makefile index 03d5211d73..72f6eac962 100644 --- a/Makefile +++ b/Makefile @@ -189,3 +189,16 @@ install-tools: ## Installs development tools required by the Makefile (mdbook, t cargo install taplo-cli --locked cargo install cargo-machete --locked @echo "Development tools installation complete!" + +# -- documentation --------------------------------------------------------------------------------- + +AGGLAYER_DIAGRAMS_DIR := crates/miden-agglayer/diagrams +EXCALIDRAW_SOURCES := $(wildcard $(AGGLAYER_DIAGRAMS_DIR)/*.excalidraw) +EXCALIDRAW_PNGS := $(EXCALIDRAW_SOURCES:.excalidraw=.png) + +.PHONY: agglayer-spec +agglayer-spec: $(EXCALIDRAW_PNGS) ## Exports AggLayer spec diagrams from .excalidraw to .png + +$(AGGLAYER_DIAGRAMS_DIR)/%.png: $(AGGLAYER_DIAGRAMS_DIR)/%.excalidraw + @command -v npx >/dev/null 2>&1 || { echo "npx not found. Install Node.js first."; exit 1; } + npx excalidraw-brute-export-cli -i $< --format png -o $@ --scale 2 diff --git a/crates/miden-agglayer/SPEC.md b/crates/miden-agglayer/SPEC.md index e80ab2d82c..cddf6461fd 100644 --- a/crates/miden-agglayer/SPEC.md +++ b/crates/miden-agglayer/SPEC.md @@ -25,22 +25,132 @@ implementation are called out inline with `TODO (Future)` markers. | **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. | Not an onchain entity; creates notes targeting bridge/faucet | | **Bridge Operator** (offchain) | Deploys bridge and faucet accounts. Creates CONFIG_AGG_BRIDGE notes to register faucets. Must use the bridge admin account. | Not an onchain entity; creates config notes | -### Current permissions +--- + +## 2. Protocol Description + +The crate `miden-agglayer` implements the AggLayer bridging protocol on the Miden blockchain. This section provides a high-level description of the implementation on Miden, covering the main operational flows. + +### 2.1 Bridge-out (Miden to AggLayer) + +![Bridge-out flow](diagrams/bridge-out.png) + +A user initiates a bridge-out by creating a [`B2AGG`](#41-b2agg) note containing a single fungible +asset and the destination network/address. The bridge account consumes this note: + +1. Validates that the asset's faucet is registered in the faucet registry. +2. FPIs to the faucet (`agglayer_faucet::asset_to_origin_asset`) to obtain the scaled + U256 amount, origin token address, and origin network. +3. FPIs to the faucet (`agglayer_faucet::get_metadata_hash`) to obtain the metadata hash. +4. Constructs a leaf-data structure (leaf type, origin network, origin token address, + destination network, destination address, amount, metadata hash). +5. Computes the Keccak-256 leaf value and appends it to the Local Exit Tree (LET). +6. Creates a public [`BURN`](#45-burn-generated) note targeting the faucet, which burns the asset and + decreases the faucet's token supply. + +The leaf appended to the LET can later be included in a Merkle proof on any +AggLayer-connected chain to claim the bridged asset. + +TODO: The bridge currently has no emergency pause mechanism to halt operations +([#2696](https://github.com/0xMiden/protocol/issues/2696)). + +### 2.2 Bridge-in (AggLayer to Miden) + +![Bridge-in flow](diagrams/bridge-in.png) + +When a new deposit into Miden is made on an AggLayer-connected chain, Miden needs to be "informed" of the updated AggLayer state by having a new Global Exit Root (GER) injected - see [Section 2.3](#23-ger-injection). + +Once the GER is injected, any user can initiate the claim process by creating a [`CLAIM`](#42-claim) note on Miden containing Merkle proofs and leaf data (by monitoring updates to the AggLayer contract on Ethereum L1). This will typically be done by a claim manager service for convenience, but is permissionless and open to any user. +The `CLAIM` note is consumed by the bridge account: + +1. Validates the Global Exit Root (GER) is known in the bridge's `ger_map`. +2. Parses the global index to determine whether this is a mainnet or rollup deposit, + and extracts the leaf index and source bridge network. +3. Verifies the Merkle proof: for mainnet deposits, a single proof against + `mainnet_exit_root`; for rollup deposits, a two-level proof (leaf against + `local_exit_root`, then `local_exit_root` against `rollup_exit_root`). +4. Updates the claimed global index (CGI) chain hash: + `NEW_CGI = Keccak256(OLD_CGI, Keccak256(GLOBAL_INDEX, LEAF_VALUE))`. +5. Checks and sets the claim nullifier to prevent double-claiming. +6. Looks up the faucet from the origin token address via the token registry. +7. Verifies the claim amount against the leaf's U256 amount and the faucet's scale factor. +8. Creates a [`MINT`](#47-mint-generated) note targeting the faucet. + +The faucet consumes the `MINT` note, mints the specified amount, and creates a [`P2ID`](#46-p2id-generated) note +that delivers the minted assets to the recipient's Miden account. + +TODO: Destination network from the leaf data is not validated against Miden's own network +ID ([#2698](https://github.com/0xMiden/protocol/issues/2698)). + +TODO: The leaf type field is not validated to be `LEAF_TYPE_ASSET` (0) +([#2699](https://github.com/0xMiden/protocol/issues/2699)). + +TODO: Claims cannot be reversed once the nullifier is set +([#2703](https://github.com/0xMiden/protocol/issues/2703)). + +### 2.3 GER Injection + +![GER injection flow](diagrams/ger-injection.png) + +Global Exit Roots represent a snapshot of exit tree roots across all AggLayer-connected +chains. A GER Manager observes L1 GER updates and creates [`UPDATE_GER`](#44-update_ger) notes +on Miden. The bridge consumes these notes: + +1. Asserts the note sender is the designated GER manager. +2. Computes `KEY = poseidon2::merge(GER_LOWER, GER_UPPER)`. +3. Stores `KEY -> [1, 0, 0, 0]` in the `ger_map`, marking the GER as known. + +Subsequent CLAIM notes reference a GER that must be present in this map for the claim +to be valid. + +TODO: GERs cannot be removed once inserted +([#2702](https://github.com/0xMiden/protocol/issues/2702)). + +TODO: No hash chain tracks GER insertions for proof generation +([#2707](https://github.com/0xMiden/protocol/issues/2707)). + +TODO: Duplicate GER insertions are silently accepted +([#2708](https://github.com/0xMiden/protocol/issues/2708)). -| Note type | Issuer (sender check) | Consumer (consuming-account check) | -|-----------|----------------------|-----------------------------------| -| B2AGG (bridge-out) | Any user -- not restricted | Bridge account -- **enforced** via `NetworkAccountTarget` attachment | -| B2AGG (reclaim) | Any user -- not restricted | Original sender only -- **enforced**: script checks `sender == consuming account` | -| CONFIG_AGG_BRIDGE | Bridge admin only -- **enforced** by `bridge_config::register_faucet` procedure | Bridge account -- **enforced** via `NetworkAccountTarget` attachment | -| UPDATE_GER | GER manager only -- **enforced** by `bridge_config::update_ger` procedure | Bridge account -- **enforced** via `NetworkAccountTarget` attachment | -| CLAIM | Anyone -- not restricted | Bridge account -- **enforced** via `NetworkAccountTarget` attachment | -| MINT | Bridge account only -- **enforced** by faucet's `owner_only` mint policy via `Ownable2Step` (asserts note sender is the faucet's owner, i.e. the bridge) | Target faucet only -- **enforced** via `NetworkAccountTarget` attachment | +### 2.4 Faucet Registration + +![Faucet registration flow](diagrams/faucet-registration.png) + +Each bridged token requires a dedicated AggLayer faucet on Miden. The Bridge Operator +creates [`CONFIG_AGG_BRIDGE`](#43-config_agg_bridge) notes to register faucets. The bridge consumes these notes, +asserting the sender is the bridge admin, then registers the faucet in both the faucet +registry and the token registry. For a detailed description of the faucet and token +registries, see [Section 7](#7-faucet-registry). + +TODO: Faucet registrations are permanent; no remapping or deregistration is supported +([#2704](https://github.com/0xMiden/protocol/issues/2704), +[#2705](https://github.com/0xMiden/protocol/issues/2705)). + +TODO: Faucet existence and code commitment are not validated during registration +([#2709](https://github.com/0xMiden/protocol/issues/2709)). + +### 2.5 Administration + +The bridge has two administrative roles set at account creation time: + +- **Bridge admin** (`admin_account_id`): authorizes faucet registration via + [`CONFIG_AGG_BRIDGE`](#43-config_agg_bridge) notes. +- **GER manager** (`ger_manager_account_id`): authorizes GER updates via [`UPDATE_GER`](#44-update_ger) + notes. + +Both roles are verified by checking the note sender against the stored account ID. + +TODO: Administrative roles cannot be transferred after account creation +([#2706](https://github.com/0xMiden/protocol/issues/2706)). + +TODO: No emergency pause mechanism exists +([#2696](https://github.com/0xMiden/protocol/issues/2696)). --- -## 2. Contracts and Public Interfaces +## 3. Contracts and Public Interfaces -### 2.1 Bridge Account Component +### 3.1 Bridge Account Component The bridge account has a single unified `bridge` component (`components/bridge.masm`), which is a thin wrapper that re-exports procedures from the `agglayer` library modules: @@ -137,7 +247,7 @@ Validates a bridge-in claim and creates a MINT note targeting the faucet: 7. Verifies the `faucet_mint_amount` against the leaf data's U256 amount and the faucet's scale factor (via FPI to `agglayer_faucet::get_scale`), using `asset_conversion::verify_u256_to_native_amount_conversion`. -8. Builds a MINT output note targeting the faucet (see [Section 3.7](#37-mint-generated)). +8. Builds a MINT output note targeting the faucet (see [Section 4.7](#47-mint-generated)). #### Bridge Account Storage @@ -159,7 +269,7 @@ Validates a bridge-in claim and creates a MINT note targeting the faucet: Initial state: all map slots empty, all value slots `[0, 0, 0, 0]` except `admin_account_id` and `ger_manager_account_id` which are set at account creation time. -### 2.2 Faucet Account Component +### 3.2 Faucet Account Component The faucet account has the `agglayer_faucet` component (`components/faucet.masm`), which is a thin wrapper that re-exports procedures from the `agglayer` library: @@ -257,17 +367,17 @@ companion components required by `network_fungible::mint_and_send`: --- -## 3. Note Types and Storage Layouts +## 4. Note Types and Storage Layouts **Encoding conventions:** All multi-byte values in note storage (addresses, U256 integers, Keccak-256 hashes) are encoded as arrays of u32 felts via `bytes_to_packed_u32_felts`: big-endian limb order with **little-endian byte order** -within each 4-byte limb (see [Section 5.5](#55-endianness-summary)). Scalar u32 fields +within each 4-byte limb (see [Section 6.5](#65-endianness-summary)). Scalar u32 fields (network IDs) are byte-reversed at storage time so their in-memory bytes align with the Keccak preimage format directly — the felt value does **not** equal the numeric value (e.g., chain ID `1` = `0x00000001` is stored as felt `0x01000000`). -### 3.1 B2AGG +### 4.1 B2AGG (Bridge-to-AggLayer) **Purpose:** User bridges an asset from Miden to the AggLayer. @@ -309,7 +419,23 @@ Keccak preimage format directly — the felt value does **not** equal the numeri - **Reclaim:** Consuming account is the original sender -> assets are added back to the account via `basic_wallet::add_assets_to_account`. No output notes. -### 3.2 CLAIM +#### Permissions + +**Bridge-out:** + +| Role | Enforcement | +|------|------------| +| **Issuer** | Any user -- not restricted | +| **Consumer** | Bridge account -- **enforced** via `NetworkAccountTarget` attachment | + +**Reclaim:** + +| Role | Enforcement | +|------|------------| +| **Issuer** | Any user -- not restricted | +| **Consumer** | Original sender only -- **enforced**: script checks `sender == consuming account` | + +### 4.2 CLAIM **Purpose:** Claim assets, which were deposited on any AggLayer-connected rollup, on Miden. Consumed by the bridge account, which validates the proof, looks up the faucet via the @@ -373,7 +499,14 @@ The storage is divided into three logical regions: proof data (felts 0-535), lea faucet via the token registry, verifies the amount conversion, then builds a MINT output note targeting the faucet. -### 3.3 CONFIG_AGG_BRIDGE +#### Permissions + +| Role | Enforcement | +|------|------------| +| **Issuer** | Anyone -- not restricted | +| **Consumer** | Bridge account -- **enforced** via `NetworkAccountTarget` attachment | + +### 4.3 CONFIG_AGG_BRIDGE **Purpose:** Registers a faucet in the bridge's faucet registry. @@ -412,7 +545,14 @@ The storage is divided into three logical regions: proof data (felts 0-535), lea `bridge_config::register_faucet` (which asserts sender is bridge admin and performs two-step registration into `faucet_registry_map` and `token_registry_map`). -### 3.4 UPDATE_GER +#### Permissions + +| Role | Enforcement | +|------|------------| +| **Issuer** | Bridge admin only -- **enforced** by `bridge_config::register_faucet` procedure | +| **Consumer** | Bridge account -- **enforced** via `NetworkAccountTarget` attachment | + +### 4.4 UPDATE_GER **Purpose:** Stores a new Global Exit Root (GER) in the bridge account so that subsequent CLAIM notes can be verified against it. @@ -451,7 +591,14 @@ CLAIM notes can be verified against it. `bridge_config::update_ger` (which asserts sender is GER manager), which computes `poseidon2::merge(GER_LOWER, GER_UPPER)` and stores the result in the GER map. -### 3.5 BURN (generated) +#### Permissions + +| Role | Enforcement | +|------|------------| +| **Issuer** | GER manager only -- **enforced** by `bridge_config::update_ger` procedure | +| **Consumer** | Bridge account -- **enforced** via `NetworkAccountTarget` attachment | + +### 4.5 BURN (generated) **Purpose:** Created by `bridge_out::bridge_out` to burn the bridged asset on the faucet. @@ -488,7 +635,14 @@ The standard BURN script calls `faucets::burn` on the consuming faucet account. validates that the note contains exactly one fungible asset issued by that faucet and decreases the faucet's total token supply by the burned amount. -### 3.6 P2ID (generated) +#### Permissions + +| Role | Enforcement | +|------|------------| +| **Issuer** | Bridge account (created by `bridge_out::bridge_out`) | +| **Consumer** | Target faucet only -- **enforced** via `NetworkAccountTarget` attachment | + +### 4.6 P2ID (generated) **Purpose:** Created by the faucet (via `mint_and_send`) when consuming a MINT note, to deliver minted assets to the recipient. @@ -529,7 +683,14 @@ Consuming account must match `target_account_id` from note storage (enforced by script). All note assets are added to the consuming account via `basic_wallet::add_assets_to_account`. -### 3.7 MINT (generated) +#### Permissions + +| Role | Enforcement | +|------|------------| +| **Issuer** | Faucet account (created by `mint_and_send`) | +| **Consumer** | Destination account only -- **enforced** by P2ID script (checks `target_account_id`) | + +### 4.7 MINT (generated) **Purpose:** Created by `bridge_in::claim` on the bridge account. Consumed by the faucet to mint and distribute assets to the recipient. @@ -587,22 +748,29 @@ After the policy check passes, `mint_and_send` mints the specified amount and cr P2ID output note for the recipient using the storage items (script root, serial number, destination account ID, tag). +#### Permissions + +| Role | Enforcement | +|------|------------| +| **Issuer** | Bridge account only -- **enforced** by faucet's `owner_only` mint policy via `Ownable2Step` (asserts note sender is the faucet's owner, i.e. the bridge) | +| **Consumer** | Target faucet only -- **enforced** via `NetworkAccountTarget` attachment | + --- -## 4. Amount Conversion +## 5. Amount Conversion *This section is a placeholder. Content to be added.* --- -## 5. Ethereum ↔ Miden Address Conversion +## 6. Ethereum ↔ Miden Address Conversion The AggLayer bridge operates across two address spaces: Ethereum's 20-byte addresses and Miden's `AccountId` (two field elements). This section specifies the encoding that maps between them, as implemented in Rust (`eth_types/address.rs`) and MASM (`agglayer/common/eth_address.masm`). -### 5.1 Background +### 6.1 Background Miden's `AccountId` (version 0) consists of two Goldilocks field elements: @@ -620,7 +788,7 @@ Ethereum addresses are 20-byte (160-bit) values. Because every valid `AccountId` 16 bytes (prefix: 8 bytes, suffix: 8 bytes), it can be embedded into the lower 16 bytes of an Ethereum address with 4 zero-padding bytes at the top. -### 5.2 Embedded Format +### 6.2 Embedded Format An `AccountId` is embedded in a 20-byte Ethereum address as follows: @@ -655,7 +823,7 @@ non-zero, if the packed `u64` values exceed the field modulus, or if the resulti don't form a valid `AccountId`. Arbitrary Ethereum addresses (e.g., from EOAs or contracts on L1) cannot generally be decoded into `AccountId` values. -### 5.3 MASM Limb Representation +### 6.3 MASM Limb Representation Inside the Miden VM, a 20-byte Ethereum address is represented as 5 field elements, each holding a `u32` value. This layout uses **big-endian limb @@ -676,9 +844,9 @@ Keccak-256 precompile. The Rust function `EthAddressFormat::to_elements()` produces exactly this 5-felt array from a 20-byte address. -### 5.4 Conversion Procedures +### 6.4 Conversion Procedures -#### 5.4.1 `AccountId` → Ethereum Address (Rust) +#### 6.4.1 `AccountId` → Ethereum Address (Rust) `EthAddressFormat::from_account_id(account_id: AccountId) -> EthAddressFormat` @@ -694,7 +862,7 @@ This is the **external API** used by the bridge interface. It lets a user conver This conversion is **infallible**: every valid `AccountId` produces a valid 20-byte address. -#### 5.4.2 Ethereum Address → `AccountId` (Rust) +#### 6.4.2 Ethereum Address → `AccountId` (Rust) `EthAddressFormat::to_account_id(&self) -> Result` @@ -723,7 +891,7 @@ extract the recipient's `AccountId` from the embedded Ethereum address and e.g. | `FeltOutOfField` | A `u64` value ≥ the Goldilocks prime `p` | | `InvalidAccountId` | The resulting felts don't form a valid `AccountId` | -#### 5.4.3 Ethereum Address → `AccountId` (MASM) +#### 6.4.3 Ethereum Address → `AccountId` (MASM) `eth_address::to_account_id` — Module: `agglayer::common::eth_address` @@ -743,7 +911,7 @@ Invocation: exec 1. `assertz limb0` — the most-significant limb must be zero (error: `ERR_MSB_NONZERO`). 2. Build `suffix` from `(limb4, limb3)`: - a. Validate both values are `u32` (error: `ERR_NOT_U32`). - - b. Byte-swap each limb from little-endian to big-endian via `utils::swap_u32_bytes` (see [Section 5.5](#55-endianness-summary)). + - b. Byte-swap each limb from little-endian to big-endian via `utils::swap_u32_bytes` (see [Section 6.5](#65-endianness-summary)). - c. Pack into a felt: `suffix = bswap(limb3) × 2^32 + bswap(limb4)`. - d. Verify no mod-p reduction: split the felt back via `u32split` and assert equality with the original limbs (error: `ERR_FELT_OUT_OF_FIELD @@ -772,17 +940,17 @@ Outputs: [swapped] Reverses the byte order of a `u32`: `[b0, b1, b2, b3] → [b3, b2, b1, b0]`. -#### 5.4.4 Ethereum Address → Field Elements (Rust) +#### 6.4.4 Ethereum Address → Field Elements (Rust) `EthAddressFormat::to_elements(&self) -> Vec` Converts the 20-byte address into a field element array for use in the Miden VM. Each 4-byte chunk is interpreted as a **little-endian** `u32` and stored as a `Felt`. -The output order matches the big-endian limb order described in [Section 5.3](#53-masm-limb-representation). +The output order matches the big-endian limb order described in [Section 6.3](#63-masm-limb-representation). -This is used when constructing `NoteStorage` for B2AGG notes (see [Section 3.1](#31-b2agg)) and CLAIM notes (see [Section 3.2](#32-claim)). +This is used when constructing `NoteStorage` for B2AGG notes (see [Section 4.1](#41-b2agg)) and CLAIM notes (see [Section 4.2](#42-claim)). -### 5.5 Endianness Summary +### 6.5 Endianness Summary The conversion involves multiple levels of byte ordering: this table clarifies the different conventions used. @@ -795,7 +963,7 @@ The conversion involves multiple levels of byte ordering: this table clarifies t The byte swap (`swap_u32_bytes`) in the MASM `build_felt` procedure bridges between the little-endian bytes within each limb in `NoteStorage` and the big-endian-bytes within the `u32` pairs needed to construct the prefix and suffix in the MASM `build_felt` procedure. -### 5.6 Roundtrip Guarantee +### 6.6 Roundtrip Guarantee The encoding is a bijection over the set of valid `AccountId` values: for every valid `AccountId`, `from_account_id` followed by `to_account_id` (or the MASM equivalent) @@ -803,7 +971,7 @@ recovers the original. --- -## 6. Faucet Registry +## 7. Faucet Registry The AggLayer bridge connects multiple chains, each with its own native token ecosystem. When tokens move between chains, they need a representation on the destination chain. @@ -819,7 +987,7 @@ Terminology: AggLayer faucet. On EVM chains, each non-native Miden token would be represented by a deployed wrapped ERC20 contract. -A faucet must be registered in the [Bridge Contract](#21-bridge-account-component) before it can participate in bridging. The +A faucet must be registered in the [Bridge Contract](#31-bridge-account-component) before it can participate in bridging. The bridge maintains two registry maps: - **Faucet registry** (`agglayer::bridge::faucet_registry_map`): maps faucet account IDs @@ -830,9 +998,9 @@ bridge maintains two registry maps: correct faucet for a given origin token (see `bridge_config::lookup_faucet_by_token_address`). -Both registries are populated atomically by `bridge_config::register_faucet` during the [`CONFIG_AGG_BRIDGE`](#33-config_agg_bridge) note consumption. +Both registries are populated atomically by `bridge_config::register_faucet` during the [`CONFIG_AGG_BRIDGE`](#43-config_agg_bridge) note consumption. -### 6.1 Bridging-in: Registering non-native faucets on Miden +### 7.1 Bridging-in: Registering non-native faucets on Miden When a new ERC20 token is bridged to Miden for the first time, a corresponding AggLayer faucet account must be created and registered. The faucet serves as the mint/burn @@ -847,7 +1015,7 @@ configuration: - Scale factor: the exponent used to convert between EVM U256 amounts and Field elements on Miden - Metadata hash: `keccak256(abi.encode(name, symbol, decimals))`. This is precomputed by the bridge admin at faucet creation time and is currently not verified onchain (TODO Verify metadata hash onchain ([#2586](https://github.com/0xMiden/protocol/issues/2586))) -Registration is performed via [`CONFIG_AGG_BRIDGE`](#33-config_agg_bridge) notes. The bridge +Registration is performed via [`CONFIG_AGG_BRIDGE`](#43-config_agg_bridge) notes. The bridge operator creates a `CONFIG_AGG_BRIDGE` note containing the faucet's account ID and the origin token address, then sends it to the bridge account. On consumption, the note script calls `bridge_config::register_faucet`, which performs a two-step registration: @@ -860,7 +1028,7 @@ script calls `bridge_config::register_faucet`, which performs a two-step registr The token registry enables the bridge to resolve which Miden-side faucet corresponds to a given origin token address during CLAIM note processing. When the bridge -processes a [`CLAIM`](#32-claim) note, it reads the origin token address from the leaf data and calls +processes a [`CLAIM`](#42-claim) note, it reads the origin token address from the leaf data and calls `bridge_config::lookup_faucet_by_token_address` to find the registered faucet. This lookup hashes the address with Poseidon2 and retrieves the faucet ID from the token registry map. If the token address is not registered, the `CLAIM` note consumption will fail. @@ -869,7 +1037,7 @@ This means that the bridge admin must register the faucet on the Miden side befo The bridge admin is a trusted role, and is the sole entity that can register faucets on the Miden side (due to the caller restriction on [`bridge_config::register_faucet`](#bridge_configregister_faucet)). -### 6.2 Bridging-out: How Miden-native tokens are registered on other chains +### 7.2 Bridging-out: How Miden-native tokens are registered on other chains When an asset is bridged out from Miden, [`bridge_out::bridge_out`](#bridge_outbridge_out) constructs a leaf for the Local Exit Tree. The leaf includes the metadata hash, which the bridge fetches from @@ -889,7 +1057,7 @@ A Miden-native faucet uses the same storage layout and registration flow as a wrapped faucet. The key difference is what values are stored in the conversion metadata: -- `origin_token_address`: the faucet's own `AccountId` as per the [Embedded Format](#52-embedded-format). +- `origin_token_address`: the faucet's own `AccountId` as per the [Embedded Format](#62-embedded-format). - `origin_network`: Miden's network ID as assigned by AggLayer (currently unassigned). - `metadata_hash`: `keccak256(abi.encode(name, symbol, decimals))` - same as for wrapped faucets. diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm index 7c486135e0..4df7db2aac 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm @@ -8,41 +8,42 @@ use miden::protocol::native_account # ================================================================================================= const ERR_GER_NOT_FOUND = "GER not found in storage" -const ERR_FAUCET_NOT_REGISTERED="faucet is not registered in the bridge's faucet registry" -const ERR_TOKEN_NOT_REGISTERED="token address is not registered in the bridge's token registry" -const ERR_SENDER_NOT_BRIDGE_ADMIN="note sender is not the bridge admin" -const ERR_SENDER_NOT_GER_MANAGER="note sender is not the global exit root manager" +const ERR_FAUCET_NOT_REGISTERED = "faucet is not registered in the bridge's faucet registry" +const ERR_TOKEN_NOT_REGISTERED = "token address is not registered in the bridge's token registry" +const ERR_SENDER_NOT_BRIDGE_ADMIN = "note sender is not the bridge admin" +const ERR_SENDER_NOT_GER_MANAGER = "note sender is not the global exit root manager" # CONSTANTS # ================================================================================================= # Storage slots -const BRIDGE_ADMIN_SLOT=word("agglayer::bridge::admin_account_id") -const GER_MANAGER_SLOT=word("agglayer::bridge::ger_manager_account_id") -const GER_MAP_STORAGE_SLOT=word("agglayer::bridge::ger_map") -const FAUCET_REGISTRY_MAP_SLOT=word("agglayer::bridge::faucet_registry_map") -const TOKEN_REGISTRY_MAP_SLOT=word("agglayer::bridge::token_registry_map") +const BRIDGE_ADMIN_SLOT = word("agglayer::bridge::admin_account_id") +const GER_MANAGER_SLOT = word("agglayer::bridge::ger_manager_account_id") +const GER_MAP_STORAGE_SLOT = word("agglayer::bridge::ger_map") +const FAUCET_REGISTRY_MAP_SLOT = word("agglayer::bridge::faucet_registry_map") +const TOKEN_REGISTRY_MAP_SLOT = word("agglayer::bridge::token_registry_map") # Flags -const GER_KNOWN_FLAG=1 -const IS_FAUCET_REGISTERED_FLAG=1 +const GER_KNOWN_FLAG = 1 +const IS_FAUCET_REGISTERED_FLAG = 1 # Offset in the local memory of the `hash_token_address` procedure -const TOKEN_ADDR_HASH_PTR=0 +const TOKEN_ADDR_HASH_PTR = 0 # PUBLIC INTERFACE # ================================================================================================= #! Updates the Global Exit Root (GER) in the bridge account storage. #! -#! Computes hash(GER) = poseidon2::merge(GER_LOWER, GER_UPPER) and stores it in a map -#! with value [GER_KNOWN_FLAG, 0, 0, 0] to indicate the GER is known. -#! -#! Panics if the note sender is not the global exit root manager. +#! Computes hash(GER) = poseidon2::merge(GER_LOWER, GER_UPPER) and stores it in a map with value +#! [GER_KNOWN_FLAG, 0, 0, 0] to indicate the GER is known. #! #! Inputs: [GER_LOWER[4], GER_UPPER[4], pad(8)] #! Outputs: [pad(16)] #! +#! Panics if: +#! - the note sender is not the global exit root manager. +#! #! Invocation: call pub proc update_ger # assert the note sender is the global exit root manager. @@ -65,14 +66,15 @@ pub proc update_ger exec.native_account::set_map_item # => [OLD_VALUE, pad(12)] + dropw # => [pad(16)] end #! Asserts that the provided GER is valid (exists in storage). #! -#! Computes hash(GER) = poseidon2::merge(GER_LOWER, GER_UPPER) and looks up the hash in -#! the GER storage map. Panics if the GER has never been stored. +#! Computes hash(GER) = poseidon2::merge(GER_LOWER, GER_UPPER) and looks up the hash in the GER +#! storage map. Panics if the GER has never been stored. #! #! Inputs: [GER_ROOT[8]] #! Outputs: [] @@ -81,7 +83,7 @@ end #! - the GER is not found in storage. #! #! Invocation: exec -pub proc assert_valid_ger +proc assert_valid_ger # compute hash(GER) exec.poseidon2::merge # => [GER_HASH] @@ -104,14 +106,15 @@ end #! #! 1. Writes `KEY -> [1, 0, 0, 0]` into the `faucet_registry` map, where #! `KEY = [0, 0, faucet_id_suffix, faucet_id_prefix]`. -#! 2. Writes `hash(tokenAddress[5]) -> [faucet_id_suffix, faucet_id_prefix, 0, 0]` -#! into the `token_registry` map. -#! -#! Panics if the note sender is not the bridge admin. +#! 2. Writes `hash(tokenAddress[5]) -> [faucet_id_suffix, faucet_id_prefix, 0, 0]` into the +#! `token_registry` map. #! #! Inputs: [origin_token_addr(5), faucet_id_suffix, faucet_id_prefix, pad(9)] #! Outputs: [pad(16)] #! +#! Panics if: +#! - the note sender is not the bridge admin. +#! #! Invocation: call pub proc register_faucet # assert the note sender is the bridge admin. @@ -120,19 +123,24 @@ pub proc register_faucet # Save faucet ID for later use in token_registry dup.6 dup.6 - # => [faucet_id_suffix, faucet_id_prefix, origin_token_addr(5), faucet_id_suffix, faucet_id_prefix, pad(9)] + # => [faucet_id_suffix, faucet_id_prefix, origin_token_addr(5), + # faucet_id_suffix, faucet_id_prefix, pad(9)] # --- 1. Register faucet in faucet_registry --- + # set_map_item expects [slot_id(2), KEY, VALUE] and returns [OLD_VALUE]. # Build KEY = [0, 0, suffix, prefix] and VALUE = [IS_FAUCET_REGISTERED_FLAG, 0, 0, 0] push.0.0.0.IS_FAUCET_REGISTERED_FLAG - # => [IS_FAUCET_REGISTERED_FLAG, 0, 0, 0, faucet_id_suffix, faucet_id_prefix, origin_token_addr(5), faucet_id_suffix, faucet_id_prefix, pad(9)] + # => [IS_FAUCET_REGISTERED_FLAG, 0, 0, 0, + # faucet_id_suffix, faucet_id_prefix, origin_token_addr(5), + # faucet_id_suffix, faucet_id_prefix, pad(9)] - movup.5 movup.5 - # => [faucet_id_suffix, faucet_id_prefix, IS_FAUCET_REGISTERED_FLAG, 0, 0, 0, origin_token_addr(5), faucet_id_suffix, faucet_id_prefix, pad(9)] - - push.0.0 - # => [[0, 0, faucet_id_suffix, faucet_id_prefix], [IS_FAUCET_REGISTERED_FLAG, 0, 0, 0], origin_token_addr(5), faucet_id_suffix, faucet_id_prefix, pad(9)] + movup.5 movup.5 push.0.0 + # => [ + # [0, 0, faucet_id_suffix, faucet_id_prefix], + # [IS_FAUCET_REGISTERED_FLAG, 0, 0, 0], + # origin_token_addr(5), faucet_id_suffix, faucet_id_prefix, pad(9) + # ] push.FAUCET_REGISTRY_MAP_SLOT[0..2] exec.native_account::set_map_item @@ -148,10 +156,7 @@ pub proc register_faucet # => [TOKEN_ADDR_HASH, faucet_id_suffix, faucet_id_prefix, pad(10)] # Build VALUE = [0, 0, faucet_id_suffix, faucet_id_prefix] - movup.5 movup.5 - # => [faucet_id_suffix, faucet_id_prefix, TOKEN_ADDR_HASH, pad(10)] - - push.0.0 + movup.5 movup.5 push.0.0 # => [0, 0, faucet_id_suffix, faucet_id_prefix, TOKEN_ADDR_HASH, pad(10)] swapw @@ -167,8 +172,7 @@ end #! Asserts that a faucet is registered in the bridge's faucet registry. #! -#! Looks up the faucet ID in the faucet registry map and asserts the registration -#! flag is set. +#! Looks up the faucet ID in the faucet registry map and asserts the registration flag is set. #! #! Inputs: [faucet_id_suffix, faucet_id_prefix] #! Outputs: [] @@ -177,7 +181,7 @@ end #! - the faucet is not registered in the faucet registry. #! #! Invocation: exec -pub proc assert_faucet_registered +proc assert_faucet_registered # Build KEY = [0, 0, faucet_id_suffix, faucet_id_prefix] push.0.0 # => [0, 0, faucet_id_suffix, faucet_id_prefix] @@ -202,7 +206,7 @@ end #! - the token address is not registered in the token registry. #! #! Invocation: exec -pub proc lookup_faucet_by_token_address +proc lookup_faucet_by_token_address # Hash the token address exec.hash_token_address # => [TOKEN_ADDR_HASH] @@ -213,7 +217,7 @@ pub proc lookup_faucet_by_token_address # Assert the token is registered: faucet_id_prefix is always non-zero for valid account IDs. dup.3 dup.3 push.0.0 - # => [0, 0, faucet_id_suffix, faucet_id_prefix, 0, 0, faucet_id_suffix, faucet_id_prefix] + # => [0, 0, faucet_id_suffix, faucet_id_prefix, 0, 0, faucet_id_suffix, faucet_id_prefix] exec.account_id::is_equal # => [is_id_zero, 0, 0, faucet_id_suffix, faucet_id_prefix] @@ -250,8 +254,8 @@ end #! Asserts that the note sender matches the bridge admin stored in account storage. #! -#! Reads the bridge admin account ID from BRIDGE_ADMIN_SLOT and compares it against -#! the sender of the currently executing note. Panics if they do not match. +#! Reads the bridge admin account ID from BRIDGE_ADMIN_SLOT and compares it against the sender of +#! the currently executing note. #! #! Inputs: [pad(16)] #! Outputs: [pad(16)] @@ -260,9 +264,7 @@ end #! - the note sender does not match the bridge admin account ID. #! #! Invocation: exec -pub proc assert_sender_is_bridge_admin - # => [pad(16)] - +proc assert_sender_is_bridge_admin push.BRIDGE_ADMIN_SLOT[0..2] exec.active_account::get_item # => [0, 0, admin_suffix, admin_prefix, pad(16)] @@ -280,8 +282,8 @@ end #! Asserts that the note sender matches the global exit root manager stored in account storage. #! -#! Reads the GER manager account ID from GER_MANAGER_SLOT and compares it against -#! the sender of the currently executing note. Panics if they do not match. +#! Reads the GER manager account ID from GER_MANAGER_SLOT and compares it against the sender of the +#! currently executing note. #! #! Inputs: [pad(16)] #! Outputs: [pad(16)] @@ -290,9 +292,7 @@ end #! - the note sender does not match the GER manager account ID. #! #! Invocation: exec -pub proc assert_sender_is_ger_manager - # => [pad(16)] - +proc assert_sender_is_ger_manager push.GER_MANAGER_SLOT[0..2] exec.active_account::get_item # => [0, 0, mgr_suffix, mgr_prefix, pad(16)] diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_in.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_in.masm index 6ebe17236a..f101c90b40 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_in.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_in.masm @@ -43,7 +43,7 @@ const IS_CLAIMED_FLAG = [1, 0, 0, 0] # ------------------------------------------------------------------------------------------------- # The slot in this component's storage layout where claim nullifiers are stored. -# Map entries: RPO(leaf_index, source_bridge_network) => [1, 0, 0, 0] +# Map entries: Poseidon2(leaf_index, source_bridge_network) => [1, 0, 0, 0] const CLAIM_NULLIFIERS_SLOT = word("agglayer::bridge::claim_nullifiers") # Storage slot constants for the CGI (claimed global index) chain hash. @@ -58,10 +58,16 @@ const CGI_CHAIN_HASH_HI_SLOT_NAME = word("agglayer::bridge::cgi_chain_hash_hi") const CLAIM_PROOF_DATA_WORD_LEN = 134 const CLAIM_LEAF_DATA_WORD_LEN = 8 -# MINT note storage layout (public mode, 18 items): -# [0]: tag, [1]: amount, [2]: attachment_kind, [3]: attachment_scheme, -# [4-7]: ATTACHMENT, [8-11]: P2ID_SCRIPT_ROOT, [12-15]: SERIAL_NUM, -# [16]: account_id_suffix, [17]: account_id_prefix +# MINT note storage layout (public mode, 18 felts total): +# - tag [0] : 1 felt +# - amount [1] : 1 felt +# - attachment_kind [2] : 1 felt +# - attachment_scheme [3] : 1 felt +# - ATTACHMENT [4..7] : 4 felts +# - P2ID_SCRIPT_ROOT [8..11] : 4 felts +# - SERIAL_NUM [12..15] : 4 felts +# - account_id_suffix [16] : 1 felt +# - account_id_prefix [17] : 1 felt const MINT_NOTE_NUM_STORAGE_ITEMS = 18 # P2ID output note constants @@ -153,11 +159,11 @@ const CLAIM_DEST_ID_SUFFIX_LOCAL = 1 # PUBLIC INTERFACE # ================================================================================================= -#! Validates a claim against the AggLayer bridge and creates a MINT note for the aggfaucet. +#! Validates a claim against the AggLayer bridge and creates a MINT note for the AggLayer faucet. #! #! This procedure is called by the CLAIM note script. It validates the Merkle proof and then #! looks up the faucet account ID from the token registry using the origin token address from -#! the leaf data, and creates a MINT note targeting the aggfaucet. +#! the leaf data, and creates a MINT note targeting the AggLayer Faucet. #! #! The MINT note uses the standard MINT note pattern (public mode) with 18 storage items. #! See `write_mint_note_storage` for the full storage layout. @@ -229,7 +235,7 @@ pub proc claim exec.verify_claim_amount # => [faucet_id_suffix, faucet_id_prefix, pad(16)] - # Build MINT output note targeting the aggfaucet + # Build MINT output note targeting the AggLayer faucet loc_load.CLAIM_DEST_ID_PREFIX_LOCAL loc_load.CLAIM_DEST_ID_SUFFIX_LOCAL # => [destination_id_suffix, destination_id_prefix, faucet_id_suffix, faucet_id_prefix, pad(16)] @@ -350,12 +356,13 @@ pub proc process_global_index_rollup repeat.5 assertz.err=ERR_LEADING_BITS_NON_ZERO end + # => [mainnet_flag_le, rollup_index_le, leaf_index_le] # the next element is the mainnet flag (LE-packed u32) # for a rollup deposit it must be exactly 0; zero is byte-order-independent, # so no swap is needed before asserting - # => [mainnet_flag_le, rollup_index_le, leaf_index_le] assertz.err=ERR_BRIDGE_NOT_ROLLUP + # => [rollup_index_le, leaf_index_le] # byte-swap rollup_index from LE to BE exec.utils::swap_u32_bytes @@ -368,8 +375,8 @@ end #! Computes the Global Exit Tree (GET) root from the mainnet and rollup exit roots. #! -#! The mainnet exit root is expected at `exit_roots_ptr` and -#! the rollup exit root is expected at `exit_roots_ptr + 8`. +#! The mainnet exit root is expected at `exit_roots_ptr` and the rollup exit root is expected at +#! `exit_roots_ptr + 8`. #! #! Inputs: [exit_roots_ptr] #! Outputs: [GER_ROOT[8]] @@ -378,6 +385,7 @@ end pub proc compute_ger(exit_roots_ptr: MemoryAddress) -> DoubleWord push.64 swap # => [exit_roots_ptr, len_bytes] + exec.keccak256::hash_bytes # => [GER_ROOT[8]] end @@ -419,14 +427,15 @@ pub proc verify_merkle_proof( movdn.8 exec.word::eq and # => [verification_flag] end -#! Verifies that the faucet_mint_amount matches the raw U256 amount from the leaf data, -#! scaled down by the faucet's scale factor. + +#! Verifies that the faucet_mint_amount matches the raw U256 amount from the leaf data, scaled down +#! by the faucet's scale factor. #! #! This procedure: #! 1. Performs an FPI call to the faucet's `get_scale` procedure to retrieve the scale factor. #! 2. Loads the raw U256 amount from the leaf data in memory. -#! 3. Calls `verify_u256_to_native_amount_conversion` to assert that: -#! faucet_mint_amount == floor(raw_amount / 10^scale) +#! 3. Calls `verify_u256_to_native_amount_conversion` to assert that +#! `faucet_mint_amount == floor(raw_amount / 10^scale)`. #! #! Inputs: [faucet_id_suffix, faucet_id_prefix] #! Outputs: [] @@ -522,16 +531,20 @@ end #! Invocation: exec proc verify_leaf movupw.2 + # => [PROOF_DATA_KEY, LEAF_VALUE[8]] + # load proof data from the advice map into memory adv.push_mapval # => [PROOF_DATA_KEY, LEAF_VALUE[8]] push.SMT_PROOF_LOCAL_EXIT_ROOT_PTR push.CLAIM_PROOF_DATA_WORD_LEN exec.mem::pipe_preimage_to_memory drop + # => [LEAF_VALUE[8]] # 1. compute GER from mainnet + rollup exit roots push.EXIT_ROOTS_PTR # => [exit_roots_ptr, LEAF_VALUE[8]] + exec.compute_ger # => [GER[8], LEAF_VALUE[8]] @@ -548,8 +561,8 @@ proc verify_leaf # [gi0, gi1, gi2, gi3, gi4, mainnet_flag_le, rollup_index_le, leaf_index_le] # gi0 is on top (position 0). The mainnet flag is at stack position 5. - # Duplicate the mainnet flag element, byte-swap from LE to BE, - # assert it is a valid boolean (0 or 1), then use it to branch. + # Duplicate the mainnet flag element, byte-swap from LE to BE, assert it is a valid boolean + # (0 or 1), then use it to branch. dup.5 exec.utils::swap_u32_bytes dup # => [mainnet_flag, mainnet_flag, GLOBAL_INDEX[8], LEAF_VALUE[8]] @@ -558,6 +571,7 @@ proc verify_leaf if.true # ==================== MAINNET DEPOSIT ==================== + exec.process_global_index_mainnet # => [leaf_index, LEAF_VALUE[8]] @@ -585,8 +599,9 @@ proc verify_leaf # => [] else # ==================== ROLLUP DEPOSIT ==================== - # mainnet_flag = 0; extract rollup_index and leaf_index via helper, - # then do two-level verification + + # mainnet_flag = 0; extract rollup_index and leaf_index via helper, then do two-level + # verification exec.process_global_index_rollup # => [leaf_index, rollup_index, LEAF_VALUE[8]] @@ -616,7 +631,6 @@ proc verify_leaf # => [LOCAL_EXIT_ROOT_LO, LOCAL_EXIT_ROOT_HI, rollup_index, rollup_exit_root_ptr] push.SMT_PROOF_ROLLUP_EXIT_ROOT_PTR movdn.8 - # => [LOCAL_EXIT_ROOT[8], smt_proof_rollup_ptr, rollup_index, rollup_exit_root_ptr] exec.verify_merkle_proof @@ -644,8 +658,8 @@ proc verify_leaf # => [] end -#! Computes the claim nullifier as RPO(leaf_index, source_bridge_network), then checks -#! that the claim has not been spent and marks it as spent. +#! Computes the claim nullifier as Poseidon2(leaf_index, source_bridge_network), then checks that +#! the claim has not been spent and marks it as spent. #! #! This mimics the Solidity `_setAndCheckClaimed(leafIndex, sourceBridgeNetwork)` function. #! See: https://github.com/agglayer/agglayer-contracts/blob/60d06fc3224792ce55dc2690d66b6719a73398e7/contracts/v2/PolygonZkEVMBridgeV2.sol#L987 @@ -676,10 +690,10 @@ proc set_and_check_claimed # => [] end -#! Checks that the CLAIM note has not already been spent, and marks it as spent -#! by storing [1, 0, 0, 0] in the CLAIM_NULLIFIERS_SLOT map. +#! Checks that the CLAIM note has not already been spent, and marks it as spent by storing +#! [1, 0, 0, 0] in the CLAIM_NULLIFIERS_SLOT map. #! -#! The nullifier is computed as RPO(leaf_index, source_bridge_network), which uniquely +#! The nullifier is computed as Poseidon2(leaf_index, source_bridge_network), which uniquely #! identifies a claim in the Global Exit Root (GER) as per the AggLayer protocol. #! #! Inputs: [NULLIFIER] @@ -713,10 +727,11 @@ proc claim_batch_pipe_double_words # 1) Verify PROOF_DATA_KEY mem_storew_be.CLAIM_PROOF_DATA_KEY_MEM_ADDR adv.push_mapval - # => [PROOF_DATA_KEY] + # => [PROOF_DATA_KEY, LEAF_DATA_KEY] push.CLAIM_PROOF_DATA_START_PTR push.CLAIM_PROOF_DATA_WORD_LEN exec.mem::pipe_double_words_preimage_to_memory drop + # => [LEAF_DATA_KEY] # 2) Verify LEAF_DATA_KEY mem_storew_be.CLAIM_LEAF_DATA_KEY_MEM_ADDR @@ -725,6 +740,7 @@ proc claim_batch_pipe_double_words push.CLAIM_LEAF_DATA_START_PTR push.CLAIM_LEAF_DATA_WORD_LEN exec.mem::pipe_double_words_preimage_to_memory drop + # => [] end #! Extracts the destination account ID as address[5] from memory. @@ -744,8 +760,12 @@ proc load_destination_address # => [address[5]] end -# Inputs: [] -# Outputs: [U256[0], U256[1]] +#! Loads the claim note asset amount onto the stack from memory. +#! +#! Inputs: [] +#! Outputs: [U256_LO, U256_HI] +#! +#! Invocation: exec proc load_raw_claim_amount mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_7 mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_6 @@ -755,6 +775,7 @@ proc load_raw_claim_amount mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_2 mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_1 mem_load.OUTPUT_NOTE_ASSET_AMOUNT_MEM_ADDR_0 + # => [U256_LO, U256_HI] end #! Reads the origin token address (5 felts) from the leaf data in memory. @@ -772,10 +793,10 @@ proc load_origin_token_address # => [origin_token_addr(5)] end -#! Builds a PUBLIC MINT output note targeting the aggfaucet. +#! Builds a PUBLIC MINT output note targeting the AggLayer Faucet. #! -#! The MINT note uses public mode (18 storage items) so the AggFaucet creates -#! a PUBLIC P2ID note on consumption. This procedure orchestrates three steps: +#! The MINT note uses public mode (18 storage items) so the AggLayer Faucet creates a PUBLIC P2ID +#! note on consumption. This procedure orchestrates three steps: #! 1. Write all 18 MINT note storage items to global memory. #! 2. Build the MINT note recipient digest from the storage. #! 3. Create the output note, and set the attachment. @@ -787,11 +808,11 @@ end proc build_mint_output_note # Step 1: Write all 18 MINT note storage items to global memory exec.write_mint_note_storage - # => [faucet_id_prefix, faucet_id_suffix] + # => [faucet_id_suffix, faucet_id_prefix] # Step 2: Build the MINT note recipient digest exec.build_mint_recipient - # => [MINT_RECIPIENT, faucet_id_prefix, faucet_id_suffix] + # => [MINT_RECIPIENT, faucet_id_suffix, faucet_id_prefix] # Step 3: Create the output note and set the faucet attachment exec.create_mint_note_with_attachment @@ -904,40 +925,38 @@ proc build_mint_recipient # => [MINT_RECIPIENT] end - - #! Creates the MINT output note and sets the NetworkAccountTarget attachment on it. #! -#! Creates a public output note with no assets, and sets the attachment so only the -#! target faucet can consume the note. +#! Creates a public output note with no assets, and sets the attachment so only the target faucet +#! can consume the note. #! -#! Inputs: [MINT_RECIPIENT, faucet_id_prefix, faucet_id_suffix] +#! Inputs: [MINT_RECIPIENT, faucet_id_suffix, faucet_id_prefix] #! Outputs: [] #! #! Invocation: exec proc create_mint_note_with_attachment # Create the MINT output note targeting the faucet push.OUTPUT_NOTE_TYPE_PUBLIC - # => [note_type, MINT_RECIPIENT, faucet_id_prefix, faucet_id_suffix] + # => [note_type, MINT_RECIPIENT, faucet_id_suffix, faucet_id_prefix] # Set tag to DEFAULT push.DEFAULT_TAG - # => [note_type, MINT_RECIPIENT, faucet_id_prefix, faucet_id_suffix] + # => [tag, note_type, MINT_RECIPIENT, faucet_id_suffix, faucet_id_prefix] # Create the output note (no assets - MINT notes carry no assets) exec.output_note::create - # => [note_idx, faucet_id_prefix, faucet_id_suffix] + # => [note_idx, faucet_id_suffix, faucet_id_prefix] movdn.2 - # => [faucet_id_prefix, faucet_id_suffix, note_idx] + # => [faucet_id_suffix, faucet_id_prefix, note_idx] # Set the attachment on the MINT note to target the faucet account # NetworkAccountTarget attachment: targets the faucet so only it can consume the note - # network_account_target::new expects [prefix, suffix, exec_hint] + # network_account_target::new expects [suffix, prefix, exec_hint] # and returns [attachment_scheme, attachment_kind, ATTACHMENT] push.ALWAYS # exec_hint = ALWAYS movdn.2 - # => [faucet_id_prefix, faucet_id_suffix, exec_hint, note_idx] + # => [faucet_id_suffix, faucet_id_prefix, exec_hint, note_idx] exec.network_account_target::new # => [attachment_scheme, attachment_kind, ATTACHMENT, note_idx] @@ -1093,4 +1112,4 @@ proc store_cgi_chain_hash push.CGI_CHAIN_HASH_HI_SLOT_NAME[0..2] exec.native_account::set_item dropw # => [] -end \ No newline at end of file +end diff --git a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm index c94b00a62b..824ad3da19 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm @@ -10,15 +10,13 @@ use miden::standards::note_tag::DEFAULT_TAG use miden::standards::note::execution_hint::ALWAYS use miden::protocol::types::MemoryAddress use miden::protocol::output_note -use miden::core::crypto::hashes::keccak256 use miden::core::crypto::hashes::poseidon2 -use miden::core::word use agglayer::common::utils use agglayer::faucet -> agglayer_faucet use agglayer::bridge::bridge_config use agglayer::bridge::leaf_utils use agglayer::bridge::merkle_tree_frontier -use agglayer::common::utils::EthereumAddressFormat +use agglayer::common::eth_address::EthereumAddressFormat # CONSTANTS # ================================================================================================= @@ -176,7 +174,7 @@ pub proc bridge_out exec.asset::load swapw dropw # => [ASSET_KEY, PROC_MAST_ROOT, pad(16)] - # ASSET layout: [0, 0, faucet_id_suffix, faucet_id_prefix] + # ASSET_KEY layout: [0, 0, faucet_id_suffix, faucet_id_prefix] # Extract faucet ID, drop padding and amount drop drop @@ -188,6 +186,7 @@ pub proc bridge_out push.LEAF_DATA_START_PTR push.METADATA_HASH_OFFSET add movdn.8 # => [METADATA_HASH_LO, METADATA_HASH_HI, metadata_hash_ptr, pad(8)] + exec.utils::mem_store_double_word_unaligned # => [pad(16)] @@ -215,6 +214,7 @@ pub proc bridge_out locaddr.BRIDGE_OUT_BURN_ASSET_LOC exec.asset::load # => [ASSET_KEY, ASSET_VALUE, pad(16)] + exec.create_burn_note # => [pad(16)] end @@ -222,19 +222,19 @@ end # HELPER PROCEDURES # ================================================================================================= -#! Validates that a faucet is registered in the bridge's faucet registry, then performs -#! an FPI call to the faucet's `asset_to_origin_asset` procedure to obtain the scaled -#! amount, origin token address, and origin network. +#! Validates that a faucet is registered in the bridge's faucet registry, then performs an FPI call +#! to the faucet's `asset_to_origin_asset` procedure to obtain the scaled amount, origin token +#! address, and origin network. #! #! Inputs: [ASSET_KEY, ASSET_VALUE] -#! Outputs: [AMOUNT_U256[0](4), AMOUNT_U256[1](4), origin_addr(5), origin_network] +#! Outputs: [AMOUNT_U256_LO, AMOUNT_U256_HI, origin_addr(5), origin_network] #! #! Where: #! - ASSET_KEY is the vault key of the asset to be bridged out. #! - ASSET_VALUE is the value of the asset to be bridged out. -#! - AMOUNT_U256: scaled amount as 8 u32 limbs (little-endian) -#! - origin_addr: origin token address (5 u32 felts) -#! - origin_network: origin network identifier +#! - AMOUNT_U256: scaled amount as 8 u32 limbs (little-endian). +#! - origin_addr: origin token address (5 u32 felts). +#! - origin_network: origin network identifier. #! #! Panics if: #! - The faucet is not registered in the faucet registry. @@ -248,9 +248,8 @@ proc convert_asset padw padw swapdw end # => [ASSET_KEY, ASSET_VALUE, pad(16)] - swapw - exec.asset::fungible_value_into_amount - movdn.4 + + swapw exec.asset::fungible_value_into_amount movdn.4 # => [ASSET_KEY, amount, pad(16)] exec.asset::key_into_faucet_id @@ -270,13 +269,13 @@ proc convert_asset # => [faucet_id_suffix, faucet_id_prefix, PROC_MAST_ROOT, amount, pad(15), pad(1)] exec.tx::execute_foreign_procedure - # => [AMOUNT_U256[0](4), AMOUNT_U256[1](4), origin_addr(5), origin_network, pad(2), pad(1)] + # => [AMOUNT_U256_LO, AMOUNT_U256_HI, origin_addr(5), origin_network, pad(2), pad(1)] # drop the 3 trailing padding elements repeat.3 movup.14 drop end - # => [AMOUNT_U256[0](4), AMOUNT_U256[1](4), origin_addr(5), origin_network] + # => [AMOUNT_U256_LO, AMOUNT_U256_HI, origin_addr(5), origin_network] end #! Computes the leaf value from the leaf data in memory and appends it to the Local Exit Tree. @@ -324,8 +323,8 @@ end #! Loads the LET (Local Exit Tree) frontier from account storage into memory. #! -#! The num_leaves is read from its dedicated value slot, and the 32 frontier entries are read -#! from the LET map slot (double-word array, indices 0..31). The data is placed into memory at +#! The num_leaves is read from its dedicated value slot, and the 32 frontier entries are read from +#! the LET map slot (double-word array, indices 0..31). The data is placed into memory at #! LET_FRONTIER_MEM_PTR, matching the layout expected by append_and_update_frontier: #! [num_leaves, 0, 0, 0, [[FRONTIER_NODE_LO, FRONTIER_NODE_HI]; 32]] #! @@ -394,6 +393,7 @@ proc save_let_root_and_num_leaves # 3. Save new_leaf_count to its value slot as [new_leaf_count, 0, 0, 0] push.0.0.0 movup.3 # => [new_leaf_count, 0, 0, 0] + push.LET_NUM_LEAVES_SLOT[0..2] exec.native_account::set_item dropw @@ -455,6 +455,7 @@ proc write_address_to_memory(mem_ptr: MemoryAddress, address: EthereumAddressFor # => [mem_ptr+4, address(1)] mem_store + # => [] end #! Computes the SERIAL_NUM of the outputted BURN note. @@ -539,8 +540,7 @@ proc create_burn_note # => [note_idx, pad(15)] # duplicate note_idx: one for set_attachment, one for add_asset - dup - swapw loc_loadw_le.ATTACHMENT_LOC + dup swapw loc_loadw_le.ATTACHMENT_LOC # => [NOTE_ATTACHMENT, note_idx, note_idx, pad(11)] loc_load.ATTACHMENT_KIND_LOC diff --git a/crates/miden-agglayer/asm/agglayer/bridge/leaf_utils.masm b/crates/miden-agglayer/asm/agglayer/bridge/leaf_utils.masm index f9d697c66d..2ef1cbf4a2 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/leaf_utils.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/leaf_utils.masm @@ -20,13 +20,14 @@ const PACKED_DATA_NUM_ELEMENTS = 29 #! Given a memory address where the unpacked leaf data starts, packs the leaf data in-place, and #! computes the leaf value by hashing the packed bytes. #! -#! Inputs: [LEAF_DATA_START_PTR] +#! Inputs: [leaf_data_start_ptr] #! Outputs: [LEAF_VALUE[8]] #! #! Invocation: exec pub proc compute_leaf_value(leaf_data_start_ptr: MemoryAddress) -> DoubleWord dup # => [leaf_data_start_ptr, leaf_data_start_ptr] + exec.pack_leaf_data # => [leaf_data_start_ptr] @@ -39,17 +40,17 @@ end #! Packs the raw leaf data by shifting left 3 bytes to match Solidity's abi.encodePacked format. #! -#! The raw data has leafType occupying 4 bytes (as a u32 felt) but Solidity's abi.encodePacked -#! only uses 1 byte for uint8 leafType. This procedure shifts all data left by 3 bytes so that: +#! The raw data has leafType occupying 4 bytes (as a u32 felt) but Solidity's abi.encodePacked only +#! uses 1 byte for uint8 leafType. This procedure shifts all data left by 3 bytes so that: #! - Byte 0: leafType (1 byte) #! - Bytes 1-4: originNetwork (4 bytes) #! - etc. #! #! The Keccak precompile expects u32 values packed in little-endian byte order. -#! For each packed element, we drop the leading 3 bytes and rebuild the u32 so that -#! bytes [b0, b1, b2, b3] map to u32::from_le_bytes([b0, b1, b2, b3]). -#! With little-endian input limbs, the first byte comes from the MSB of `curr` and -#! the next three bytes come from the LSBs of `next`: +#! For each packed element, we drop the leading 3 bytes and rebuild the u32 so that bytes +#! [b0, b1, b2, b3] map to u32::from_le_bytes([b0, b1, b2, b3]). +#! With little-endian input limbs, the first byte comes from the MSB of `curr` and the next three +#! bytes come from the LSBs of `next`: #! packed = ((curr >> 24) & 0xFF) #! | (next & 0xFF) << 8 #! | ((next >> 8) & 0xFF) << 16 @@ -57,6 +58,7 @@ end #! #! To help visualize the packing process, consider that each field element represents a 4-byte #! value [u8; 4] (LE). +#! #! Memory before is: #! ptr+0: 1 felt: [a, b, c, d] #! ptr+1: 1 felt: [e, f, g, h] @@ -100,11 +102,11 @@ pub proc pack_leaf_data(leaf_data_start_ptr: MemoryAddress) # compute source address for next element (counter + 1) dup.2 loc_load.PACKING_START_PTR_LOCAL add add.1 - # => [next_src_addr, curr_lsb, curr_elem, counter] + # => [next_src_addr, curr_msb, curr_elem, counter] # load next element mem_load - # => [next_elem, curr_lsb, curr_elem, counter] + # => [next_elem, curr_msb, curr_elem, counter] # keep curr_msb on top for combination swap diff --git a/crates/miden-agglayer/asm/agglayer/bridge/merkle_tree_frontier.masm b/crates/miden-agglayer/asm/agglayer/bridge/merkle_tree_frontier.masm index ed613d1bf9..26add7c242 100644 --- a/crates/miden-agglayer/asm/agglayer/bridge/merkle_tree_frontier.masm +++ b/crates/miden-agglayer/asm/agglayer/bridge/merkle_tree_frontier.masm @@ -306,27 +306,3 @@ pub proc append_and_update_frontier padw loc_loadw_le.CUR_HASH_LO_LOCAL # => [NEW_ROOT_LO, NEW_ROOT_HI, new_leaf_count] end - -# HELPER PROCEDURES -# ================================================================================================= - -#! Stores the canonical zeros from the advice map to the memory at the provided address. -#! -#! Inputs: [zeros_ptr] -#! Outputs: [] -proc store_canonical_zeros - # prepare the stack for the adv_pipe instruction - padw padw padw - # => [PAD, PAD, PAD, zeros_ptr] - - # TODO: use constant once constant usage will be implemented - repeat.32 - adv_pipe - # => [ZERO_I_L, ZERO_I_R, PAD, zeros_ptr+8] - end - # => [ZERO_31_L, ZERO_31_R, PAD, zeros_ptr+256] - - # clean the stack - dropw dropw dropw drop - # => [] -end diff --git a/crates/miden-agglayer/asm/agglayer/common/asset_conversion.masm b/crates/miden-agglayer/asm/agglayer/common/asset_conversion.masm index 298d85eccc..832f1a770b 100644 --- a/crates/miden-agglayer/asm/agglayer/common/asset_conversion.masm +++ b/crates/miden-agglayer/asm/agglayer/common/asset_conversion.masm @@ -72,12 +72,12 @@ end #! returns the result as 8 u32 limbs in little-endian order (U256 format). #! #! Inputs: [amount, target_scale] -#! Outputs: [[RESULT_U256[0], RESULT_U256[1]]] +#! Outputs: [[RESULT_U256_LO, RESULT_U256_HI]] #! #! Where: #! - amount: The asset amount to be converted (range: 0 to 2^63 - 2^31) #! - target_scale: Exponent for scaling factor (10^target_scale) -#! - [RESULT_U256[0], RESULT_U256[1]]: U256 value as 8 u32 limbs in little-endian order +#! - [RESULT_U256_LO, RESULT_U256_HI]: U256 value as 8 u32 limbs in little-endian order #! (least significant limb at the top of the stack, each limb stored in little-endian format) #! #! Examples: @@ -106,7 +106,7 @@ pub proc scale_native_amount_to_u256 # convert to U256 & little endian padw swapw - # => [RESULT_U256[0], RESULT_U256[1]] + # => [RESULT_U256_LO, RESULT_U256_HI] end #! Reverse the limbs and change the byte endianness of the result. @@ -123,7 +123,7 @@ pub proc reverse_limbs_and_change_byte_endianness movdn.7 end - # => [RESULT_U256[0], RESULT_U256[1]] + # => [RESULT_U256_LO, RESULT_U256_HI] end #! Subtract two 128-bit integers (little-endian u32 limbs) and assert no underflow. diff --git a/crates/miden-agglayer/asm/agglayer/common/eth_address.masm b/crates/miden-agglayer/asm/agglayer/common/eth_address.masm index 5a038f07fe..b9cc4a9c33 100644 --- a/crates/miden-agglayer/asm/agglayer/common/eth_address.masm +++ b/crates/miden-agglayer/asm/agglayer/common/eth_address.masm @@ -2,6 +2,11 @@ use agglayer::common::utils use miden::core::crypto::hashes::keccak256 use miden::core::word +# TYPE ALIASES +# ================================================================================================= + +pub type EthereumAddressFormat = struct { a: felt, b: felt, c: felt, d: felt, e: felt } + # ERRORS # ================================================================================================= diff --git a/crates/miden-agglayer/asm/agglayer/common/utils.masm b/crates/miden-agglayer/asm/agglayer/common/utils.masm index ac4e5fbbae..45bed28405 100644 --- a/crates/miden-agglayer/asm/agglayer/common/utils.masm +++ b/crates/miden-agglayer/asm/agglayer/common/utils.masm @@ -3,11 +3,6 @@ use miden::protocol::types::DoubleWord use miden::protocol::types::MemoryAddress -# TYPE ALIASES -# ================================================================================================= - -pub type EthereumAddressFormat = struct { a: felt, b: felt, c: felt, d: felt, e: felt } - # BYTE MANIPULATION # ================================================================================================= @@ -15,7 +10,7 @@ pub type EthereumAddressFormat = struct { a: felt, b: felt, c: felt, d: felt, e: #! #! Inputs: [value] #! Outputs: [swapped] -pub proc swap_u32_bytes +proc swap_u32_bytes # part0 = (value & 0xFF) << 24 dup u32and.0xFF u32shl.24 # => [part0, value] @@ -43,7 +38,9 @@ end #! #! Inputs: [WORD_1, WORD_2, ptr] #! Outputs: [WORD_1, WORD_2, ptr] -pub proc mem_store_double_word( +#! +#! Total cycles: 28 +proc mem_store_double_word( double_word_to_store: DoubleWord, mem_ptr: MemoryAddress ) -> (DoubleWord, MemoryAddress) @@ -58,7 +55,7 @@ end #! #! Inputs: [WORD_1, WORD_2, ptr] #! Outputs: [] -pub proc mem_store_double_word_unaligned( +proc mem_store_double_word_unaligned( double_word_to_store: DoubleWord, mem_ptr: MemoryAddress ) @@ -86,7 +83,7 @@ end #! #! Inputs: [ptr] #! Outputs: [WORD_1, WORD_2] -pub proc mem_load_double_word(mem_ptr: MemoryAddress) -> DoubleWord +proc mem_load_double_word(mem_ptr: MemoryAddress) -> DoubleWord padw dup.4 add.4 mem_loadw_le # => [WORD_2, ptr] diff --git a/crates/miden-agglayer/asm/agglayer/faucet/mod.masm b/crates/miden-agglayer/asm/agglayer/faucet/mod.masm index 0b9c00db78..408c0e93e1 100644 --- a/crates/miden-agglayer/asm/agglayer/faucet/mod.masm +++ b/crates/miden-agglayer/asm/agglayer/faucet/mod.masm @@ -1,11 +1,7 @@ use miden::core::sys use agglayer::common::utils use agglayer::common::asset_conversion -use agglayer::common::eth_address use miden::protocol::active_account -use miden::protocol::active_note -use miden::standards::faucets -use miden::standards::faucets::network_fungible # CONSTANTS # ================================================================================================= @@ -26,8 +22,7 @@ const METADATA_HASH_HI_SLOT = word("agglayer::faucet::metadata_hash_hi") #! Returns the origin token address (5 felts) from faucet conversion storage. #! -#! Reads conversion_info_1 (first 4 felts of address) and conversion_info_2 (5th felt) -#! from storage. +#! Reads conversion_info_1 (first 4 felts of address) and conversion_info_2 (5th felt) from storage. #! #! Inputs: [] #! Outputs: [addr0, addr1, addr2, addr3, addr4] @@ -80,8 +75,8 @@ end #! Returns the pre-computed metadata hash (8 u32 felts) from faucet storage. #! -#! The metadata hash is `keccak256(abi.encode(name, symbol, decimals))` and is stored -#! across two value slots (lo and hi, 4 felts each). +#! The metadata hash is `keccak256(abi.encode(name, symbol, decimals))` and is stored across two +#! value slots (lo and hi, 4 felts each). #! #! Inputs: [pad(16)] #! Outputs: [METADATA_HASH_LO(4), METADATA_HASH_HI(4), pad(8)] @@ -121,15 +116,15 @@ pub proc get_scale # => [scale, pad(15)] end -#! Converts a native Miden asset amount to origin asset data using the stored -#! conversion metadata (origin_token_address, origin_network, and scale). +#! Converts a native Miden asset amount to origin asset data using the stored conversion metadata +#! (origin_token_address, origin_network, and scale). #! #! This procedure is intended to be called via FPI from the bridge account. -#! It reads the faucet's conversion metadata from storage, scales the native amount -#! to U256 format, and returns the result along with origin token address and network. +#! It reads the faucet's conversion metadata from storage, scales the native amount to U256 format, +#! and returns the result along with origin token address and network. #! #! Inputs: [amount, pad(15)] -#! Outputs: [AMOUNT_U256[0], AMOUNT_U256[1], addr0, addr1, addr2, addr3, addr4, origin_network, pad(2)] +#! Outputs: [AMOUNT_U256_LO, AMOUNT_U256_HI, addr0, addr1, addr2, addr3, addr4, origin_network, pad(2)] #! #! Where: #! - amount: The native Miden asset amount @@ -142,9 +137,7 @@ pub proc asset_to_origin_asset # => [amount, pad(15)] # Step 1: Get scale from storage - exec.get_scale_inner - # => [scale, amount, pad(15)] - swap + exec.get_scale_inner swap # => [amount, scale, pad(15)] # Step 2: Scale amount to U256 @@ -170,6 +163,7 @@ pub proc asset_to_origin_asset # => [U256_LO, U256_HI, addr0, addr1, addr2, addr3, addr4, origin_network, pad(15)] exec.sys::truncate_stack + # => [U256_LO, U256_HI, addr0, addr1, addr2, addr3, addr4, origin_network, pad(2)] end #! Burns the fungible asset from the active note. @@ -190,7 +184,7 @@ end #! Invocation: call pub use ::miden::standards::faucets::basic_fungible::burn -#! Re-export the network fungible faucet's mint_and_send procedure. +#! Re-export the network fungible faucet's `mint_and_send` procedure. #! #! See `miden::standards::faucets::network_fungible::mint_and_send` for more details. #! diff --git a/crates/miden-agglayer/asm/components/bridge.masm b/crates/miden-agglayer/asm/components/bridge.masm index 15f10fd4b6..4c38d5a019 100644 --- a/crates/miden-agglayer/asm/components/bridge.masm +++ b/crates/miden-agglayer/asm/components/bridge.masm @@ -1,7 +1,12 @@ # The MASM code of the AggLayer Bridge Account Component. # -# This is a thin wrapper that re-exports bridge-related procedures from the -# agglayer library. +# This is a thin wrapper that re-exports bridge-related procedures from the agglayer library. +# +# The bridge exposes: +# - `register_faucet` from the bridge_config module +# - `update_ger` from the bridge_config module +# - `claim` for bridge-in +# - `bridge_out` for bridge-out pub use ::agglayer::bridge::bridge_config::register_faucet pub use ::agglayer::bridge::bridge_config::update_ger diff --git a/crates/miden-agglayer/asm/components/faucet.masm b/crates/miden-agglayer/asm/components/faucet.masm index ffa33f399e..71927c63d9 100644 --- a/crates/miden-agglayer/asm/components/faucet.masm +++ b/crates/miden-agglayer/asm/components/faucet.masm @@ -1,8 +1,10 @@ # The MASM code of the AggLayer Faucet Account Component. # -# This is a thin wrapper that re-exports faucet-related procedures from the -# agglayer library. The faucet exposes: -# - `mint_and_send` from the network fungible faucet (for MINT note consumption, with owner verification) +# This is a thin wrapper that re-exports faucet-related procedures from the agglayer library. +# +# The faucet exposes: +# - `mint_and_send` from the network fungible faucet (for MINT note consumption, with owner +# verification) # - `asset_to_origin_asset` for bridge-out FPI # - `get_metadata_hash` for bridge-out FPI (metadata hash retrieval) # - `get_scale` for bridge-in FPI (amount verification) diff --git a/crates/miden-agglayer/asm/note_scripts/B2AGG.masm b/crates/miden-agglayer/asm/note_scripts/B2AGG.masm index bc5ee0931b..0ae42e52e8 100644 --- a/crates/miden-agglayer/asm/note_scripts/B2AGG.masm +++ b/crates/miden-agglayer/asm/note_scripts/B2AGG.masm @@ -3,19 +3,17 @@ use miden::protocol::account_id use miden::protocol::active_account use miden::protocol::active_note use miden::protocol::asset -use miden::protocol::asset::ASSET_VALUE_MEMORY_OFFSET -use miden::protocol::note use miden::standards::attachments::network_account_target use miden::standards::wallets::basic->basic_wallet # CONSTANTS # ================================================================================================= -const ASSET_PTR=0 -const B2AGG_NOTE_NUM_STORAGE_ITEMS=6 +const B2AGG_NOTE_ASSETS_PTR = 0 +const B2AGG_NOTE_NUM_ASSETS = 1 -const STORAGE_START_PTR=8 -const STORAGE_END_PTR=STORAGE_START_PTR + 8 +const B2AGG_NOTE_STORAGE_PTR = 8 +const B2AGG_NOTE_NUM_STORAGE_ITEMS = 6 # ERRORS # ================================================================================================= @@ -29,7 +27,8 @@ const ERR_B2AGG_TARGET_ACCOUNT_MISMATCH="B2AGG note attachment target account do #! Bridge-to-AggLayer (B2AGG) note script: bridges assets from Miden to an AggLayer-connected chain. #! #! This note can be consumed in two ways: -#! - If the consuming account is the sender (reclaim): the note's assets are added back to the consuming account. +#! - If the consuming account is the sender (reclaim case): the note's assets are added back to the +#! consuming account. #! - If the consuming account is the Agglayer Bridge: the note's assets are moved to a BURN note, #! and the note details are hashed into a leaf and appended to the Local Exit Tree. #! @@ -37,8 +36,8 @@ const ERR_B2AGG_TARGET_ACCOUNT_MISMATCH="B2AGG note attachment target account do #! Outputs: [] #! #! Note storage layout (6 felts total): -#! - destination_network [0] : 1 felt -#! - destination_address [1..5] : 5 felts +#! - destination_network [0] : 1 felt +#! - destination_address [1..5] : 5 felts #! #! Where: #! - destination_network: Destination network identifier (uint32) @@ -51,7 +50,6 @@ const ERR_B2AGG_TARGET_ACCOUNT_MISMATCH="B2AGG note attachment target account do #! - 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 # => [pad(16)] @@ -77,26 +75,27 @@ begin # => [pad(16)] # Store note storage -> mem[8..14] - push.STORAGE_START_PTR exec.active_note::get_storage - # => [num_storage_items, dest_ptr, pad(16)] + push.B2AGG_NOTE_STORAGE_PTR exec.active_note::get_storage + # => [num_storage_items, storage_ptr, pad(16)] - push.B2AGG_NOTE_NUM_STORAGE_ITEMS assert_eq.err=ERR_B2AGG_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS drop - # => [pad(16)] + # Validate the number of storage items + push.B2AGG_NOTE_NUM_STORAGE_ITEMS assert_eq.err=ERR_B2AGG_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS + # => [storage_ptr, pad(16)] + + # load the 6 B2AGG felts from B2AGG_NOTE_STORAGE_PTR as two words + add.4 mem_loadw_le swapw mem_loadw_le.B2AGG_NOTE_STORAGE_PTR + # => [dest_network, dest_address(5), pad(10)] # Store note assets -> mem[0..8] - push.ASSET_PTR exec.active_note::get_assets - # => [num_assets, ptr, pad(16)] + push.B2AGG_NOTE_ASSETS_PTR exec.active_note::get_assets + # => [num_assets, assets_ptr, dest_network, dest_address(5), pad(10)] # Must be exactly 1 asset - push.1 assert_eq.err=ERR_B2AGG_WRONG_NUMBER_OF_ASSETS drop - # => [pad(16)] - - # load the 6 B2AGG felts from note storage as two words - push.STORAGE_START_PTR add.4 mem_loadw_le swapw mem_loadw_le.STORAGE_START_PTR - # => [dest_network, dest_address(5), pad(10)] + push.B2AGG_NOTE_NUM_ASSETS assert_eq.err=ERR_B2AGG_WRONG_NUMBER_OF_ASSETS + # => [assets_ptr, dest_network, dest_address(5), pad(10)] - # Load asset onto the stack from ASSET_PTR - push.ASSET_PTR exec.asset::load + # Load asset onto the stack from B2AGG_NOTE_ASSETS_PTR + exec.asset::load # => [ASSET_KEY, ASSET_VALUE, dest_network, dest_address(5), pad(10)] call.bridge_out::bridge_out diff --git a/crates/miden-agglayer/asm/note_scripts/CLAIM.masm b/crates/miden-agglayer/asm/note_scripts/CLAIM.masm index 036b34298e..715ab6d7f9 100644 --- a/crates/miden-agglayer/asm/note_scripts/CLAIM.masm +++ b/crates/miden-agglayer/asm/note_scripts/CLAIM.masm @@ -1,9 +1,6 @@ use agglayer::bridge::bridge_in -> bridge use miden::protocol::active_note -use miden::protocol::note -use miden::core::crypto::hashes::keccak256 use miden::core::crypto::hashes::poseidon2 -use miden::core::mem use miden::standards::attachments::network_account_target # CONSTANTS @@ -13,7 +10,8 @@ const PROOF_DATA_SIZE = 536 const LEAF_DATA_SIZE = 32 const OUTPUT_NOTE_SIZE = 8 -const PROOF_DATA_START_PTR = 0 +const CLAIM_NOTE_STORAGE_PTR = 0 +const PROOF_DATA_START_PTR = CLAIM_NOTE_STORAGE_PTR const LEAF_DATA_START_PTR = 536 const FAUCET_MINT_AMOUNT = 568 @@ -22,74 +20,15 @@ const FAUCET_MINT_AMOUNT = 568 const ERR_CLAIM_TARGET_ACCT_MISMATCH = "CLAIM note attachment target account does not match consuming account" -#! Reads claim data from memory and inserts it into the advice map under two separate keys. -#! -#! This procedure organizes the claim note data into two logical groups and inserts them -#! into the advice map under separate keys for easier access. -#! -#! Inputs: [] -#! Outputs: [PROOF_DATA_KEY, LEAF_DATA_KEY] -#! -#! Advice map entries created: -#! PROOF_DATA_KEY => [ -#! smtProofLocalExitRoot[256], // SMT proof for local exit root (256 felts, bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH]) -#! smtProofRollupExitRoot[256], // SMT proof for rollup exit root (256 felts, bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH]) -#! globalIndex[8], // Global index (8 felts, uint256 as 8 u32 felts) -#! mainnetExitRoot[8], // Mainnet exit root hash (8 felts, bytes32 as 8 u32 felts) -#! rollupExitRoot[8], // Rollup exit root hash (8 felts, bytes32 as 8 u32 felts) -#! ] -#! -#! LEAF_DATA_KEY => [ -#! leafType[1], // Leaf type (1 felt, uint32) -#! originNetwork[1], // Origin network identifier (1 felt, uint32) -#! originTokenAddress[5], // Origin token address (5 felts, address as 5 u32 felts) -#! destinationNetwork[1], // Destination network identifier (1 felt, uint32) -#! destinationAddress[5], // Destination address (5 felts, address as 5 u32 felts) -#! amount[8], // Amount of tokens (8 felts, uint256 as 8 u32 felts) -#! metadata[8], // ABI encoded metadata (8 felts, fixed size) -#! padding[3], // padding (3 felts) -#! ] -#! -#! Invocation: exec -proc write_claim_data_into_advice_map_by_key - # 1) Get LEAF_DATA_KEY - push.LEAF_DATA_SIZE push.LEAF_DATA_START_PTR - exec.poseidon2::hash_elements - # => [LEAF_DATA_KEY] - - push.LEAF_DATA_SIZE add.LEAF_DATA_START_PTR push.LEAF_DATA_START_PTR - movdn.5 movdn.5 - # => [LEAF_DATA_KEY, start_ptr, end_ptr] - - adv.insert_mem - # OS => [LEAF_DATA_KEY, start_ptr, end_ptr] - # AM => {LEAF_DATA_KEY: mem[start_ptr..end_ptr] } - movup.4 drop movup.4 drop - # => [LEAF_DATA_KEY] - - # 2) Get PROOF_DATA_KEY - push.PROOF_DATA_SIZE push.PROOF_DATA_START_PTR - exec.poseidon2::hash_elements - # => [PROOF_DATA_KEY, LEAF_DATA_KEY] - - push.PROOF_DATA_SIZE push.PROOF_DATA_START_PTR - movdn.5 movdn.5 - # => [PROOF_DATA_KEY, start_ptr, end_ptr, LEAF_DATA_KEY] - - adv.insert_mem - # OS => [PROOF_DATA_KEY, start_ptr, end_ptr, LEAF_DATA_KEY] - # AM => {PROOF_DATA_KEY: mem[start_ptr..end_ptr] } - - movup.4 drop movup.4 drop - # => [PROOF_DATA_KEY, LEAF_DATA_KEY] -end +# NOTE SCRIPT +# ================================================================================================= #! Agglayer Bridge CLAIM script: claims assets by calling the bridge's claim function. #! #! This note is consumed by the agglayer bridge account whose ID is provided #! in the note attachment (NetworkAccountTarget). Upon consumption, the bridge validates #! the Merkle proof, looks up the faucet from the token registry, and creates a MINT note -#! targeting the aggfaucet. +#! targeting the Agglayer Faucet. #! #! Requires that the account exposes: #! - agglayer::bridge::bridge_in::claim procedure. @@ -98,20 +37,20 @@ end #! Outputs: [pad(16)] #! #! NoteStorage layout (569 felts total): -#! - smtProofLocalExitRoot [0..255] : 256 felts -#! - smtProofRollupExitRoot [256..511]: 256 felts -#! - globalIndex [512..519]: 8 felts -#! - mainnetExitRoot [520..527]: 8 felts -#! - rollupExitRoot [528..535]: 8 felts -#! - leafType [536] : 1 felt -#! - originNetwork [537] : 1 felt -#! - originTokenAddress [538..542]: 5 felts -#! - destinationNetwork [543] : 1 felt -#! - destinationAddress [544..548]: 5 felts -#! - amount [549..556]: 8 felts -#! - metadata [557..564]: 8 felts -#! - padding [565..567]: 3 felts -#! - miden_claim_amount [568] : 1 felt +#! - smtProofLocalExitRoot [0..255] : 256 felts +#! - smtProofRollupExitRoot [256..511] : 256 felts +#! - globalIndex [512..519] : 8 felts +#! - mainnetExitRoot [520..527] : 8 felts +#! - rollupExitRoot [528..535] : 8 felts +#! - leafType [536] : 1 felt +#! - originNetwork [537] : 1 felt +#! - originTokenAddress [538..542] : 5 felts +#! - destinationNetwork [543] : 1 felt +#! - destinationAddress [544..548] : 5 felts +#! - amount [549..556] : 8 felts +#! - metadata [557..564] : 8 felts +#! - padding [565..567] : 3 felts +#! - miden_claim_amount [568] : 1 felt #! #! Where: #! - smtProofLocalExitRoot: SMT proof for local exit root (bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH]) @@ -146,7 +85,7 @@ begin # => [pad(16)] # Load CLAIM note storage into memory, starting at address 0 - push.0 exec.active_note::get_storage drop drop + push.CLAIM_NOTE_STORAGE_PTR exec.active_note::get_storage drop drop # => [pad(16)] exec.write_claim_data_into_advice_map_by_key @@ -167,3 +106,68 @@ begin # => [pad(16)] end + +# HELPER PROCEDURES +# ================================================================================================= + +#! Reads claim data from memory and inserts it into the advice map under two separate keys. +#! +#! This procedure organizes the claim note data into two logical groups and inserts them +#! into the advice map under separate keys for easier access. +#! +#! Inputs: [] +#! Outputs: [PROOF_DATA_KEY, LEAF_DATA_KEY] +#! +#! Advice map entries created: +#! PROOF_DATA_KEY => [ +#! smtProofLocalExitRoot[256], // SMT proof for local exit root (256 felts, bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH]) +#! smtProofRollupExitRoot[256], // SMT proof for rollup exit root (256 felts, bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH]) +#! globalIndex[8], // Global index (8 felts, uint256 as 8 u32 felts) +#! mainnetExitRoot[8], // Mainnet exit root hash (8 felts, bytes32 as 8 u32 felts) +#! rollupExitRoot[8], // Rollup exit root hash (8 felts, bytes32 as 8 u32 felts) +#! ] +#! +#! LEAF_DATA_KEY => [ +#! leafType[1], // Leaf type (1 felt, uint32) +#! originNetwork[1], // Origin network identifier (1 felt, uint32) +#! originTokenAddress[5], // Origin token address (5 felts, address as 5 u32 felts) +#! destinationNetwork[1], // Destination network identifier (1 felt, uint32) +#! destinationAddress[5], // Destination address (5 felts, address as 5 u32 felts) +#! amount[8], // Amount of tokens (8 felts, uint256 as 8 u32 felts) +#! metadata[8], // ABI encoded metadata (8 felts, fixed size) +#! padding[3], // padding (3 felts) +#! ] +#! +#! Invocation: exec +proc write_claim_data_into_advice_map_by_key + # 1) Get LEAF_DATA_KEY + push.LEAF_DATA_SIZE push.LEAF_DATA_START_PTR + exec.poseidon2::hash_elements + # => [LEAF_DATA_KEY] + + push.LEAF_DATA_SIZE add.LEAF_DATA_START_PTR push.LEAF_DATA_START_PTR + movdn.5 movdn.5 + # => [LEAF_DATA_KEY, start_ptr, end_ptr] + + adv.insert_mem + # OS => [LEAF_DATA_KEY, start_ptr, end_ptr] + # AM => {LEAF_DATA_KEY: mem[start_ptr..end_ptr] } + movup.4 drop movup.4 drop + # => [LEAF_DATA_KEY] + + # 2) Get PROOF_DATA_KEY + push.PROOF_DATA_SIZE push.PROOF_DATA_START_PTR + exec.poseidon2::hash_elements + # => [PROOF_DATA_KEY, LEAF_DATA_KEY] + + push.PROOF_DATA_SIZE push.PROOF_DATA_START_PTR + movdn.5 movdn.5 + # => [PROOF_DATA_KEY, start_ptr, end_ptr, LEAF_DATA_KEY] + + adv.insert_mem + # OS => [PROOF_DATA_KEY, start_ptr, end_ptr, LEAF_DATA_KEY] + # AM => {PROOF_DATA_KEY: mem[start_ptr..end_ptr] } + + movup.4 drop movup.4 drop + # => [PROOF_DATA_KEY, LEAF_DATA_KEY] +end diff --git a/crates/miden-agglayer/asm/note_scripts/CONFIG_AGG_BRIDGE.masm b/crates/miden-agglayer/asm/note_scripts/CONFIG_AGG_BRIDGE.masm index 12259b36f7..98df2690fd 100644 --- a/crates/miden-agglayer/asm/note_scripts/CONFIG_AGG_BRIDGE.masm +++ b/crates/miden-agglayer/asm/note_scripts/CONFIG_AGG_BRIDGE.masm @@ -1,17 +1,14 @@ use agglayer::bridge::bridge_config use miden::protocol::active_note -use miden::protocol::active_account -use miden::protocol::account_id use miden::standards::attachments::network_account_target # CONSTANTS # ================================================================================================= -const STORAGE_START_PTR = 0 const CONFIG_AGG_BRIDGE_NUM_STORAGE_ITEMS = 7 -const FAUCET_ID_SUFFIX = 5 -const FAUCET_ID_PREFIX = 6 +const STORAGE_START_PTR = 0 +const ORIGIN_TOKEN_ADDR_0 = STORAGE_START_PTR const ORIGIN_TOKEN_ADDR_4 = 4 # ERRORS @@ -20,7 +17,8 @@ const ORIGIN_TOKEN_ADDR_4 = 4 const ERR_CONFIG_AGG_BRIDGE_UNEXPECTED_STORAGE_ITEMS = "CONFIG_AGG_BRIDGE expects exactly 7 note storage items" const ERR_CONFIG_AGG_BRIDGE_TARGET_ACCOUNT_MISMATCH = "CONFIG_AGG_BRIDGE note attachment target account does not match consuming account" -#! Registers a faucet in the bridge's faucet registry and token registry. +#! Agglayer Bridge CONFIG_AGG_BRIDGE script: registers a faucet in the bridge's faucet registry and +#! token registry. #! #! This note can only be consumed by the Agglayer Bridge account that is targeted by the note #! attachment, and only if the note was sent by the bridge admin. @@ -33,14 +31,13 @@ const ERR_CONFIG_AGG_BRIDGE_TARGET_ACCOUNT_MISMATCH = "CONFIG_AGG_BRIDGE note at #! Outputs: [pad(16)] #! #! NoteStorage layout (7 felts total): -#! - origin_token_addr_0 [0]: 1 felt -#! - origin_token_addr_1 [1]: 1 felt -#! - origin_token_addr_2 [2]: 1 felt -#! - origin_token_addr_3 [3]: 1 felt -#! - origin_token_addr_4 [4]: 1 felt -#! - faucet_id_suffix [5]: 1 felt -#! - faucet_id_prefix [6]: 1 felt - +#! - origin_token_addr_0 [0] : 1 felt +#! - origin_token_addr_1 [1] : 1 felt +#! - origin_token_addr_2 [2] : 1 felt +#! - origin_token_addr_3 [3] : 1 felt +#! - origin_token_addr_4 [4] : 1 felt +#! - faucet_id_suffix [5] : 1 felt +#! - faucet_id_prefix [6] : 1 felt #! #! Where: #! - faucet_id_suffix: Suffix felt of the faucet account ID to register. @@ -63,24 +60,30 @@ begin push.STORAGE_START_PTR exec.active_note::get_storage # => [num_storage_items, dest_ptr, pad(16)] + # Validate the number of storage items push.CONFIG_AGG_BRIDGE_NUM_STORAGE_ITEMS assert_eq.err=ERR_CONFIG_AGG_BRIDGE_UNEXPECTED_STORAGE_ITEMS drop # => [pad(16)] # Load origin_token_addr(5) and faucet_id from memory # register_faucet expects: [origin_token_addr(5), faucet_id_suffix, faucet_id_prefix, pad(9)] - # Load all 7 values individually in the correct order - mem_load.FAUCET_ID_PREFIX mem_load.FAUCET_ID_SUFFIX mem_load.ORIGIN_TOKEN_ADDR_4 - # => [addr4, faucet_id_suffix, faucet_id_prefix, pad(16)] + # Load origin_token_addr_4, faucet_id_suffix, and faucet_id_prefix onto the sack. Notice that we + # can use `mem_loadw_le` here: that allows us to reuse the existing zeros on the stack, and + # since note memory was not initialized, fourth element on the stack will be equal ZERO, which + # is what we want. + mem_loadw_le.ORIGIN_TOKEN_ADDR_4 + # => [addr4, faucet_id_suffix, faucet_id_prefix, pad(13)] - padw mem_loadw_le.STORAGE_START_PTR - # => [addr4, addr3, addr2, addr1, addr0, faucet_id_suffix, faucet_id_prefix, pad(16)] + # Load remaining origin_token_addr_[0..3] onto the stack + padw mem_loadw_le.ORIGIN_TOKEN_ADDR_0 + # => [addr0, addr1, addr2, addr3, addr4, faucet_id_suffix, faucet_id_prefix, pad(13)] # Register the faucet in the bridge - # => [addr4, addr3, addr2, addr1, addr0, faucet_id_suffix, faucet_id_prefix, pad(9), pad(7)] + # => [addr0, addr1, addr2, addr3, addr4, faucet_id_suffix, faucet_id_prefix, pad(9), pad(4)] + call.bridge_config::register_faucet - # => [pad(16), pad(7)] + # => [pad(16), pad(4)] - dropw drop drop drop + dropw # => [pad(16)] end diff --git a/crates/miden-agglayer/asm/note_scripts/UPDATE_GER.masm b/crates/miden-agglayer/asm/note_scripts/UPDATE_GER.masm index c11d609db0..070d181ef1 100644 --- a/crates/miden-agglayer/asm/note_scripts/UPDATE_GER.masm +++ b/crates/miden-agglayer/asm/note_scripts/UPDATE_GER.masm @@ -1,38 +1,39 @@ use agglayer::bridge::bridge_config use miden::protocol::active_note -use miden::protocol::active_account -use miden::protocol::account_id -use miden::protocol::note use miden::standards::attachments::network_account_target # 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" const ERR_UPDATE_GER_TARGET_ACCOUNT_MISMATCH = "UPDATE_GER note attachment target account does not match consuming account" # NOTE SCRIPT # ================================================================================================= -#! Agglayer Bridge UPDATE_GER script: updates the GER by calling the bridge_config::update_ger function. +#! Agglayer Bridge UPDATE_GER script: updates the GER by calling the bridge_config::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), and only if the note was sent by the -#! global exit root manager. +#! in the note attachment (target_account_id), and only if the note was sent by the global exit root +#! manager. #! #! Requires that the account exposes: #! - agglayer::bridge_config::update_ger procedure. #! #! Inputs: [ARGS, pad(12)] #! Outputs: [pad(16)] +#! #! NoteStorage layout (8 felts total): -#! - GER_LOWER [0..3] -#! - GER_UPPER [4..7] +#! - GER_LOWER [0..3] : 4 felts +#! - GER_UPPER [4..7] : 4 felts #! #! Panics if: #! - account does not expose update_ger procedure. @@ -47,22 +48,21 @@ begin assert.err=ERR_UPDATE_GER_TARGET_ACCOUNT_MISMATCH # => [pad(16)] - # proceed with the GER update logic - + # Load note storage to memory push.STORAGE_PTR_GER_LOWER exec.active_note::get_storage # => [num_storage_items, dest_ptr, pad(16)] + # Validate the number of storage items 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_le.STORAGE_PTR_GER_UPPER # => [GER_UPPER[4], pad(12)] - swapw - mem_loadw_le.STORAGE_PTR_GER_LOWER + + swapw mem_loadw_le.STORAGE_PTR_GER_LOWER # => [GER_LOWER[4], GER_UPPER[4], pad(8)] call.bridge_config::update_ger # => [pad(16)] - end diff --git a/crates/miden-agglayer/diagrams/bridge-in.excalidraw b/crates/miden-agglayer/diagrams/bridge-in.excalidraw new file mode 100644 index 0000000000..94eb00a2fd --- /dev/null +++ b/crates/miden-agglayer/diagrams/bridge-in.excalidraw @@ -0,0 +1,470 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "miden-agglayer", + "elements": [ + { + "id": "title", + "type": "text", + "x": 400, + "y": 30, + "width": 500, + "height": 35, + "text": "Bridge-in (AggLayer to Miden)", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "l1", + "type": "rectangle", + "x": 40, + "y": 320, + "width": 160, + "height": 50, + "strokeColor": "#868e96", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "roundness": { "type": 3 }, + "boundElements": [{"id": "arrow-l1-user", "type": "arrow"}] + }, + { + "id": "l1-label", + "type": "text", + "x": 60, + "y": 332, + "width": 100, + "height": 25, + "text": "AggLayer L1", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "containerId": "l1" + }, + { + "id": "user", + "type": "diamond", + "x": 30, + "y": 120, + "width": 180, + "height": 100, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "boundElements": [ + {"id": "arrow-l1-user", "type": "arrow"}, + {"id": "arrow1", "type": "arrow"} + ] + }, + { + "id": "user-label", + "type": "text", + "x": 60, + "y": 150, + "width": 120, + "height": 40, + "text": "User / Claim\nManager", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "containerId": "user" + }, + { + "id": "arrow-l1-user", + "type": "arrow", + "x": 110, + "y": 320, + "width": 10, + "height": 100, + "strokeColor": "#868e96", + "strokeWidth": 2, + "strokeStyle": "dashed", + "points": [[0, 0], [10, -100]], + "startBinding": {"elementId": "l1", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "user", "focus": 0, "gap": 5} + }, + { + "id": "al1-label", + "type": "text", + "x": 125, + "y": 260, + "width": 80, + "height": 20, + "text": "reads proofs", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left" + }, + { + "id": "claim", + "type": "rectangle", + "x": 300, + "y": 140, + "width": 120, + "height": 60, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "roundness": { "type": 3 }, + "boundElements": [ + {"id": "arrow1", "type": "arrow"}, + {"id": "arrow2", "type": "arrow"} + ] + }, + { + "id": "claim-label", + "type": "text", + "x": 322, + "y": 158, + "width": 76, + "height": 25, + "text": "CLAIM", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "containerId": "claim" + }, + { + "id": "bridge", + "type": "rectangle", + "x": 510, + "y": 90, + "width": 230, + "height": 210, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "boundElements": [ + {"id": "arrow2", "type": "arrow"}, + {"id": "arrow3", "type": "arrow"}, + {"id": "arrow-ger", "type": "arrow"} + ] + }, + { + "id": "bridge-name", + "type": "text", + "x": 570, + "y": 98, + "width": 110, + "height": 35, + "text": "Bridge", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "bridge-desc", + "type": "text", + "x": 525, + "y": 140, + "width": 200, + "height": 150, + "text": "1. Validate GER\n2. Parse global index\n3. Verify Merkle proof\n4. Update CGI chain\n5. Check nullifier\n6. Lookup faucet\n7. Verify amount\n8. Create MINT note", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left" + }, + { + "id": "mint", + "type": "rectangle", + "x": 830, + "y": 140, + "width": 110, + "height": 60, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "roundness": { "type": 3 }, + "boundElements": [ + {"id": "arrow3", "type": "arrow"}, + {"id": "arrow4", "type": "arrow"} + ] + }, + { + "id": "mint-label", + "type": "text", + "x": 855, + "y": 158, + "width": 60, + "height": 25, + "text": "MINT", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "containerId": "mint" + }, + { + "id": "faucet", + "type": "rectangle", + "x": 1030, + "y": 120, + "width": 170, + "height": 100, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "boundElements": [ + {"id": "arrow4", "type": "arrow"}, + {"id": "arrow5", "type": "arrow"} + ] + }, + { + "id": "faucet-name", + "type": "text", + "x": 1068, + "y": 128, + "width": 94, + "height": 35, + "text": "Faucet", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "faucet-desc", + "type": "text", + "x": 1070, + "y": 173, + "width": 90, + "height": 25, + "text": "Mint & send", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "p2id", + "type": "rectangle", + "x": 1290, + "y": 140, + "width": 100, + "height": 60, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "roundness": { "type": 3 }, + "boundElements": [ + {"id": "arrow5", "type": "arrow"}, + {"id": "arrow6", "type": "arrow"} + ] + }, + { + "id": "p2id-label", + "type": "text", + "x": 1312, + "y": 158, + "width": 56, + "height": 25, + "text": "P2ID", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "containerId": "p2id" + }, + { + "id": "recipient", + "type": "diamond", + "x": 1480, + "y": 120, + "width": 160, + "height": 100, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "boundElements": [{"id": "arrow6", "type": "arrow"}] + }, + { + "id": "recipient-label", + "type": "text", + "x": 1512, + "y": 155, + "width": 96, + "height": 25, + "text": "Recipient", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "containerId": "recipient" + }, + { + "id": "ger-box", + "type": "rectangle", + "x": 555, + "y": 370, + "width": 140, + "height": 50, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "roundness": { "type": 3 }, + "boundElements": [{"id": "arrow-ger", "type": "arrow"}] + }, + { + "id": "ger-label", + "type": "text", + "x": 580, + "y": 382, + "width": 90, + "height": 25, + "text": "GER Map", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "containerId": "ger-box" + }, + { + "id": "arrow1", + "type": "arrow", + "x": 210, + "y": 170, + "width": 90, + "height": 0, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "points": [[0, 0], [90, 0]], + "startBinding": {"elementId": "user", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "claim", "focus": 0, "gap": 5} + }, + { + "id": "a1-label", + "type": "text", + "x": 220, + "y": 148, + "width": 70, + "height": 20, + "text": "creates", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "arrow2", + "type": "arrow", + "x": 420, + "y": 170, + "width": 90, + "height": 0, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "points": [[0, 0], [90, 0]], + "startBinding": {"elementId": "claim", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "bridge", "focus": 0, "gap": 5} + }, + { + "id": "arrow3", + "type": "arrow", + "x": 740, + "y": 170, + "width": 90, + "height": 0, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "points": [[0, 0], [90, 0]], + "startBinding": {"elementId": "bridge", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "mint", "focus": 0, "gap": 5} + }, + { + "id": "a3-label", + "type": "text", + "x": 750, + "y": 148, + "width": 70, + "height": 20, + "text": "creates", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "arrow4", + "type": "arrow", + "x": 940, + "y": 170, + "width": 90, + "height": 0, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "points": [[0, 0], [90, 0]], + "startBinding": {"elementId": "mint", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "faucet", "focus": 0, "gap": 5} + }, + { + "id": "arrow5", + "type": "arrow", + "x": 1200, + "y": 170, + "width": 90, + "height": 0, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "points": [[0, 0], [90, 0]], + "startBinding": {"elementId": "faucet", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "p2id", "focus": 0, "gap": 5} + }, + { + "id": "a5-label", + "type": "text", + "x": 1210, + "y": 148, + "width": 70, + "height": 20, + "text": "creates", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "arrow6", + "type": "arrow", + "x": 1390, + "y": 170, + "width": 90, + "height": 0, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "points": [[0, 0], [90, 0]], + "startBinding": {"elementId": "p2id", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "recipient", "focus": 0, "gap": 5} + }, + { + "id": "arrow-ger", + "type": "arrow", + "x": 625, + "y": 300, + "width": 0, + "height": 70, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "strokeStyle": "dashed", + "points": [[0, 0], [0, 70]], + "startBinding": {"elementId": "bridge", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "ger-box", "focus": 0, "gap": 5} + }, + { + "id": "ager-label", + "type": "text", + "x": 635, + "y": 328, + "width": 60, + "height": 20, + "text": "validate", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left" + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} diff --git a/crates/miden-agglayer/diagrams/bridge-in.png b/crates/miden-agglayer/diagrams/bridge-in.png new file mode 100644 index 0000000000..00efefc85c Binary files /dev/null and b/crates/miden-agglayer/diagrams/bridge-in.png differ diff --git a/crates/miden-agglayer/diagrams/bridge-out.excalidraw b/crates/miden-agglayer/diagrams/bridge-out.excalidraw new file mode 100644 index 0000000000..5301ab97ce --- /dev/null +++ b/crates/miden-agglayer/diagrams/bridge-out.excalidraw @@ -0,0 +1,373 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "miden-agglayer", + "elements": [ + { + "id": "title", + "type": "text", + "x": 350, + "y": 30, + "width": 500, + "height": 35, + "text": "Bridge-out (Miden to AggLayer)", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "user", + "type": "diamond", + "x": 50, + "y": 130, + "width": 140, + "height": 100, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "boundElements": [{"id": "arrow1", "type": "arrow"}] + }, + { + "id": "user-label", + "type": "text", + "x": 88, + "y": 167, + "width": 64, + "height": 25, + "text": "User", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "containerId": "user" + }, + { + "id": "b2agg", + "type": "rectangle", + "x": 280, + "y": 150, + "width": 140, + "height": 60, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "roundness": { "type": 3 }, + "boundElements": [ + {"id": "arrow1", "type": "arrow"}, + {"id": "arrow2", "type": "arrow"} + ] + }, + { + "id": "b2agg-label", + "type": "text", + "x": 310, + "y": 168, + "width": 80, + "height": 25, + "text": "B2AGG", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "containerId": "b2agg" + }, + { + "id": "bridge", + "type": "rectangle", + "x": 510, + "y": 100, + "width": 220, + "height": 180, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "boundElements": [ + {"id": "arrow2", "type": "arrow"}, + {"id": "arrow3", "type": "arrow"}, + {"id": "arrow5", "type": "arrow"} + ] + }, + { + "id": "bridge-name", + "type": "text", + "x": 565, + "y": 108, + "width": 110, + "height": 35, + "text": "Bridge", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "bridge-desc", + "type": "text", + "x": 525, + "y": 150, + "width": 190, + "height": 120, + "text": "1. Validate faucet\n2. FPI: get amount\n3. FPI: get metadata\n4. Build leaf data\n5. Append to LET\n6. Create BURN note", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left" + }, + { + "id": "burn", + "type": "rectangle", + "x": 820, + "y": 150, + "width": 120, + "height": 60, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "roundness": { "type": 3 }, + "boundElements": [ + {"id": "arrow3", "type": "arrow"}, + {"id": "arrow4", "type": "arrow"} + ] + }, + { + "id": "burn-label", + "type": "text", + "x": 845, + "y": 168, + "width": 70, + "height": 25, + "text": "BURN", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "containerId": "burn" + }, + { + "id": "faucet", + "type": "rectangle", + "x": 1030, + "y": 130, + "width": 160, + "height": 100, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "boundElements": [{"id": "arrow4", "type": "arrow"}] + }, + { + "id": "faucet-name", + "type": "text", + "x": 1063, + "y": 140, + "width": 94, + "height": 35, + "text": "Faucet", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "faucet-desc", + "type": "text", + "x": 1070, + "y": 185, + "width": 80, + "height": 25, + "text": "Burn asset", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "let-box", + "type": "rectangle", + "x": 540, + "y": 350, + "width": 160, + "height": 50, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "roundness": { "type": 3 }, + "boundElements": [ + {"id": "arrow5", "type": "arrow"}, + {"id": "arrow-l1", "type": "arrow"} + ] + }, + { + "id": "let-label", + "type": "text", + "x": 555, + "y": 362, + "width": 130, + "height": 25, + "text": "Local Exit Tree", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "containerId": "let-box" + }, + { + "id": "l1", + "type": "rectangle", + "x": 900, + "y": 350, + "width": 160, + "height": 50, + "strokeColor": "#868e96", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "roundness": { "type": 3 }, + "boundElements": [{"id": "arrow-l1", "type": "arrow"}] + }, + { + "id": "l1-label", + "type": "text", + "x": 930, + "y": 362, + "width": 100, + "height": 25, + "text": "AggLayer L1", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "containerId": "l1" + }, + { + "id": "arrow1", + "type": "arrow", + "x": 190, + "y": 180, + "width": 90, + "height": 0, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "points": [[0, 0], [90, 0]], + "startBinding": {"elementId": "user", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "b2agg", "focus": 0, "gap": 5} + }, + { + "id": "a1-label", + "type": "text", + "x": 200, + "y": 157, + "width": 70, + "height": 20, + "text": "creates", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "arrow2", + "type": "arrow", + "x": 420, + "y": 180, + "width": 90, + "height": 0, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "points": [[0, 0], [90, 0]], + "startBinding": {"elementId": "b2agg", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "bridge", "focus": 0, "gap": 5} + }, + { + "id": "arrow3", + "type": "arrow", + "x": 730, + "y": 180, + "width": 90, + "height": 0, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "points": [[0, 0], [90, 0]], + "startBinding": {"elementId": "bridge", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "burn", "focus": 0, "gap": 5} + }, + { + "id": "a3-label", + "type": "text", + "x": 740, + "y": 157, + "width": 70, + "height": 20, + "text": "creates", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "arrow4", + "type": "arrow", + "x": 940, + "y": 180, + "width": 90, + "height": 0, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "points": [[0, 0], [90, 0]], + "startBinding": {"elementId": "burn", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "faucet", "focus": 0, "gap": 5} + }, + { + "id": "arrow5", + "type": "arrow", + "x": 620, + "y": 280, + "width": 0, + "height": 70, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "strokeStyle": "dashed", + "points": [[0, 0], [0, 70]], + "startBinding": {"elementId": "bridge", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "let-box", "focus": 0, "gap": 5} + }, + { + "id": "a5-label", + "type": "text", + "x": 630, + "y": 308, + "width": 80, + "height": 20, + "text": "append leaf", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left" + }, + { + "id": "arrow-l1", + "type": "arrow", + "x": 700, + "y": 375, + "width": 200, + "height": 0, + "strokeColor": "#868e96", + "strokeWidth": 2, + "strokeStyle": "dashed", + "points": [[0, 0], [200, 0]], + "startBinding": {"elementId": "let-box", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "l1", "focus": 0, "gap": 5} + }, + { + "id": "al1-label", + "type": "text", + "x": 745, + "y": 353, + "width": 110, + "height": 20, + "text": "LET root posted", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center" + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} diff --git a/crates/miden-agglayer/diagrams/bridge-out.png b/crates/miden-agglayer/diagrams/bridge-out.png new file mode 100644 index 0000000000..19b8665f70 Binary files /dev/null and b/crates/miden-agglayer/diagrams/bridge-out.png differ diff --git a/crates/miden-agglayer/diagrams/faucet-registration.excalidraw b/crates/miden-agglayer/diagrams/faucet-registration.excalidraw new file mode 100644 index 0000000000..c5a235074e --- /dev/null +++ b/crates/miden-agglayer/diagrams/faucet-registration.excalidraw @@ -0,0 +1,317 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "miden-agglayer", + "elements": [ + { + "id": "title", + "type": "text", + "x": 280, + "y": 30, + "width": 400, + "height": 35, + "text": "Faucet Registration", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "operator", + "type": "diamond", + "x": 30, + "y": 120, + "width": 180, + "height": 100, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "boundElements": [{"id": "arrow1", "type": "arrow"}] + }, + { + "id": "operator-label", + "type": "text", + "x": 55, + "y": 150, + "width": 130, + "height": 40, + "text": "Bridge\nOperator", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "containerId": "operator" + }, + { + "id": "config-note", + "type": "rectangle", + "x": 270, + "y": 120, + "width": 280, + "height": 60, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "roundness": { "type": 3 }, + "boundElements": [ + {"id": "arrow1", "type": "arrow"}, + {"id": "arrow2", "type": "arrow"} + ] + }, + { + "id": "config-label", + "type": "text", + "x": 285, + "y": 133, + "width": 250, + "height": 35, + "text": "CONFIG_AGG_BRIDGE", + "fontSize": 22, + "fontFamily": 1, + "textAlign": "center", + "containerId": "config-note" + }, + { + "id": "bridge", + "type": "rectangle", + "x": 620, + "y": 100, + "width": 220, + "height": 150, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "boundElements": [ + {"id": "arrow2", "type": "arrow"}, + {"id": "arrow3", "type": "arrow"}, + {"id": "arrow4", "type": "arrow"}, + {"id": "arrow-ref", "type": "arrow"} + ] + }, + { + "id": "bridge-name", + "type": "text", + "x": 675, + "y": 108, + "width": 110, + "height": 35, + "text": "Bridge", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "bridge-desc", + "type": "text", + "x": 635, + "y": 150, + "width": 190, + "height": 85, + "text": "1. Assert sender\n2. Register in faucet reg\n3. Register in token reg", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left" + }, + { + "id": "faucet-reg", + "type": "rectangle", + "x": 580, + "y": 320, + "width": 140, + "height": 50, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "roundness": { "type": 3 }, + "boundElements": [{"id": "arrow3", "type": "arrow"}] + }, + { + "id": "faucet-reg-label", + "type": "text", + "x": 595, + "y": 332, + "width": 110, + "height": 25, + "text": "Faucet Registry", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "containerId": "faucet-reg" + }, + { + "id": "token-reg", + "type": "rectangle", + "x": 760, + "y": 320, + "width": 140, + "height": 50, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "roundness": { "type": 3 }, + "boundElements": [{"id": "arrow4", "type": "arrow"}] + }, + { + "id": "token-reg-label", + "type": "text", + "x": 775, + "y": 332, + "width": 110, + "height": 25, + "text": "Token Registry", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "containerId": "token-reg" + }, + { + "id": "faucet-acct", + "type": "rectangle", + "x": 940, + "y": 120, + "width": 170, + "height": 80, + "strokeColor": "#868e96", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "boundElements": [{"id": "arrow-ref", "type": "arrow"}] + }, + { + "id": "faucet-name", + "type": "text", + "x": 978, + "y": 140, + "width": 94, + "height": 35, + "text": "Faucet", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "arrow1", + "type": "arrow", + "x": 210, + "y": 170, + "width": 60, + "height": 0, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "points": [[0, 0], [60, 0]], + "startBinding": {"elementId": "operator", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "config-note", "focus": 0, "gap": 5} + }, + { + "id": "a1-label", + "type": "text", + "x": 215, + "y": 145, + "width": 50, + "height": 20, + "text": "creates", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "arrow2", + "type": "arrow", + "x": 550, + "y": 150, + "width": 70, + "height": 0, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "points": [[0, 0], [70, 0]], + "startBinding": {"elementId": "config-note", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "bridge", "focus": 0, "gap": 5} + }, + { + "id": "arrow3", + "type": "arrow", + "x": 680, + "y": 250, + "width": -30, + "height": 70, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "strokeStyle": "dashed", + "points": [[0, 0], [-30, 70]], + "startBinding": {"elementId": "bridge", "focus": -0.5, "gap": 5}, + "endBinding": {"elementId": "faucet-reg", "focus": 0, "gap": 5} + }, + { + "id": "a3-label", + "type": "text", + "x": 615, + "y": 278, + "width": 40, + "height": 20, + "text": "write", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left" + }, + { + "id": "arrow4", + "type": "arrow", + "x": 780, + "y": 250, + "width": 50, + "height": 70, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "strokeStyle": "dashed", + "points": [[0, 0], [50, 70]], + "startBinding": {"elementId": "bridge", "focus": 0.5, "gap": 5}, + "endBinding": {"elementId": "token-reg", "focus": 0, "gap": 5} + }, + { + "id": "a4-label", + "type": "text", + "x": 810, + "y": 278, + "width": 40, + "height": 20, + "text": "write", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left" + }, + { + "id": "arrow-ref", + "type": "arrow", + "x": 840, + "y": 160, + "width": 100, + "height": 0, + "strokeColor": "#868e96", + "strokeWidth": 2, + "strokeStyle": "dashed", + "points": [[0, 0], [100, 0]], + "startBinding": {"elementId": "bridge", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "faucet-acct", "focus": 0, "gap": 5} + }, + { + "id": "aref-label", + "type": "text", + "x": 855, + "y": 138, + "width": 80, + "height": 20, + "text": "references", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center" + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} diff --git a/crates/miden-agglayer/diagrams/faucet-registration.png b/crates/miden-agglayer/diagrams/faucet-registration.png new file mode 100644 index 0000000000..7f43fa2bee Binary files /dev/null and b/crates/miden-agglayer/diagrams/faucet-registration.png differ diff --git a/crates/miden-agglayer/diagrams/ger-injection.excalidraw b/crates/miden-agglayer/diagrams/ger-injection.excalidraw new file mode 100644 index 0000000000..4899e06b26 --- /dev/null +++ b/crates/miden-agglayer/diagrams/ger-injection.excalidraw @@ -0,0 +1,267 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "miden-agglayer", + "elements": [ + { + "id": "title", + "type": "text", + "x": 250, + "y": 30, + "width": 400, + "height": 35, + "text": "GER Injection", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "ger-mgr", + "type": "diamond", + "x": 30, + "y": 120, + "width": 180, + "height": 100, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "boundElements": [ + {"id": "arrow1", "type": "arrow"}, + {"id": "arrow-observe", "type": "arrow"} + ] + }, + { + "id": "ger-mgr-label", + "type": "text", + "x": 55, + "y": 150, + "width": 130, + "height": 40, + "text": "GER\nManager", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "containerId": "ger-mgr" + }, + { + "id": "l1", + "type": "rectangle", + "x": 50, + "y": 280, + "width": 140, + "height": 50, + "strokeColor": "#868e96", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "roundness": { "type": 3 }, + "boundElements": [{"id": "arrow-observe", "type": "arrow"}] + }, + { + "id": "l1-label", + "type": "text", + "x": 65, + "y": 292, + "width": 110, + "height": 25, + "text": "AggLayer L1", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "containerId": "l1" + }, + { + "id": "arrow-observe", + "type": "arrow", + "x": 120, + "y": 280, + "width": 0, + "height": -60, + "strokeColor": "#868e96", + "strokeWidth": 2, + "strokeStyle": "dashed", + "points": [[0, 0], [0, -60]], + "startBinding": {"elementId": "l1", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "ger-mgr", "focus": 0, "gap": 5} + }, + { + "id": "observe-label", + "type": "text", + "x": 130, + "y": 240, + "width": 90, + "height": 20, + "text": "observes GER", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left" + }, + { + "id": "update-ger", + "type": "rectangle", + "x": 310, + "y": 140, + "width": 160, + "height": 60, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "roundness": { "type": 3 }, + "boundElements": [ + {"id": "arrow1", "type": "arrow"}, + {"id": "arrow2", "type": "arrow"} + ] + }, + { + "id": "update-ger-label", + "type": "text", + "x": 325, + "y": 150, + "width": 130, + "height": 40, + "text": "UPDATE_GER", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "containerId": "update-ger" + }, + { + "id": "bridge", + "type": "rectangle", + "x": 570, + "y": 100, + "width": 220, + "height": 150, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "boundElements": [ + {"id": "arrow2", "type": "arrow"}, + {"id": "arrow3", "type": "arrow"} + ] + }, + { + "id": "bridge-name", + "type": "text", + "x": 625, + "y": 108, + "width": 110, + "height": 35, + "text": "Bridge", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "bridge-desc", + "type": "text", + "x": 585, + "y": 150, + "width": 190, + "height": 85, + "text": "1. Assert sender\n2. Compute GER hash\n3. Store in ger_map", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left" + }, + { + "id": "ger-map", + "type": "rectangle", + "x": 610, + "y": 320, + "width": 140, + "height": 50, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "roundness": { "type": 3 }, + "boundElements": [{"id": "arrow3", "type": "arrow"}] + }, + { + "id": "ger-map-label", + "type": "text", + "x": 635, + "y": 332, + "width": 90, + "height": 25, + "text": "GER Map", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "containerId": "ger-map" + }, + { + "id": "arrow1", + "type": "arrow", + "x": 210, + "y": 170, + "width": 100, + "height": 0, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "points": [[0, 0], [100, 0]], + "startBinding": {"elementId": "ger-mgr", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "update-ger", "focus": 0, "gap": 5} + }, + { + "id": "a1-label", + "type": "text", + "x": 225, + "y": 148, + "width": 70, + "height": 20, + "text": "creates", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center" + }, + { + "id": "arrow2", + "type": "arrow", + "x": 470, + "y": 170, + "width": 100, + "height": 0, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "points": [[0, 0], [100, 0]], + "startBinding": {"elementId": "update-ger", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "bridge", "focus": 0, "gap": 5} + }, + { + "id": "arrow3", + "type": "arrow", + "x": 680, + "y": 250, + "width": 0, + "height": 70, + "strokeColor": "#1e1e1e", + "strokeWidth": 2, + "strokeStyle": "dashed", + "points": [[0, 0], [0, 70]], + "startBinding": {"elementId": "bridge", "focus": 0, "gap": 5}, + "endBinding": {"elementId": "ger-map", "focus": 0, "gap": 5} + }, + { + "id": "a3-label", + "type": "text", + "x": 690, + "y": 278, + "width": 40, + "height": 20, + "text": "store", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left" + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} diff --git a/crates/miden-agglayer/diagrams/ger-injection.png b/crates/miden-agglayer/diagrams/ger-injection.png new file mode 100644 index 0000000000..31bee22110 Binary files /dev/null and b/crates/miden-agglayer/diagrams/ger-injection.png differ diff --git a/crates/miden-agglayer/src/bridge.rs b/crates/miden-agglayer/src/bridge.rs index 08b493f947..2ec155232b 100644 --- a/crates/miden-agglayer/src/bridge.rs +++ b/crates/miden-agglayer/src/bridge.rs @@ -116,7 +116,7 @@ static LET_NUM_LEAVES_SLOT_NAME: LazyLock = LazyLock::new(|| { /// - `update_ger`, which injects a new GER into the storage map. /// - `bridge_out`, which bridges an asset out of Miden to the destination network. /// - `claim`, which validates a claim against the AggLayer bridge and creates a MINT note for the -/// AggFaucet. +/// AggLayer Faucet. /// /// ## Storage Layout /// diff --git a/crates/miden-agglayer/src/claim_note.rs b/crates/miden-agglayer/src/claim_note.rs index 5cd5b9f17f..a3c5702bae 100644 --- a/crates/miden-agglayer/src/claim_note.rs +++ b/crates/miden-agglayer/src/claim_note.rs @@ -162,7 +162,7 @@ impl TryFrom for NoteStorage { // ================================================================================================ /// Generates a CLAIM note - a note that instructs the bridge to validate a claim and create -/// a MINT note for the aggfaucet. +/// a MINT note for the AggLayer Faucet. /// /// # Parameters /// - `storage`: The core storage for creating the CLAIM note diff --git a/crates/miden-testing/tests/agglayer/bridge_out.rs b/crates/miden-testing/tests/agglayer/bridge_out.rs index 52c11a6a73..e0a61d3e47 100644 --- a/crates/miden-testing/tests/agglayer/bridge_out.rs +++ b/crates/miden-testing/tests/agglayer/bridge_out.rs @@ -16,7 +16,7 @@ use miden_protocol::Felt; use miden_protocol::account::auth::AuthScheme; use miden_protocol::account::{AccountId, AccountIdVersion, AccountStorageMode, AccountType}; use miden_protocol::asset::{Asset, FungibleAsset}; -use miden_protocol::note::{NoteAssets, NoteScript, NoteType}; +use miden_protocol::note::{NoteAssets, NoteType}; use miden_protocol::transaction::RawOutputNote; use miden_standards::account::faucets::TokenMetadata; use miden_standards::account::mint_policies::OwnerControlledInitConfig; @@ -156,7 +156,6 @@ async fn bridge_out_consecutive() -> anyhow::Result<()> { // STEP 2: CONSUME 32 B2AGG NOTES AND VERIFY FRONTIER EVOLUTION // -------------------------------------------------------------------------------------------- - let burn_note_script: NoteScript = StandardNote::BURN.script(); let mut burn_note_ids = Vec::with_capacity(note_count); for (i, note) in notes.iter().enumerate() { @@ -164,7 +163,6 @@ async fn bridge_out_consecutive() -> anyhow::Result<()> { let executed_tx = mock_chain .build_tx_context(bridge_account.clone(), &[note.id()], &[])? - .add_note_script(burn_note_script.clone()) .foreign_accounts(vec![foreign_account_inputs]) .build()? .execute()