Skip to content

Commit b26fec7

Browse files
feat(agglayer): add faucet deregistration
Adds a bridge-admin-gated `deregister_faucet` MASM procedure that revokes a faucet, clearing every entry registration wrote for it: the `faucet_registry_map`, `token_registry_map`, and all four `faucet_metadata_map` sub-keys. Wired through a new `DEREGISTER_AGG_BRIDGE` note script and a `DeregisterAggBridgeNote` Rust builder. After deregistration, in-flight B2AGG and CLAIM notes referencing the cleared faucet fail their existing registration checks. The token registry is pair-keyed on (origin_token_address, origin_network). Rather than trusting note-supplied values, `deregister_faucet` reads the faucet's registered address and network back from its own `faucet_metadata_map` (via `get_faucet_conversion_info`) and recomputes the key, so the cleared token-registry entry is provably the one `register_faucet` wrote and the two registries cannot desynchronize. The note therefore carries only the faucet id (2 felts). - bridge_config.masm: new `deregister_faucet` proc reusing `assert_sender_is_bridge_admin`, `assert_faucet_registered`, `get_faucet_conversion_info`, and `hash_token_address`. - components/bridge.masm: re-export `deregister_faucet`. - note_scripts/deregister_agg_bridge.masm: new note script. - src/deregister_note.rs: new `DeregisterAggBridgeNote` builder. - bridge.rs: add the DEREGISTER script root to the network-account note allowlist. - tests/agglayer/config_bridge.rs: round-trip test verifying all three maps are cleared, an end-to-end test proving the faucet is revoked, and negative tests for `ERR_FAUCET_NOT_REGISTERED` and `ERR_SENDER_NOT_BRIDGE_ADMIN`. - SPEC.md, CHANGELOG.md: documentation updates.
1 parent 88cd3a6 commit b26fec7

11 files changed

Lines changed: 1107 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
- [BREAKING] Removed `AccountStorageMode::Network`; network accounts are now identified via `NetworkAccountNoteAllowlist` ([#2900](https://github.com/0xMiden/protocol/pull/2900)).
5454
- Added `PswapAttachment` scheme and `PswapNote::payback_note` / `remainder_note` discovery helpers so creators can reconstruct private paybacks from on-chain commitments ([#2909](https://github.com/0xMiden/protocol/pull/2909)).
5555
- Added benchmark for ECDSA signed transaction ([#2967](https://github.com/0xMiden/protocol/pull/2967)).
56+
- [agglayer] Added `bridge_config::deregister_faucet` MASM procedure, `DEREGISTER_AGG_BRIDGE` note script, and `DeregisterAggBridgeNote` Rust builder, enabling the bridge admin to revoke a faucet's authorization by clearing its `faucet_registry_map`, `token_registry_map`, and `faucet_metadata_map` entries. The token-registry key is recomputed from the faucet's stored metadata so the cleared key always matches what registration wrote, rather than trusting note-supplied values. `bridge_in::claim` now also re-checks `assert_faucet_registered` after the token lookup, making deregistration an unconditional revocation even if a stale token-registry key survives a re-registration ([#2838](https://github.com/0xMiden/protocol/pull/2838)).
5657

5758
### Changes
5859

crates/miden-agglayer/SPEC.md

Lines changed: 118 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ asset and the destination network/address. The bridge account consumes this note
4949
5. Computes the Keccak-256 leaf value and appends it to the Local Exit Tree (LET).
5050
6. Dispatches on the faucet's `is_native` flag (also read from the registry):
5151
- **Wrapped faucet (`is_native = false`):** the bridge does not hold the asset onchain; it
52-
emits a public [`BURN`](#45-burn-generated) note targeting the faucet, which the faucet
52+
emits a public [`BURN`](#46-burn-generated) note targeting the faucet, which the faucet
5353
consumes to burn the asset and decrement the faucet's token supply.
5454
- **Miden-native faucet (`is_native = true`):** the bridge does not hold mint/burn authority
5555
for the faucet, so it cannot emit a `BURN`. Instead it locks the asset by adding it to
@@ -81,12 +81,13 @@ The `CLAIM` note is consumed by the bridge account:
8181
`NEW_CGI = Keccak256(OLD_CGI, Keccak256(GLOBAL_INDEX, LEAF_VALUE))`.
8282
5. Checks and sets the claim nullifier to prevent double-claiming.
8383
6. Looks up the faucet from the `(origin_token_address, origin_network)` pair via the token
84-
registry.
84+
registry, then asserts the resolved faucet is still registered (`assert_faucet_registered`) so a
85+
deregistered faucet reachable via a stale token key cannot mint.
8586
7. Verifies the claim amount against the leaf's U256 amount and the faucet's scale factor.
8687
8. Dispatches on the faucet's `is_native` flag:
87-
- **Wrapped faucet (`is_native = false`):** the bridge emits a [`MINT`](#47-mint-generated)
88+
- **Wrapped faucet (`is_native = false`):** the bridge emits a [`MINT`](#48-mint-generated)
8889
note targeting the faucet. The faucet consumes the `MINT` note, mints the specified amount,
89-
and creates a [`P2ID`](#46-p2id-generated) note delivering the minted assets to the
90+
and creates a [`P2ID`](#47-p2id-generated) note delivering the minted assets to the
9091
recipient's Miden account.
9192
- **Miden-native faucet (`is_native = true`):** the bridge cannot mint via the faucet, so
9293
it removes the asset from its own vault (`native_account::remove_asset`) and emits a
@@ -107,7 +108,7 @@ TODO: Claims cannot be reversed once the nullifier is set
107108
![GER injection flow](diagrams/ger-injection.png)
108109

109110
Global Exit Roots represent a snapshot of exit tree roots across all AggLayer-connected
110-
chains. A GER Manager observes L1 GER updates and creates [`UPDATE_GER`](#44-update_ger) notes
111+
chains. A GER Manager observes L1 GER updates and creates [`UPDATE_GER`](#45-update_ger) notes
111112
on Miden. The bridge consumes these notes:
112113

113114
1. Asserts the note sender is the designated GER manager.
@@ -165,7 +166,7 @@ The bridge has two administrative roles set at account creation time:
165166

166167
- **Bridge admin** (`admin_account_id`): authorizes faucet registration via
167168
[`CONFIG_AGG_BRIDGE`](#43-config_agg_bridge) notes.
168-
- **GER manager** (`ger_manager_account_id`): authorizes GER updates via [`UPDATE_GER`](#44-update_ger)
169+
- **GER manager** (`ger_manager_account_id`): authorizes GER updates via [`UPDATE_GER`](#45-update_ger)
169170
notes.
170171

171172
Both roles are verified by checking the note sender against the stored account ID.
@@ -186,6 +187,7 @@ The bridge account has a single unified `bridge` component (`components/bridge.m
186187
which is a thin wrapper that re-exports procedures from the `agglayer` library modules:
187188

188189
- `bridge_config::register_faucet`
190+
- `bridge_config::deregister_faucet`
189191
- `bridge_config::update_ger`
190192
- `bridge_in::claim`
191193
- `bridge_out::bridge_out`
@@ -234,6 +236,44 @@ Asserts the note sender matches the bridge admin stored in
234236
the address alone would let a CLAIM bound to one origin network resolve to the faucet of
235237
the same address on another network.
236238

239+
#### `bridge_config::deregister_faucet`
240+
241+
| | |
242+
|-|-|
243+
| **Invocation** | `call` |
244+
| **Inputs** | `[faucet_id_suffix, faucet_id_prefix, pad(14)]` |
245+
| **Outputs** | `[pad(16)]` |
246+
| **Context** | Consuming a `DEREGISTER_AGG_BRIDGE` note on the bridge account |
247+
| **Panics** | Note sender is not the bridge admin; faucet is not currently registered |
248+
249+
Asserts the note sender matches the bridge admin stored in
250+
`agglayer::bridge::admin_account_id` and the faucet is currently registered (via
251+
`assert_faucet_registered`), then clears every entry `register_faucet` wrote for the faucet:
252+
253+
1. Writes `[0, 0, faucet_id_suffix, faucet_id_prefix] -> [0, 0, 0, 0]` into the
254+
`faucet_registry_map` map slot.
255+
2. Reads the faucet's registered `origin_token_addr` and `origin_network` back from
256+
`faucet_metadata_map` (via `get_faucet_conversion_info`), byte-swaps `origin_network`, hashes
257+
`origin_token_addr` (5 felts) concatenated with the swapped network using
258+
`Poseidon2::hash_elements`, and writes `hash(origin_token_addr || origin_network) -> [0, 0, 0, 0]`
259+
into the `token_registry_map` map slot. Reading the address/network from on-chain metadata
260+
(rather than from the note) guarantees the cleared key is byte-identical to the one
261+
`register_faucet` wrote for the faucet's current registration, so a note cannot direct the clear
262+
at the wrong key. (Caveat: `register_faucet` does not clear a faucet's prior `token_registry`
263+
key when the same faucet is re-registered under a different token identity, so a stale token key
264+
can survive deregistration. That stale entry is inert: `claim` re-checks
265+
`assert_faucet_registered` after the token lookup and `bridge_out` already does, so a
266+
deregistered faucet can never mint or unlock through it. Deregistration is an unconditional
267+
revocation.)
268+
3. Clears all four `faucet_metadata_map` sub-keys (origin-address lo/hi, network, scale, and
269+
metadata hash lo/hi) for the faucet, so no stale conversion data is left behind.
270+
271+
After deregistration, both `bridge_out` (which calls `assert_faucet_registered`)
272+
and `claim` (which calls `lookup_faucet_by_token_address`) will fail for any
273+
in-flight notes referencing the deregistered faucet. The bridge admin should
274+
warn users before deregistering a faucet that has unprocessed B2AGG or CLAIM
275+
notes in flight.
276+
237277
#### `bridge_config::update_ger`
238278

239279
| | |
@@ -287,7 +327,7 @@ Validates a bridge-in claim and creates a MINT note targeting the faucet:
287327
7. Verifies the `faucet_mint_amount` against the leaf data's U256 amount and the
288328
faucet's scale factor (via FPI to `agglayer_faucet::get_scale`), using
289329
`asset_conversion::verify_u256_to_native_amount_conversion`.
290-
8. Builds a MINT output note targeting the faucet (see [Section 4.7](#47-mint-generated)).
330+
8. Builds a MINT output note targeting the faucet (see [Section 4.8](#48-mint-generated)).
291331

292332
#### Bridge Account Storage
293333

@@ -598,7 +638,61 @@ two-step registration into `faucet_registry_map` and `token_registry_map`).
598638
| **Issuer** | Bridge admin only -- **enforced** by `bridge_config::register_faucet` procedure |
599639
| **Consumer** | Bridge account -- **enforced** via `NetworkAccountTarget` attachment |
600640

601-
### 4.4 UPDATE_GER
641+
### 4.4 DEREGISTER_AGG_BRIDGE
642+
643+
**Purpose:** Deregisters a faucet from the bridge's faucet and token registries.
644+
645+
**`NoteHeader`**
646+
647+
*`NoteMetadata`:*
648+
649+
| Field | Value |
650+
|-------|-------|
651+
| `sender` | Bridge admin (sender authorization enforced by the bridge's `deregister_faucet` procedure) |
652+
| `note_type` | `NoteType::Public` |
653+
| `tag` | `NoteTag::default()` |
654+
| `attachment` | `NetworkAccountTarget` -- target is the bridge account; execution hint: Always |
655+
656+
**`NoteDetails`**
657+
658+
*`NoteAssets`:* None (empty).
659+
660+
*`NoteRecipient`:*
661+
662+
| Field | Value |
663+
|-------|-------|
664+
| `serial_num` | Random (`rng.draw_word()`) |
665+
| `script` | `deregister_agg_bridge.masm` |
666+
| `storage` | 2 felts -- the faucet id |
667+
668+
**Storage layout (2 felts):**
669+
670+
| Index | Field | Encoding |
671+
|-------|-------|----------|
672+
| 0 | `faucet_id_suffix` | Felt (AccountId suffix of the faucet to deregister) |
673+
| 1 | `faucet_id_prefix` | Felt (AccountId prefix of the faucet to deregister) |
674+
675+
The origin token address and origin network are not carried by the note; the bridge reads them
676+
back from its own `faucet_metadata_map` when clearing the token registry.
677+
678+
**Consumption:** Script validates attachment target, loads storage, and calls
679+
`bridge_config::deregister_faucet` (which asserts sender is bridge admin, asserts
680+
the faucet is currently registered, and clears the `faucet_registry_map`,
681+
`token_registry_map`, and `faucet_metadata_map` entries).
682+
683+
After consumption, in-flight B2AGG / CLAIM notes referencing the deregistered
684+
faucet will fail their `assert_faucet_registered` / `lookup_faucet_by_token_address`
685+
checks. The bridge admin should drain or otherwise warn users about pending
686+
notes before sending a `DEREGISTER_AGG_BRIDGE`.
687+
688+
#### Permissions
689+
690+
| Role | Enforcement |
691+
|------|------------|
692+
| **Issuer** | Bridge admin only -- **enforced** by `bridge_config::deregister_faucet` procedure |
693+
| **Consumer** | Bridge account -- **enforced** via `NetworkAccountTarget` attachment |
694+
695+
### 4.5 UPDATE_GER
602696

603697
**Purpose:** Stores a new Global Exit Root (GER) in the bridge account so that subsequent
604698
CLAIM notes can be verified against it.
@@ -644,7 +738,7 @@ CLAIM notes can be verified against it.
644738
| **Issuer** | GER manager only -- **enforced** by `bridge_config::update_ger` procedure |
645739
| **Consumer** | Bridge account -- **enforced** via `NetworkAccountTarget` attachment |
646740

647-
### 4.5 BURN (generated)
741+
### 4.6 BURN (generated)
648742

649743
**Purpose:** Created by `bridge_out::bridge_out` to burn the bridged asset on the faucet.
650744

@@ -688,7 +782,7 @@ decreases the faucet's total token supply by the burned amount.
688782
| **Issuer** | Bridge account (created by `bridge_out::bridge_out`) |
689783
| **Consumer** | Target faucet only -- **enforced** via `NetworkAccountTarget` attachment |
690784

691-
### 4.6 P2ID (generated)
785+
### 4.7 P2ID (generated)
692786

693787
**Purpose:** Created by the faucet (via `mint_and_send`) when consuming a MINT note, to
694788
deliver minted assets to the recipient.
@@ -736,7 +830,7 @@ script). All note assets are added to the consuming account via
736830
| **Issuer** | Faucet account (created by `mint_and_send`) |
737831
| **Consumer** | Destination account only -- **enforced** by P2ID script (checks `target_account_id`) |
738832

739-
### 4.7 MINT (generated)
833+
### 4.8 MINT (generated)
740834

741835
**Purpose:** Created by `bridge_in::claim` on the bridge account. Consumed by the faucet
742836
to mint and distribute assets to the recipient.
@@ -1111,6 +1205,19 @@ The bridge admin is a trusted role, and is the sole entity that can register fau
11111205
the Miden side (enforced by the caller restriction on
11121206
[`bridge_config::register_faucet`](#bridge_configregister_faucet)).
11131207

1208+
The bridge admin can also revoke a faucet's authorization via a
1209+
[`DEREGISTER_AGG_BRIDGE`](#44-deregister_agg_bridge) note, which carries only the faucet id and
1210+
calls [`bridge_config::deregister_faucet`](#bridge_configderegister_faucet) to clear every entry
1211+
the faucet's current registration wrote: the `faucet_registry_map`, `token_registry_map`, and
1212+
`faucet_metadata_map`. The token-registry key is recomputed from the faucet's own stored metadata,
1213+
so a note cannot direct the clear at the wrong key. Deregistration is an unconditional revocation:
1214+
`claim` re-checks `assert_faucet_registered` after the token lookup, so even a stale token key left
1215+
by a re-registration cannot mint through a deregistered faucet. This is useful for retiring
1216+
compromised, broken, or deprecated faucets without redeploying the bridge. Note that
1217+
in-flight `B2AGG` and `CLAIM` notes referencing the deregistered faucet will fail
1218+
their existing registration checks after deregistration, so users should be warned
1219+
before a deregistration is performed.
1220+
11141221
#### Wrapped (`is_native = false`) vs Miden-native (`is_native = true`) faucets
11151222

11161223
The difference between the two kinds is what they represent and how the bridge dispatches

crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ const REG_SCALE_LOC = 6
4242
const REG_ORIGIN_NETWORK_LOC = 7
4343
const REG_IS_NATIVE_LOC = 8
4444

45+
# Local memory slot offsets used inside the `deregister_faucet` procedure
46+
const DEREG_FAUCET_ID_SUFFIX_LOC = 0
47+
const DEREG_FAUCET_ID_PREFIX_LOC = 1
48+
4549
# faucet_metadata_map sub-keys (used as the first element of the 4-felt KEY).
4650
# Each sub-key indexes a different word of metadata for a given faucet ID.
4751
const FAUCET_METADATA_SUBKEY_ADDR_LO = 0 # [addr0, addr1, addr2, addr3]
@@ -282,6 +286,122 @@ pub proc store_faucet_metadata_hash
282286
# => [pad(16)]
283287
end
284288

289+
#! Deregisters a faucet, clearing every entry `register_faucet` wrote for it.
290+
#!
291+
#! Asserts the faucet is currently registered, then clears it from the `faucet_registry`,
292+
#! `token_registry`, and `faucet_metadata` maps. After this call, subsequent
293+
#! `assert_faucet_registered` and `lookup_faucet_by_token_address` calls panic, so any in-flight
294+
#! notes (B2AGG / CLAIM) targeting the deregistered faucet fail their existing registration checks.
295+
#!
296+
#! The origin token address and origin network are NOT taken from the note: they are read back
297+
#! from the faucet's own `faucet_metadata_map` entry (written by `register_faucet`) via
298+
#! `get_faucet_conversion_info`. This guarantees the `token_registry` key cleared here is exactly
299+
#! the one `register_faucet` wrote for this faucet's *current* registration, so a note cannot point
300+
#! the clear at the wrong key.
301+
#!
302+
#! Caveat: `register_faucet` does not clear a faucet's previous `token_registry` key when the same
303+
#! faucet ID is re-registered under a different (origin token address, origin network). Such a
304+
#! re-registered faucet therefore has a stale `token_registry` entry that this procedure does not
305+
#! reach (it only clears the key derived from the latest metadata). That stale entry is inert,
306+
#! however: `bridge_in::claim` re-checks `assert_faucet_registered` after the token lookup, and
307+
#! `bridge_out` already does, so a deregistered faucet can never mint or unlock even via a stale
308+
#! key. Deregistration is therefore an unconditional revocation of the faucet.
309+
#!
310+
#! 1. Writes `[0, 0, faucet_id_suffix, faucet_id_prefix] -> [0, 0, 0, 0]` into `faucet_registry`.
311+
#! 2. Writes `hash(tokenAddress[5] || origin_network) -> [0, 0, 0, 0]` into `token_registry`, where
312+
#! the address/network come from `faucet_metadata` and origin_network is byte-swapped before
313+
#! hashing (matching `register_faucet` Step 4 and `lookup_faucet_by_token_address`).
314+
#! 3. Clears all four `faucet_metadata` sub-keys (addr-lo, addr-hi/network/scale, hash-lo, hash-hi).
315+
#!
316+
#! Inputs: [faucet_id_suffix, faucet_id_prefix, pad(14)]
317+
#! Outputs: [pad(16)]
318+
#!
319+
#! Panics if:
320+
#! - the note sender is not the bridge admin.
321+
#! - the faucet is not currently registered in the faucet registry.
322+
#!
323+
#! Invocation: call
324+
@locals(2)
325+
pub proc deregister_faucet
326+
# assert the note sender is the bridge admin.
327+
exec.assert_sender_is_bridge_admin
328+
# => [faucet_id_suffix, faucet_id_prefix, pad(14)]
329+
330+
# Stash the faucet ID; it is needed for every map key below.
331+
dup.0 loc_store.DEREG_FAUCET_ID_SUFFIX_LOC
332+
dup.1 loc_store.DEREG_FAUCET_ID_PREFIX_LOC
333+
# => [faucet_id_suffix, faucet_id_prefix, pad(14)]
334+
335+
# --- 1. Assert faucet is currently registered (consumes [suffix, prefix]) ---
336+
exec.assert_faucet_registered
337+
# => [pad(16)]
338+
339+
# --- 2. Clear token_registry ---
340+
# Read the faucet's registered (origin_token_addr, origin_network) from faucet_metadata so the
341+
# cleared key is provably the one register_faucet wrote. Drop the scale we don't need.
342+
loc_load.DEREG_FAUCET_ID_PREFIX_LOC loc_load.DEREG_FAUCET_ID_SUFFIX_LOC
343+
exec.get_faucet_conversion_info
344+
# => [origin_token_addr(5), origin_network, scale, pad(8)]
345+
346+
movup.6 drop
347+
# => [origin_token_addr(5), origin_network, pad(9)]
348+
349+
# Byte-swap origin_network so the hash input matches register_faucet's convention
350+
# (`claim_note.rs` LE-packs the leaf's origin_network felt).
351+
movup.5 exec.utils::swap_u32_bytes movdn.5
352+
# => [origin_token_addr(5), origin_network_swapped, pad(9)]
353+
354+
exec.hash_token_address
355+
# => [TOKEN_ADDR_HASH, pad(12)]
356+
357+
push.0.0.0.0 swapw
358+
# => [TOKEN_ADDR_HASH, 0, 0, 0, 0, pad(12)]
359+
360+
push.TOKEN_REGISTRY_MAP_SLOT[0..2]
361+
exec.native_account::set_map_item
362+
dropw
363+
# => [pad(16)]
364+
365+
# --- 3. Clear faucet_registry: KEY=[0,0,suffix,prefix], VALUE=[0,0,0,0] ---
366+
push.0.0.0.0
367+
loc_load.DEREG_FAUCET_ID_PREFIX_LOC loc_load.DEREG_FAUCET_ID_SUFFIX_LOC push.0.0
368+
# => [[0, 0, suffix, prefix], [0, 0, 0, 0], pad(16)]
369+
push.FAUCET_REGISTRY_MAP_SLOT[0..2]
370+
exec.native_account::set_map_item
371+
dropw
372+
# => [pad(16)]
373+
374+
# --- 4. Clear all four faucet_metadata sub-keys for this faucet ---
375+
# Each sub-key KEY = [sub_key, 0, suffix, prefix] is cleared to VALUE = [0, 0, 0, 0].
376+
push.0.0.0.0
377+
loc_load.DEREG_FAUCET_ID_PREFIX_LOC loc_load.DEREG_FAUCET_ID_SUFFIX_LOC push.0.FAUCET_METADATA_SUBKEY_ADDR_LO
378+
push.FAUCET_METADATA_MAP_SLOT[0..2]
379+
exec.native_account::set_map_item
380+
dropw
381+
# => [pad(16)]
382+
383+
push.0.0.0.0
384+
loc_load.DEREG_FAUCET_ID_PREFIX_LOC loc_load.DEREG_FAUCET_ID_SUFFIX_LOC push.0.FAUCET_METADATA_SUBKEY_ADDR_HI
385+
push.FAUCET_METADATA_MAP_SLOT[0..2]
386+
exec.native_account::set_map_item
387+
dropw
388+
# => [pad(16)]
389+
390+
push.0.0.0.0
391+
loc_load.DEREG_FAUCET_ID_PREFIX_LOC loc_load.DEREG_FAUCET_ID_SUFFIX_LOC push.0.FAUCET_METADATA_SUBKEY_HASH_LO
392+
push.FAUCET_METADATA_MAP_SLOT[0..2]
393+
exec.native_account::set_map_item
394+
dropw
395+
# => [pad(16)]
396+
397+
push.0.0.0.0
398+
loc_load.DEREG_FAUCET_ID_PREFIX_LOC loc_load.DEREG_FAUCET_ID_SUFFIX_LOC push.0.FAUCET_METADATA_SUBKEY_HASH_HI
399+
push.FAUCET_METADATA_MAP_SLOT[0..2]
400+
exec.native_account::set_map_item
401+
dropw
402+
# => [pad(16)]
403+
end
404+
285405
#! Asserts that a faucet is registered in the bridge's faucet registry.
286406
#!
287407
#! Looks up the faucet ID in the faucet registry map and asserts the registration flag is set.

crates/miden-agglayer/asm/agglayer/bridge/bridge_in.masm

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,14 @@ pub proc claim
212212
exec.bridge_config::lookup_faucet_by_token_address
213213
# => [faucet_id_suffix, faucet_id_prefix, pad(16)]
214214

215+
# Assert the resolved faucet is still registered. `lookup_faucet_by_token_address` only proves
216+
# the token route exists, but a faucet that was re-registered under a different token identity
217+
# and then deregistered can leave a stale `token_registry` key pointing at it (`register_faucet`
218+
# does not clear the prior key). Re-checking `faucet_registry` here makes deregistration an
219+
# unconditional revocation: a deregistered faucet can never mint or unlock through CLAIM.
220+
dup.1 dup.1 exec.bridge_config::assert_faucet_registered
221+
# => [faucet_id_suffix, faucet_id_prefix, pad(16)]
222+
215223
dup.1 dup.1
216224
# => [faucet_id_suffix, faucet_id_prefix, faucet_id_suffix, faucet_id_prefix, pad(16)]
217225

0 commit comments

Comments
 (0)