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
27pub 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
50include!(concat!(env!("OUT_DIR"), "/agglayer_constants.rs"));
54
55static 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
98static 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
114static 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#[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 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 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 pub fn bridge_admin_id_slot_name() -> &'static StorageSlotName {
220 &BRIDGE_ADMIN_ID_SLOT_NAME
221 }
222
223 pub fn ger_injector_id_slot_name() -> &'static StorageSlotName {
225 &GER_INJECTOR_ID_SLOT_NAME
226 }
227
228 pub fn ger_remover_id_slot_name() -> &'static StorageSlotName {
230 &GER_REMOVER_ID_SLOT_NAME
231 }
232
233 pub fn ger_map_slot_name() -> &'static StorageSlotName {
235 &GER_MAP_SLOT_NAME
236 }
237
238 pub fn removed_ger_hash_chain_lo_slot_name() -> &'static StorageSlotName {
240 &REMOVED_GER_HASH_CHAIN_LO_SLOT_NAME
241 }
242
243 pub fn removed_ger_hash_chain_hi_slot_name() -> &'static StorageSlotName {
245 &REMOVED_GER_HASH_CHAIN_HI_SLOT_NAME
246 }
247
248 pub fn faucet_registry_map_slot_name() -> &'static StorageSlotName {
250 &FAUCET_REGISTRY_MAP_SLOT_NAME
251 }
252
253 pub fn token_registry_map_slot_name() -> &'static StorageSlotName {
255 &TOKEN_REGISTRY_MAP_SLOT_NAME
256 }
257
258 pub fn faucet_metadata_map_slot_name() -> &'static StorageSlotName {
263 &FAUCET_METADATA_MAP_SLOT_NAME
264 }
265
266 pub fn claim_nullifiers_slot_name() -> &'static StorageSlotName {
270 &CLAIM_NULLIFIERS_SLOT_NAME
271 }
272
273 pub fn cgi_chain_hash_lo_slot_name() -> &'static StorageSlotName {
275 &CGI_CHAIN_HASH_LO_SLOT_NAME
276 }
277
278 pub fn cgi_chain_hash_hi_slot_name() -> &'static StorageSlotName {
280 &CGI_CHAIN_HASH_HI_SLOT_NAME
281 }
282
283 pub fn let_frontier_slot_name() -> &'static StorageSlotName {
287 &LET_FRONTIER_SLOT_NAME
288 }
289
290 pub fn let_root_lo_slot_name() -> &'static StorageSlotName {
292 &LET_ROOT_LO_SLOT_NAME
293 }
294
295 pub fn let_root_hi_slot_name() -> &'static StorageSlotName {
297 &LET_ROOT_HI_SLOT_NAME
298 }
299
300 pub fn let_num_leaves_slot_name() -> &'static StorageSlotName {
302 &LET_NUM_LEAVES_SLOT_NAME
303 }
304
305 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 pub fn is_ger_registered(
334 ger: ExitRoot,
335 bridge_account: &Account,
336 ) -> Result<bool, AgglayerBridgeError> {
337 Self::assert_bridge_account(bridge_account)?;
339
340 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 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 pub fn read_local_exit_root(account: &Account) -> Result<Vec<Felt>, AgglayerBridgeError> {
373 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 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 pub fn cgi_chain_hash(bridge_account: &Account) -> Result<CgiChainHash, AgglayerBridgeError> {
412 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 pub fn removed_ger_hash_chain(
437 bridge_account: &Account,
438 ) -> Result<RemovedGerHashChain, AgglayerBridgeError> {
439 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 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 fn assert_bridge_account(account: &Account) -> Result<(), AgglayerBridgeError> {
479 Self::assert_storage_slots(account)?;
481
482 Self::assert_code_commitment(account)?;
484
485 Ok(())
486 }
487
488 fn assert_storage_slots(account: &Account) -> Result<(), AgglayerBridgeError> {
495 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 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 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 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#[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
597fn 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}