Skip to main content

miden_agglayer/
bridge.rs

1extern crate alloc;
2
3use alloc::collections::BTreeSet;
4use alloc::vec;
5use alloc::vec::Vec;
6
7use miden_core::{Felt, ONE, Word, ZERO};
8use miden_protocol::account::component::AccountComponentMetadata;
9use miden_protocol::account::{
10    Account,
11    AccountComponent,
12    AccountId,
13    StorageMapKey,
14    StorageSlot,
15    StorageSlotName,
16};
17use miden_protocol::block::account_tree::AccountIdKey;
18use miden_protocol::crypto::hash::poseidon2::Poseidon2;
19use miden_protocol::note::NoteScriptRoot;
20use miden_utils_sync::LazyLock;
21use thiserror::Error;
22
23use super::agglayer_bridge_component_library;
24use crate::claim_note::CgiChainHash;
25use crate::utils::Keccak256Output;
26
27/// Removed-GER hash chain representation (32-byte Keccak256 hash)
28pub type RemovedGerHashChain = Keccak256Output;
29pub use crate::{
30    B2AggNote,
31    ClaimNote,
32    ClaimNoteStorage,
33    ConfigAggBridgeNote,
34    DeregisterAggFaucetNote,
35    EthAddress,
36    EthAmount,
37    EthAmountError,
38    EthEmbeddedAccountId,
39    ExitRoot,
40    GlobalIndex,
41    GlobalIndexError,
42    LeafData,
43    MetadataHash,
44    ProofData,
45    RemoveGerNote,
46    SmtNode,
47    UpdateGerNote,
48};
49
50// CONSTANTS
51// ================================================================================================
52// Include the generated agglayer constants
53include!(concat!(env!("OUT_DIR"), "/agglayer_constants.rs"));
54
55// AGGLAYER BRIDGE STRUCT
56// ================================================================================================
57
58// bridge config
59// ------------------------------------------------------------------------------------------------
60
61static BRIDGE_ADMIN_ID_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
62    StorageSlotName::new("agglayer::bridge::admin_account_id")
63        .expect("bridge admin account ID storage slot name should be valid")
64});
65static GER_INJECTOR_ID_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
66    StorageSlotName::new("agglayer::bridge::ger_injector_account_id")
67        .expect("GER injector account ID storage slot name should be valid")
68});
69static GER_REMOVER_ID_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
70    StorageSlotName::new("agglayer::bridge::ger_remover_account_id")
71        .expect("GER remover account ID storage slot name should be valid")
72});
73static GER_MAP_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
74    StorageSlotName::new("agglayer::bridge::ger_map")
75        .expect("GER map storage slot name should be valid")
76});
77static REMOVED_GER_HASH_CHAIN_LO_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
78    StorageSlotName::new("agglayer::bridge::removed_ger_hash_chain_lo")
79        .expect("removed GER hash chain lo storage slot name should be valid")
80});
81static REMOVED_GER_HASH_CHAIN_HI_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
82    StorageSlotName::new("agglayer::bridge::removed_ger_hash_chain_hi")
83        .expect("removed GER hash chain hi storage slot name should be valid")
84});
85static FAUCET_REGISTRY_MAP_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
86    StorageSlotName::new("agglayer::bridge::faucet_registry_map")
87        .expect("faucet registry map storage slot name should be valid")
88});
89static TOKEN_REGISTRY_MAP_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
90    StorageSlotName::new("agglayer::bridge::token_registry_map")
91        .expect("token registry map storage slot name should be valid")
92});
93static FAUCET_METADATA_MAP_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
94    StorageSlotName::new("agglayer::bridge::faucet_metadata_map")
95        .expect("faucet metadata map storage slot name should be valid")
96});
97
98// bridge in
99// ------------------------------------------------------------------------------------------------
100
101static CLAIM_NULLIFIERS_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
102    StorageSlotName::new("agglayer::bridge::claim_nullifiers")
103        .expect("claim nullifiers storage slot name should be valid")
104});
105static CGI_CHAIN_HASH_LO_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
106    StorageSlotName::new("agglayer::bridge::cgi_chain_hash_lo")
107        .expect("CGI chain hash_lo storage slot name should be valid")
108});
109static CGI_CHAIN_HASH_HI_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
110    StorageSlotName::new("agglayer::bridge::cgi_chain_hash_hi")
111        .expect("CGI chain hash_hi storage slot name should be valid")
112});
113
114// bridge out
115// ------------------------------------------------------------------------------------------------
116
117static LET_FRONTIER_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
118    StorageSlotName::new("agglayer::bridge::let_frontier")
119        .expect("LET frontier storage slot name should be valid")
120});
121static LET_ROOT_LO_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
122    StorageSlotName::new("agglayer::bridge::let_root_lo")
123        .expect("LET root_lo storage slot name should be valid")
124});
125static LET_ROOT_HI_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
126    StorageSlotName::new("agglayer::bridge::let_root_hi")
127        .expect("LET root_hi storage slot name should be valid")
128});
129static LET_NUM_LEAVES_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
130    StorageSlotName::new("agglayer::bridge::let_num_leaves")
131        .expect("LET num_leaves storage slot name should be valid")
132});
133
134/// An [`AccountComponent`] implementing the AggLayer Bridge.
135///
136/// It reexports the procedures from `agglayer::bridge`. When linking against this
137/// component, the `agglayer` library must be available to the assembler.
138/// The procedures of this component are:
139/// - `register_faucet`, which registers a faucet in the bridge.
140/// - `deregister_faucet`, which clears a previously-registered faucet from both the faucet registry
141///   and token registry maps.
142/// - `update_ger`, which injects a new GER into the storage map.
143/// - `remove_ger`, which removes a GER from the storage map and folds it into the running
144///   removed-GER keccak256 hash chain.
145/// - `bridge_out`, which bridges an asset out of Miden to the destination network.
146/// - `claim`, which validates a claim against the AggLayer bridge and creates a MINT note for the
147///   AggLayer Faucet.
148///
149/// ## Storage Layout
150///
151/// - [`Self::bridge_admin_id_slot_name`]: Stores the bridge admin account ID.
152/// - [`Self::ger_injector_id_slot_name`]: Stores the GER injector account ID.
153/// - [`Self::ger_remover_id_slot_name`]: Stores the GER remover account ID.
154/// - [`Self::ger_map_slot_name`]: Stores the GERs.
155/// - [`Self::removed_ger_hash_chain_lo_slot_name`]: Stores the lower 128 bits of the removed-GER
156///   keccak256 hash chain.
157/// - [`Self::removed_ger_hash_chain_hi_slot_name`]: Stores the upper 128 bits of the removed-GER
158///   keccak256 hash chain.
159/// - [`Self::faucet_registry_map_slot_name`]: Stores the faucet registry map.
160/// - [`Self::token_registry_map_slot_name`]: Stores the token address → faucet ID map.
161/// - [`Self::faucet_metadata_map_slot_name`]: Stores conversion metadata (origin address, origin
162///   network, scale, metadata hash) for all registered faucets, keyed by sub-key scheme based on
163///   faucet ID.
164/// - [`Self::claim_nullifiers_slot_name`]: Stores the CLAIM note nullifiers map (RPO(leaf_index,
165///   source_bridge_network) → \[1, 0, 0, 0\]).
166/// - [`Self::cgi_chain_hash_lo_slot_name`]: Stores the lower 128 bits of the CGI chain hash.
167/// - [`Self::cgi_chain_hash_hi_slot_name`]: Stores the upper 128 bits of the CGI chain hash.
168/// - [`Self::let_frontier_slot_name`]: Stores the Local Exit Tree (LET) frontier.
169/// - [`Self::let_root_lo_slot_name`]: Stores the lower 128 bits of the LET root.
170/// - [`Self::let_root_hi_slot_name`]: Stores the upper 128 bits of the LET root.
171/// - [`Self::let_num_leaves_slot_name`]: Stores the number of leaves in the LET frontier.
172///
173/// The bridge starts with an empty faucet registry; faucets are registered at runtime via
174/// CONFIG_AGG_BRIDGE notes and can be removed via DEREGISTER_AGG_FAUCET notes.
175///
176/// Claim validation compares the leaf's `destination_network` to the global MASM constant
177/// `agglayer::common::constants::MIDEN_NETWORK_ID`. Rust exposes the same value as
178/// [`Self::MIDEN_NETWORK_ID`] from generated `agglayer_constants.rs` file.
179#[derive(Debug, Clone)]
180pub struct AggLayerBridge {
181    bridge_admin_id: AccountId,
182    ger_injector_id: AccountId,
183    ger_remover_id: AccountId,
184}
185
186impl AggLayerBridge {
187    // CONSTANTS
188    // --------------------------------------------------------------------------------------------
189
190    /// AggLayer-assigned network ID for this Miden chain.
191    ///
192    /// Matches `const MIDEN_NETWORK_ID` in `asm/agglayer/common/constants.masm`.
193    pub const MIDEN_NETWORK_ID: u32 = MIDEN_NETWORK_ID;
194
195    const REGISTERED_GER_MAP_VALUE: Word = Word::new([ONE, ZERO, ZERO, ZERO]);
196
197    // CONSTRUCTORS
198    // --------------------------------------------------------------------------------------------
199
200    /// Creates a new AggLayer bridge component with the standard configuration.
201    pub fn new(
202        bridge_admin_id: AccountId,
203        ger_injector_id: AccountId,
204        ger_remover_id: AccountId,
205    ) -> Self {
206        Self {
207            bridge_admin_id,
208            ger_injector_id,
209            ger_remover_id,
210        }
211    }
212
213    // PUBLIC ACCESSORS
214    // --------------------------------------------------------------------------------------------
215
216    // --- bridge config ----
217
218    /// Storage slot name for the bridge admin account ID.
219    pub fn bridge_admin_id_slot_name() -> &'static StorageSlotName {
220        &BRIDGE_ADMIN_ID_SLOT_NAME
221    }
222
223    /// Storage slot name for the GER injector account ID.
224    pub fn ger_injector_id_slot_name() -> &'static StorageSlotName {
225        &GER_INJECTOR_ID_SLOT_NAME
226    }
227
228    /// Storage slot name for the GER remover account ID.
229    pub fn ger_remover_id_slot_name() -> &'static StorageSlotName {
230        &GER_REMOVER_ID_SLOT_NAME
231    }
232
233    /// Storage slot name for the GERs map.
234    pub fn ger_map_slot_name() -> &'static StorageSlotName {
235        &GER_MAP_SLOT_NAME
236    }
237
238    /// Storage slot name for the lower 128 bits of the removed-GER keccak256 hash chain.
239    pub fn removed_ger_hash_chain_lo_slot_name() -> &'static StorageSlotName {
240        &REMOVED_GER_HASH_CHAIN_LO_SLOT_NAME
241    }
242
243    /// Storage slot name for the upper 128 bits of the removed-GER keccak256 hash chain.
244    pub fn removed_ger_hash_chain_hi_slot_name() -> &'static StorageSlotName {
245        &REMOVED_GER_HASH_CHAIN_HI_SLOT_NAME
246    }
247
248    /// Storage slot name for the faucet registry map.
249    pub fn faucet_registry_map_slot_name() -> &'static StorageSlotName {
250        &FAUCET_REGISTRY_MAP_SLOT_NAME
251    }
252
253    /// Storage slot name for the token registry map.
254    pub fn token_registry_map_slot_name() -> &'static StorageSlotName {
255        &TOKEN_REGISTRY_MAP_SLOT_NAME
256    }
257
258    /// Storage slot name for the faucet metadata map.
259    ///
260    /// This map stores conversion metadata (origin address, origin network, scale, metadata hash)
261    /// for all registered faucets, keyed by sub-key scheme based on faucet ID.
262    pub fn faucet_metadata_map_slot_name() -> &'static StorageSlotName {
263        &FAUCET_METADATA_MAP_SLOT_NAME
264    }
265
266    // --- bridge in --------
267
268    /// Storage slot name for the CLAIM note nullifiers map.
269    pub fn claim_nullifiers_slot_name() -> &'static StorageSlotName {
270        &CLAIM_NULLIFIERS_SLOT_NAME
271    }
272
273    /// Storage slot name for the lower 128 bits of the CGI chain hash.
274    pub fn cgi_chain_hash_lo_slot_name() -> &'static StorageSlotName {
275        &CGI_CHAIN_HASH_LO_SLOT_NAME
276    }
277
278    /// Storage slot name for the upper 128 bits of the CGI chain hash.
279    pub fn cgi_chain_hash_hi_slot_name() -> &'static StorageSlotName {
280        &CGI_CHAIN_HASH_HI_SLOT_NAME
281    }
282
283    // --- bridge out -------
284
285    /// Storage slot name for the Local Exit Tree (LET) frontier.
286    pub fn let_frontier_slot_name() -> &'static StorageSlotName {
287        &LET_FRONTIER_SLOT_NAME
288    }
289
290    /// Storage slot name for the lower 32 bits of the LET root.
291    pub fn let_root_lo_slot_name() -> &'static StorageSlotName {
292        &LET_ROOT_LO_SLOT_NAME
293    }
294
295    /// Storage slot name for the upper 32 bits of the LET root.
296    pub fn let_root_hi_slot_name() -> &'static StorageSlotName {
297        &LET_ROOT_HI_SLOT_NAME
298    }
299
300    /// Storage slot name for the number of leaves in the LET frontier.
301    pub fn let_num_leaves_slot_name() -> &'static StorageSlotName {
302        &LET_NUM_LEAVES_SLOT_NAME
303    }
304
305    // ALLOWED NOTES
306    // --------------------------------------------------------------------------------------------
307
308    /// Returns the set of input-note script roots that AggLayer bridge accounts accept.
309    ///
310    /// The bridge's [`AuthNetworkAccount`] component is initialized with this allowlist, which
311    /// means any transaction consuming a note outside this set is rejected before reaching
312    /// `output_note::create`.
313    ///
314    /// [`AuthNetworkAccount`]: miden_standards::account::auth::AuthNetworkAccount
315    pub fn allowed_notes() -> BTreeSet<NoteScriptRoot> {
316        BTreeSet::from([
317            ClaimNote::script_root(),
318            B2AggNote::script_root(),
319            ConfigAggBridgeNote::script_root(),
320            DeregisterAggFaucetNote::script_root(),
321            UpdateGerNote::script_root(),
322            RemoveGerNote::script_root(),
323        ])
324    }
325
326    /// Returns a boolean indicating whether the provided GER is present in storage of the provided
327    /// bridge account.
328    ///
329    /// # Errors
330    ///
331    /// Returns an error if:
332    /// - the provided account is not an [`AggLayerBridge`] account.
333    pub fn is_ger_registered(
334        ger: ExitRoot,
335        bridge_account: &Account,
336    ) -> Result<bool, AgglayerBridgeError> {
337        // check that the provided account is a bridge account
338        Self::assert_bridge_account(bridge_account)?;
339
340        // Compute the expected GER hash: poseidon2::merge(GER_LOWER, GER_UPPER)
341        let ger_lower: Word = ger.to_elements()[0..4].try_into().unwrap();
342        let ger_upper: Word = ger.to_elements()[4..8].try_into().unwrap();
343        let ger_hash = Poseidon2::merge(&[ger_lower, ger_upper]);
344
345        // Get the value stored by the GER hash. If this GER was registered, the value would be
346        // equal to [1, 0, 0, 0]
347        let stored_value = bridge_account
348            .storage()
349            .get_map_item(AggLayerBridge::ger_map_slot_name(), StorageMapKey::from_raw(ger_hash))
350            .expect("provided account should have AggLayer Bridge specific storage slots");
351
352        if stored_value == Self::REGISTERED_GER_MAP_VALUE {
353            Ok(true)
354        } else {
355            Ok(false)
356        }
357    }
358
359    /// Reads the Local Exit Root (double-word) from the bridge account's storage.
360    ///
361    /// The Local Exit Root is stored in two dedicated value slots:
362    /// - [`AggLayerBridge::let_root_lo_slot_name`] — low word of the root
363    /// - [`AggLayerBridge::let_root_hi_slot_name`] — high word of the root
364    ///
365    /// Returns the 256-bit root as 8 `Felt`s: first the 4 elements of `root_lo`, followed by the 4
366    /// elements of `root_hi`. For an empty/uninitialized tree, all elements are zeros.
367    ///
368    /// # Errors
369    ///
370    /// Returns an error if:
371    /// - the provided account is not an [`AggLayerBridge`] account.
372    pub fn read_local_exit_root(account: &Account) -> Result<Vec<Felt>, AgglayerBridgeError> {
373        // check that the provided account is a bridge account
374        Self::assert_bridge_account(account)?;
375
376        let root_lo_slot = AggLayerBridge::let_root_lo_slot_name();
377        let root_hi_slot = AggLayerBridge::let_root_hi_slot_name();
378
379        let root_lo = account
380            .storage()
381            .get_item(root_lo_slot)
382            .expect("should be able to read LET root lo");
383        let root_hi = account
384            .storage()
385            .get_item(root_hi_slot)
386            .expect("should be able to read LET root hi");
387
388        let mut root = Vec::with_capacity(8);
389        root.extend(root_lo.to_vec());
390        root.extend(root_hi.to_vec());
391
392        Ok(root)
393    }
394
395    /// Returns the number of leaves in the Local Exit Tree (LET) frontier.
396    pub fn read_let_num_leaves(account: &Account) -> u64 {
397        let num_leaves_slot = AggLayerBridge::let_num_leaves_slot_name();
398        let value = account
399            .storage()
400            .get_item(num_leaves_slot)
401            .expect("should be able to read LET num leaves");
402        value.to_vec()[0].as_canonical_u64()
403    }
404
405    /// Returns the claimed global index (CGI) chain hash from the corresponding storage slot.
406    ///
407    /// # Errors
408    ///
409    /// Returns an error if:
410    /// - the provided account is not an [`AggLayerBridge`] account.
411    pub fn cgi_chain_hash(bridge_account: &Account) -> Result<CgiChainHash, AgglayerBridgeError> {
412        // check that the provided account is a bridge account
413        Self::assert_bridge_account(bridge_account)?;
414
415        let cgi_chain_hash_lo = bridge_account
416            .storage()
417            .get_item(AggLayerBridge::cgi_chain_hash_lo_slot_name())
418            .expect("failed to get CGI hash chain lo slot");
419        let cgi_chain_hash_hi = bridge_account
420            .storage()
421            .get_item(AggLayerBridge::cgi_chain_hash_hi_slot_name())
422            .expect("failed to get CGI hash chain hi slot");
423
424        Ok(CgiChainHash::new(Self::chain_hash_bytes(cgi_chain_hash_lo, cgi_chain_hash_hi)))
425    }
426
427    /// Returns the removed-GER keccak256 hash chain from the corresponding storage slots.
428    ///
429    /// The chain is the running keccak256 of all removed GERs:
430    /// `chain_n = keccak256(chain_{n-1} || removed_ger_n)` with `chain_0 = 0...0`.
431    ///
432    /// # Errors
433    ///
434    /// Returns an error if:
435    /// - the provided account is not an [`AggLayerBridge`] account.
436    pub fn removed_ger_hash_chain(
437        bridge_account: &Account,
438    ) -> Result<RemovedGerHashChain, AgglayerBridgeError> {
439        // check that the provided account is a bridge account
440        Self::assert_bridge_account(bridge_account)?;
441
442        let chain_lo = bridge_account
443            .storage()
444            .get_item(AggLayerBridge::removed_ger_hash_chain_lo_slot_name())
445            .expect("failed to get removed GER hash chain lo slot");
446        let chain_hi = bridge_account
447            .storage()
448            .get_item(AggLayerBridge::removed_ger_hash_chain_hi_slot_name())
449            .expect("failed to get removed GER hash chain hi slot");
450
451        Ok(RemovedGerHashChain::new(Self::chain_hash_bytes(chain_lo, chain_hi)))
452    }
453
454    // HELPER FUNCTIONS
455    // --------------------------------------------------------------------------------------------
456
457    /// Converts a keccak256 hash stored across two lo/hi storage words into its 32-byte form.
458    fn chain_hash_bytes(lo: Word, hi: Word) -> [u8; 32] {
459        lo.iter()
460            .chain(hi.iter())
461            .flat_map(|felt| {
462                (u32::try_from(felt.as_canonical_u64()).expect("Felt value does not fit into u32"))
463                    .to_le_bytes()
464            })
465            .collect::<Vec<u8>>()
466            .try_into()
467            .expect("keccak hash should consist of exactly 32 bytes")
468    }
469
470    /// Checks that the provided account is an [`AggLayerBridge`] account.
471    ///
472    /// # Errors
473    ///
474    /// Returns an error if:
475    /// - the provided account does not have all AggLayer Bridge specific storage slots.
476    /// - the code commitment of the provided account does not match the code commitment of the
477    ///   [`AggLayerBridge`].
478    fn assert_bridge_account(account: &Account) -> Result<(), AgglayerBridgeError> {
479        // check that the storage slots are as expected
480        Self::assert_storage_slots(account)?;
481
482        // check that the code commitment matches the code commitment of the bridge account
483        Self::assert_code_commitment(account)?;
484
485        Ok(())
486    }
487
488    /// Checks that the provided account has all storage slots required for the [`AggLayerBridge`].
489    ///
490    /// # Errors
491    ///
492    /// Returns an error if:
493    /// - provided account does not have all AggLayer Bridge specific storage slots.
494    fn assert_storage_slots(account: &Account) -> Result<(), AgglayerBridgeError> {
495        // get the storage slot names of the provided account
496        let account_storage_slot_names: Vec<&StorageSlotName> = account
497            .storage()
498            .slots()
499            .iter()
500            .map(|storage_slot| storage_slot.name())
501            .collect::<Vec<&StorageSlotName>>();
502
503        // check that all bridge specific storage slots are presented in the provided account
504        let are_slots_present = Self::slot_names()
505            .iter()
506            .all(|slot_name| account_storage_slot_names.contains(slot_name));
507        if !are_slots_present {
508            return Err(AgglayerBridgeError::StorageSlotsMismatch);
509        }
510
511        Ok(())
512    }
513
514    /// Checks that the code commitment of the provided account matches the code commitment of the
515    /// [`AggLayerBridge`].
516    ///
517    /// # Errors
518    ///
519    /// Returns an error if:
520    /// - the code commitment of the provided account does not match the code commitment of the
521    ///   [`AggLayerBridge`].
522    fn assert_code_commitment(account: &Account) -> Result<(), AgglayerBridgeError> {
523        if BRIDGE_CODE_COMMITMENT != account.code().commitment() {
524            return Err(AgglayerBridgeError::CodeCommitmentMismatch);
525        }
526
527        Ok(())
528    }
529
530    /// Returns a vector of all [`AggLayerBridge`] storage slot names.
531    fn slot_names() -> Vec<&'static StorageSlotName> {
532        vec![
533            &*GER_MAP_SLOT_NAME,
534            &*LET_FRONTIER_SLOT_NAME,
535            &*LET_ROOT_LO_SLOT_NAME,
536            &*LET_ROOT_HI_SLOT_NAME,
537            &*LET_NUM_LEAVES_SLOT_NAME,
538            &*FAUCET_REGISTRY_MAP_SLOT_NAME,
539            &*TOKEN_REGISTRY_MAP_SLOT_NAME,
540            &*FAUCET_METADATA_MAP_SLOT_NAME,
541            &*BRIDGE_ADMIN_ID_SLOT_NAME,
542            &*GER_INJECTOR_ID_SLOT_NAME,
543            &*GER_REMOVER_ID_SLOT_NAME,
544            &*REMOVED_GER_HASH_CHAIN_LO_SLOT_NAME,
545            &*REMOVED_GER_HASH_CHAIN_HI_SLOT_NAME,
546            &*CGI_CHAIN_HASH_LO_SLOT_NAME,
547            &*CGI_CHAIN_HASH_HI_SLOT_NAME,
548            &*CLAIM_NULLIFIERS_SLOT_NAME,
549        ]
550    }
551}
552
553impl From<AggLayerBridge> for AccountComponent {
554    fn from(bridge: AggLayerBridge) -> Self {
555        let bridge_admin_word = AccountIdKey::new(bridge.bridge_admin_id).as_word();
556        let ger_injector_word = AccountIdKey::new(bridge.ger_injector_id).as_word();
557        let ger_remover_word = AccountIdKey::new(bridge.ger_remover_id).as_word();
558
559        let bridge_storage_slots = vec![
560            StorageSlot::with_empty_map(GER_MAP_SLOT_NAME.clone()),
561            StorageSlot::with_empty_map(LET_FRONTIER_SLOT_NAME.clone()),
562            StorageSlot::with_value(LET_ROOT_LO_SLOT_NAME.clone(), Word::empty()),
563            StorageSlot::with_value(LET_ROOT_HI_SLOT_NAME.clone(), Word::empty()),
564            StorageSlot::with_value(LET_NUM_LEAVES_SLOT_NAME.clone(), Word::empty()),
565            StorageSlot::with_empty_map(FAUCET_REGISTRY_MAP_SLOT_NAME.clone()),
566            StorageSlot::with_empty_map(TOKEN_REGISTRY_MAP_SLOT_NAME.clone()),
567            StorageSlot::with_empty_map(FAUCET_METADATA_MAP_SLOT_NAME.clone()),
568            StorageSlot::with_value(BRIDGE_ADMIN_ID_SLOT_NAME.clone(), bridge_admin_word),
569            StorageSlot::with_value(GER_INJECTOR_ID_SLOT_NAME.clone(), ger_injector_word),
570            StorageSlot::with_value(GER_REMOVER_ID_SLOT_NAME.clone(), ger_remover_word),
571            StorageSlot::with_value(REMOVED_GER_HASH_CHAIN_LO_SLOT_NAME.clone(), Word::empty()),
572            StorageSlot::with_value(REMOVED_GER_HASH_CHAIN_HI_SLOT_NAME.clone(), Word::empty()),
573            StorageSlot::with_value(CGI_CHAIN_HASH_LO_SLOT_NAME.clone(), Word::empty()),
574            StorageSlot::with_value(CGI_CHAIN_HASH_HI_SLOT_NAME.clone(), Word::empty()),
575            StorageSlot::with_empty_map(CLAIM_NULLIFIERS_SLOT_NAME.clone()),
576        ];
577        bridge_component(bridge_storage_slots)
578    }
579}
580
581// AGGLAYER BRIDGE ERROR
582// ================================================================================================
583
584/// AggLayer Bridge related errors.
585#[derive(Debug, Error)]
586pub enum AgglayerBridgeError {
587    #[error(
588        "provided account does not have storage slots required for the AggLayer Bridge account"
589    )]
590    StorageSlotsMismatch,
591    #[error(
592        "the code commitment of the provided account does not match the code commitment of the AggLayer Bridge account"
593    )]
594    CodeCommitmentMismatch,
595}
596
597// HELPER FUNCTIONS
598// ================================================================================================
599
600/// Creates an AggLayer Bridge component with the specified storage slots.
601fn bridge_component(storage_slots: Vec<StorageSlot>) -> AccountComponent {
602    let library = agglayer_bridge_component_library();
603    let metadata = AccountComponentMetadata::new("agglayer::bridge")
604        .with_description("Bridge component for AggLayer");
605
606    AccountComponent::new(library, storage_slots, metadata)
607        .expect("bridge component should satisfy the requirements of a valid account component")
608}