Skip to content

Commit d74cf1c

Browse files
claudemmagician
authored andcommitted
docs(agglayer): add Section 6 - Faucet Registry to SPEC.md
1 parent 2affd1c commit d74cf1c

1 file changed

Lines changed: 150 additions & 0 deletions

File tree

crates/miden-agglayer/SPEC.md

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,3 +690,153 @@ the little-endian bytes within each limb in `NoteStorage` and the big-endian-byt
690690
The encoding is a bijection over the set of valid `AccountId` values: for every valid
691691
`AccountId`, `from_account_id` followed by `to_account_id` (or the MASM equivalent)
692692
recovers the original.
693+
694+
---
695+
696+
## 6. Faucet Registry
697+
698+
The AggLayer bridge connects multiple chains, each with its own native token ecosystem.
699+
When tokens move between chains, they need a representation on the destination chain.
700+
This section describes how tokens are registered for bridging and the role of the
701+
faucet registry.
702+
703+
Terminology:
704+
705+
- Native token: a token originally issued on a given chain. For example, USDC on Ethereum
706+
is native to Ethereum; a fungible faucet created directly on Miden is native to Miden.
707+
- Non-native (wrapped) token: a representation of a foreign token, created to track
708+
bridged balances. On Miden, each non-native ERC20 is represented by a dedicated
709+
AggLayer faucet. On EVM chains, each non-native Miden token would be represented by a
710+
deployed wrapped ERC20 contract.
711+
712+
A faucet must be registered in the bridge's faucet registry before it can participate in
713+
bridging. The registry is a map in the bridge account's storage
714+
(`miden::agglayer::bridge::faucet_registry`) that records which faucet account IDs are
715+
authorized for bridge-out operations. Without registration, `bridge_out::bridge_out`
716+
rejects the asset (see `bridge_config::assert_faucet_registered`).
717+
718+
### 6.1 Bridging-in: Registering non-native faucets on Miden
719+
720+
When a new ERC20 token is bridged to Miden for the first time, a corresponding AggLayer
721+
faucet account must be created and registered. The faucet serves as the mint/burn
722+
authority for the wrapped token on Miden.
723+
724+
The `AggLayerFaucet` struct (Rust, `src/lib.rs`) captures the full faucet configuration:
725+
726+
- Token metadata: symbol, decimals, max_supply (stored in the standard
727+
`NetworkFungibleFaucet` metadata slot)
728+
- Bridge account ID: the bridge this faucet is paired with
729+
(`miden::agglayer::faucet`)
730+
- Origin token address: the ERC20 contract address on the origin chain
731+
(`miden::agglayer::faucet::conversion_info_1`, `conversion_info_2`)
732+
- Origin network: the chain ID of the origin chain (stored in `conversion_info_2`)
733+
- Scale factor: the exponent used to convert between EVM U256 amounts and Miden felt
734+
amounts (stored in `conversion_info_2`)
735+
- Metadata hash: `keccak256(abi.encode(name, symbol, decimals))`, stored across two
736+
value slots (`miden::agglayer::faucet::metadata_hash_lo`,
737+
`miden::agglayer::faucet::metadata_hash_hi`)
738+
739+
Registration is performed via `CONFIG_AGG_BRIDGE` notes (see Section 3.3). The bridge
740+
operator creates a `CONFIG_AGG_BRIDGE` note containing the faucet's account ID and sends
741+
it to the bridge account. On consumption, the note script calls
742+
`bridge_config::register_faucet`, which:
743+
744+
1. Asserts the note sender matches the bridge admin stored in
745+
`miden::agglayer::bridge::admin`.
746+
2. Writes the faucet ID into the `faucet_registry` map with value `[1, 0, 0, 0]`.
747+
748+
The bridge admin is a trusted role. There is currently no on-chain verification that the
749+
faucet's stored metadata (origin address, scale, metadata hash) is correct. The bridge
750+
operator is trusted to deploy faucets with accurate configuration.
751+
752+
Implementation status:
753+
754+
- Implemented: Faucet creation with metadata hash, faucet storage layout, FPI retrieval
755+
of metadata hash via `agglayer_faucet::get_metadata_hash`, registration via
756+
`CONFIG_AGG_BRIDGE` notes, faucet registry lookup in `bridge_config::assert_faucet_registered`.
757+
- Not yet implemented: On-chain verification of the metadata hash during registration.
758+
This would require the token name to be available in faucet storage and
759+
`abi.encode(string, string, uint8)` to be implemented in MASM, so the bridge could
760+
recompute `keccak256(abi.encode(name, symbol, decimals))` and compare it against the
761+
stored hash. See the TODO in `bridge_config::register_faucet`.
762+
- Not yet implemented: Token name storage in the faucet. Currently only the symbol is
763+
stored in the standard faucet metadata slot; the full name is not persisted on-chain.
764+
A separate PR is in progress to add this.
765+
766+
### 6.2 Bridging-out: How Miden-native tokens are registered on other chains
767+
768+
When an asset is bridged out from Miden, `bridge_out::bridge_out` constructs a leaf for
769+
the Local Exit Tree. The leaf includes the metadata hash, which the bridge fetches from
770+
the faucet via FPI (`agglayer_faucet::get_metadata_hash`). The full leaf structure
771+
contains: leaf type, origin network, origin token address, destination network,
772+
destination address, amount (U256), and metadata hash (see `bridge_out.masm`,
773+
`add_leaf_bridge`).
774+
775+
On the EVM side, when a user claims the bridged asset via
776+
`PolygonZkEVMBridgeV2.claimAsset()`, the wrapped token is deployed lazily on first claim.
777+
The claimer provides the raw metadata bytes (the ABI-encoded name, symbol, and decimals)
778+
as a parameter to `claimAsset()`. The EVM bridge verifies that
779+
`keccak256(metadata_bytes) == metadataHash` from the Merkle leaf. If the hash matches and
780+
no wrapped token exists yet, the bridge deploys a new `TokenWrapped` ERC20 using the
781+
decoded name, symbol, and decimals from the metadata bytes.
782+
783+
Not yet implemented - registration of Miden-native faucets for bridging out:
784+
785+
The current implementation assumes all registered faucets wrap foreign (ERC20) tokens.
786+
For a Miden-native faucet (one that does not wrap an ERC20) to bridge out to an EVM
787+
chain, it would need to:
788+
789+
- Store a metadata hash computed from its token name, symbol, and decimals.
790+
- Be registered in the bridge's faucet registry.
791+
- Provide origin token address and origin network values that make sense for a
792+
Miden-native asset.
793+
794+
The design for how a Miden-native faucet registers itself for outbound bridging has not
795+
been finalized. An issue should be created to design this registration flow, covering
796+
questions such as: what origin network ID represents Miden, how the origin token address
797+
is derived for Miden-native tokens, and whether the bridge admin must approve registration
798+
or if self-registration is possible.
799+
800+
### 6.3 Metadata hash
801+
802+
The metadata hash serves two purposes in the AggLayer bridge:
803+
804+
1. Leaf verification: the metadata hash is included in each Local Exit Tree leaf. When a
805+
claim is verified on the destination chain, the leaf's metadata hash must match the
806+
keccak256 of the caller-provided metadata bytes. This binds the token identity to the
807+
Merkle proof.
808+
2. Wrapped token deployment: on EVM chains, the raw metadata bytes (name, symbol,
809+
decimals) provided during `claimAsset()` are used to deploy the wrapped ERC20 contract.
810+
The hash verification ensures the deployed token has the correct name, symbol, and
811+
decimals.
812+
813+
The metadata hash is computed as:
814+
815+
```
816+
keccak256(abi.encode(string name, string symbol, uint8 decimals))
817+
```
818+
819+
This matches the encoding produced by the Solidity bridge's `getTokenMetadata` function.
820+
The `abi.encode` format for `(string, string, uint8)` is:
821+
822+
- 3 x 32-byte head slots: offset to name, offset to symbol, decimals value
823+
- Name: 32-byte length word + data padded to 32-byte boundary
824+
- Symbol: 32-byte length word + data padded to 32-byte boundary
825+
826+
In the Rust implementation, `MetadataHash::from_token_info(name, symbol, decimals)`
827+
(defined in `src/eth_types/metadata_hash.rs`) performs this encoding and hashing. The
828+
helper `encode_token_metadata` produces the raw ABI-encoded bytes, and
829+
`MetadataHash::from_abi_encoded` computes the keccak256 digest.
830+
831+
The metadata hash is computed off-chain at faucet creation time and stored pre-computed
832+
in faucet storage across two value slots (`metadata_hash_lo` and `metadata_hash_hi`,
833+
each holding 4 u32 felts). During bridge-out, the bridge retrieves the hash via FPI
834+
(`agglayer_faucet::get_metadata_hash`) rather than recomputing it on-chain.
835+
836+
Correctness argument: if a faucet stores an incorrect metadata hash, the EVM-side
837+
`claimAsset()` call will fail. The claimer must provide metadata bytes whose keccak256
838+
matches the leaf's metadata hash. Since keccak256 is preimage-resistant, the claimer
839+
cannot produce valid metadata bytes for an incorrect hash. This means a misconfigured
840+
faucet would result in unclaimable assets on the destination chain, providing a strong
841+
incentive for the bridge operator to set the correct metadata hash at faucet creation
842+
time.

0 commit comments

Comments
 (0)