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::{Account, AccountComponent, AccountId, StorageSlot, StorageSlotName};
10use miden_protocol::block::account_tree::AccountIdKey;
11use miden_protocol::crypto::hash::poseidon2::Poseidon2;
12use miden_protocol::note::NoteScriptRoot;
13use miden_utils_sync::LazyLock;
14use thiserror::Error;
15
16use super::agglayer_bridge_component_library;
17use crate::claim_note::CgiChainHash;
18pub use crate::{
19 B2AggNote,
20 ClaimNote,
21 ClaimNoteStorage,
22 ConfigAggBridgeNote,
23 EthAddress,
24 EthAmount,
25 EthAmountError,
26 EthEmbeddedAccountId,
27 ExitRoot,
28 GlobalIndex,
29 GlobalIndexError,
30 LeafData,
31 MetadataHash,
32 ProofData,
33 SmtNode,
34 UpdateGerNote,
35};
36
37include!(concat!(env!("OUT_DIR"), "/agglayer_constants.rs"));
41
42static BRIDGE_ADMIN_ID_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
49 StorageSlotName::new("agglayer::bridge::admin_account_id")
50 .expect("bridge admin account ID storage slot name should be valid")
51});
52static GER_MANAGER_ID_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
53 StorageSlotName::new("agglayer::bridge::ger_manager_account_id")
54 .expect("GER manager account ID storage slot name should be valid")
55});
56static GER_MAP_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
57 StorageSlotName::new("agglayer::bridge::ger_map")
58 .expect("GER map storage slot name should be valid")
59});
60static FAUCET_REGISTRY_MAP_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
61 StorageSlotName::new("agglayer::bridge::faucet_registry_map")
62 .expect("faucet registry map storage slot name should be valid")
63});
64static TOKEN_REGISTRY_MAP_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
65 StorageSlotName::new("agglayer::bridge::token_registry_map")
66 .expect("token registry map storage slot name should be valid")
67});
68static FAUCET_METADATA_MAP_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
69 StorageSlotName::new("agglayer::bridge::faucet_metadata_map")
70 .expect("faucet metadata map storage slot name should be valid")
71});
72static NETWORK_ID_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
73 StorageSlotName::new("agglayer::bridge::network_id")
74 .expect("network ID storage slot name should be valid")
75});
76
77static CLAIM_NULLIFIERS_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
81 StorageSlotName::new("agglayer::bridge::claim_nullifiers")
82 .expect("claim nullifiers storage slot name should be valid")
83});
84static CGI_CHAIN_HASH_LO_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
85 StorageSlotName::new("agglayer::bridge::cgi_chain_hash_lo")
86 .expect("CGI chain hash_lo storage slot name should be valid")
87});
88static CGI_CHAIN_HASH_HI_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
89 StorageSlotName::new("agglayer::bridge::cgi_chain_hash_hi")
90 .expect("CGI chain hash_hi storage slot name should be valid")
91});
92
93static LET_FRONTIER_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
97 StorageSlotName::new("agglayer::bridge::let_frontier")
98 .expect("LET frontier storage slot name should be valid")
99});
100static LET_ROOT_LO_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
101 StorageSlotName::new("agglayer::bridge::let_root_lo")
102 .expect("LET root_lo storage slot name should be valid")
103});
104static LET_ROOT_HI_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
105 StorageSlotName::new("agglayer::bridge::let_root_hi")
106 .expect("LET root_hi storage slot name should be valid")
107});
108static LET_NUM_LEAVES_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
109 StorageSlotName::new("agglayer::bridge::let_num_leaves")
110 .expect("LET num_leaves storage slot name should be valid")
111});
112
113#[derive(Debug, Clone)]
152pub struct AggLayerBridge {
153 bridge_admin_id: AccountId,
154 ger_manager_id: AccountId,
155 network_id: u32,
156}
157
158impl AggLayerBridge {
159 const REGISTERED_GER_MAP_VALUE: Word = Word::new([ONE, ZERO, ZERO, ZERO]);
163
164 pub fn new(bridge_admin_id: AccountId, ger_manager_id: AccountId, network_id: u32) -> Self {
171 Self {
172 bridge_admin_id,
173 ger_manager_id,
174 network_id,
175 }
176 }
177
178 pub fn bridge_admin_id_slot_name() -> &'static StorageSlotName {
185 &BRIDGE_ADMIN_ID_SLOT_NAME
186 }
187
188 pub fn ger_manager_id_slot_name() -> &'static StorageSlotName {
190 &GER_MANAGER_ID_SLOT_NAME
191 }
192
193 pub fn ger_map_slot_name() -> &'static StorageSlotName {
195 &GER_MAP_SLOT_NAME
196 }
197
198 pub fn faucet_registry_map_slot_name() -> &'static StorageSlotName {
200 &FAUCET_REGISTRY_MAP_SLOT_NAME
201 }
202
203 pub fn token_registry_map_slot_name() -> &'static StorageSlotName {
205 &TOKEN_REGISTRY_MAP_SLOT_NAME
206 }
207
208 pub fn faucet_metadata_map_slot_name() -> &'static StorageSlotName {
213 &FAUCET_METADATA_MAP_SLOT_NAME
214 }
215
216 pub fn network_id_slot_name() -> &'static StorageSlotName {
221 &NETWORK_ID_SLOT_NAME
222 }
223
224 pub fn claim_nullifiers_slot_name() -> &'static StorageSlotName {
228 &CLAIM_NULLIFIERS_SLOT_NAME
229 }
230
231 pub fn cgi_chain_hash_lo_slot_name() -> &'static StorageSlotName {
233 &CGI_CHAIN_HASH_LO_SLOT_NAME
234 }
235
236 pub fn cgi_chain_hash_hi_slot_name() -> &'static StorageSlotName {
238 &CGI_CHAIN_HASH_HI_SLOT_NAME
239 }
240
241 pub fn let_frontier_slot_name() -> &'static StorageSlotName {
245 &LET_FRONTIER_SLOT_NAME
246 }
247
248 pub fn let_root_lo_slot_name() -> &'static StorageSlotName {
250 &LET_ROOT_LO_SLOT_NAME
251 }
252
253 pub fn let_root_hi_slot_name() -> &'static StorageSlotName {
255 &LET_ROOT_HI_SLOT_NAME
256 }
257
258 pub fn let_num_leaves_slot_name() -> &'static StorageSlotName {
260 &LET_NUM_LEAVES_SLOT_NAME
261 }
262
263 pub fn allowed_notes() -> BTreeSet<NoteScriptRoot> {
274 BTreeSet::from([
275 ClaimNote::script_root(),
276 B2AggNote::script_root(),
277 ConfigAggBridgeNote::script_root(),
278 UpdateGerNote::script_root(),
279 ])
280 }
281
282 pub fn is_ger_registered(
290 ger: ExitRoot,
291 bridge_account: &Account,
292 ) -> Result<bool, AgglayerBridgeError> {
293 Self::assert_bridge_account(bridge_account)?;
295
296 let ger_lower: Word = ger.to_elements()[0..4].try_into().unwrap();
298 let ger_upper: Word = ger.to_elements()[4..8].try_into().unwrap();
299 let ger_hash = Poseidon2::merge(&[ger_lower, ger_upper]);
300
301 let stored_value = bridge_account
304 .storage()
305 .get_map_item(AggLayerBridge::ger_map_slot_name(), ger_hash)
306 .expect("provided account should have AggLayer Bridge specific storage slots");
307
308 if stored_value == Self::REGISTERED_GER_MAP_VALUE {
309 Ok(true)
310 } else {
311 Ok(false)
312 }
313 }
314
315 pub fn read_local_exit_root(account: &Account) -> Result<Vec<Felt>, AgglayerBridgeError> {
329 Self::assert_bridge_account(account)?;
331
332 let root_lo_slot = AggLayerBridge::let_root_lo_slot_name();
333 let root_hi_slot = AggLayerBridge::let_root_hi_slot_name();
334
335 let root_lo = account
336 .storage()
337 .get_item(root_lo_slot)
338 .expect("should be able to read LET root lo");
339 let root_hi = account
340 .storage()
341 .get_item(root_hi_slot)
342 .expect("should be able to read LET root hi");
343
344 let mut root = Vec::with_capacity(8);
345 root.extend(root_lo.to_vec());
346 root.extend(root_hi.to_vec());
347
348 Ok(root)
349 }
350
351 pub fn network_id(account: &Account) -> Result<u32, AgglayerBridgeError> {
358 Self::assert_bridge_account(account)?;
360
361 let value = account
362 .storage()
363 .get_item(AggLayerBridge::network_id_slot_name())
364 .expect("should be able to read the network ID");
365 let network_id = u32::try_from(value.to_vec()[0].as_canonical_u64())
366 .map_err(|_| AgglayerBridgeError::InvalidNetworkId)?;
367
368 Ok(network_id)
369 }
370
371 pub fn read_let_num_leaves(account: &Account) -> u64 {
373 let num_leaves_slot = AggLayerBridge::let_num_leaves_slot_name();
374 let value = account
375 .storage()
376 .get_item(num_leaves_slot)
377 .expect("should be able to read LET num leaves");
378 value.to_vec()[0].as_canonical_u64()
379 }
380
381 pub fn cgi_chain_hash(bridge_account: &Account) -> Result<CgiChainHash, AgglayerBridgeError> {
388 Self::assert_bridge_account(bridge_account)?;
390
391 let cgi_chain_hash_lo = bridge_account
392 .storage()
393 .get_item(AggLayerBridge::cgi_chain_hash_lo_slot_name())
394 .expect("failed to get CGI hash chain lo slot");
395 let cgi_chain_hash_hi = bridge_account
396 .storage()
397 .get_item(AggLayerBridge::cgi_chain_hash_hi_slot_name())
398 .expect("failed to get CGI hash chain hi slot");
399
400 let cgi_chain_hash_bytes = cgi_chain_hash_lo
401 .iter()
402 .chain(cgi_chain_hash_hi.iter())
403 .flat_map(|felt| {
404 (u32::try_from(felt.as_canonical_u64()).expect("Felt value does not fit into u32"))
405 .to_le_bytes()
406 })
407 .collect::<Vec<u8>>();
408
409 Ok(CgiChainHash::new(
410 cgi_chain_hash_bytes
411 .try_into()
412 .expect("keccak hash should consist of exactly 32 bytes"),
413 ))
414 }
415
416 fn assert_bridge_account(account: &Account) -> Result<(), AgglayerBridgeError> {
428 Self::assert_storage_slots(account)?;
430
431 Self::assert_code_commitment(account)?;
433
434 Ok(())
435 }
436
437 fn assert_storage_slots(account: &Account) -> Result<(), AgglayerBridgeError> {
444 let account_storage_slot_names: Vec<&StorageSlotName> = account
446 .storage()
447 .slots()
448 .iter()
449 .map(|storage_slot| storage_slot.name())
450 .collect::<Vec<&StorageSlotName>>();
451
452 let are_slots_present = Self::slot_names()
454 .iter()
455 .all(|slot_name| account_storage_slot_names.contains(slot_name));
456 if !are_slots_present {
457 return Err(AgglayerBridgeError::StorageSlotsMismatch);
458 }
459
460 Ok(())
461 }
462
463 fn assert_code_commitment(account: &Account) -> Result<(), AgglayerBridgeError> {
472 if BRIDGE_CODE_COMMITMENT != account.code().commitment() {
473 return Err(AgglayerBridgeError::CodeCommitmentMismatch);
474 }
475
476 Ok(())
477 }
478
479 fn slot_names() -> Vec<&'static StorageSlotName> {
481 vec![
482 &*GER_MAP_SLOT_NAME,
483 &*LET_FRONTIER_SLOT_NAME,
484 &*LET_ROOT_LO_SLOT_NAME,
485 &*LET_ROOT_HI_SLOT_NAME,
486 &*LET_NUM_LEAVES_SLOT_NAME,
487 &*FAUCET_REGISTRY_MAP_SLOT_NAME,
488 &*TOKEN_REGISTRY_MAP_SLOT_NAME,
489 &*FAUCET_METADATA_MAP_SLOT_NAME,
490 &*BRIDGE_ADMIN_ID_SLOT_NAME,
491 &*GER_MANAGER_ID_SLOT_NAME,
492 &*CGI_CHAIN_HASH_LO_SLOT_NAME,
493 &*CGI_CHAIN_HASH_HI_SLOT_NAME,
494 &*CLAIM_NULLIFIERS_SLOT_NAME,
495 &*NETWORK_ID_SLOT_NAME,
496 ]
497 }
498}
499
500impl From<AggLayerBridge> for AccountComponent {
501 fn from(bridge: AggLayerBridge) -> Self {
502 let bridge_admin_word = AccountIdKey::new(bridge.bridge_admin_id).as_word();
503 let ger_manager_word = AccountIdKey::new(bridge.ger_manager_id).as_word();
504
505 let bridge_storage_slots = vec![
506 StorageSlot::with_empty_map(GER_MAP_SLOT_NAME.clone()),
507 StorageSlot::with_empty_map(LET_FRONTIER_SLOT_NAME.clone()),
508 StorageSlot::with_value(LET_ROOT_LO_SLOT_NAME.clone(), Word::empty()),
509 StorageSlot::with_value(LET_ROOT_HI_SLOT_NAME.clone(), Word::empty()),
510 StorageSlot::with_value(LET_NUM_LEAVES_SLOT_NAME.clone(), Word::empty()),
511 StorageSlot::with_empty_map(FAUCET_REGISTRY_MAP_SLOT_NAME.clone()),
512 StorageSlot::with_empty_map(TOKEN_REGISTRY_MAP_SLOT_NAME.clone()),
513 StorageSlot::with_empty_map(FAUCET_METADATA_MAP_SLOT_NAME.clone()),
514 StorageSlot::with_value(BRIDGE_ADMIN_ID_SLOT_NAME.clone(), bridge_admin_word),
515 StorageSlot::with_value(GER_MANAGER_ID_SLOT_NAME.clone(), ger_manager_word),
516 StorageSlot::with_value(CGI_CHAIN_HASH_LO_SLOT_NAME.clone(), Word::empty()),
517 StorageSlot::with_value(CGI_CHAIN_HASH_HI_SLOT_NAME.clone(), Word::empty()),
518 StorageSlot::with_empty_map(CLAIM_NULLIFIERS_SLOT_NAME.clone()),
519 StorageSlot::with_value(
520 NETWORK_ID_SLOT_NAME.clone(),
521 Word::new([Felt::from(bridge.network_id), ZERO, ZERO, ZERO]),
522 ),
523 ];
524 bridge_component(bridge_storage_slots)
525 }
526}
527
528#[derive(Debug, Error)]
533pub enum AgglayerBridgeError {
534 #[error(
535 "provided account does not have storage slots required for the AggLayer Bridge account"
536 )]
537 StorageSlotsMismatch,
538 #[error(
539 "the code commitment of the provided account does not match the code commitment of the AggLayer Bridge account"
540 )]
541 CodeCommitmentMismatch,
542 #[error("the network ID stored in the bridge account does not fit into a u32")]
543 InvalidNetworkId,
544}
545
546fn bridge_component(storage_slots: Vec<StorageSlot>) -> AccountComponent {
551 let library = agglayer_bridge_component_library();
552 let metadata = AccountComponentMetadata::new("agglayer::bridge")
553 .with_description("Bridge component for AggLayer");
554
555 AccountComponent::new(library, storage_slots, metadata)
556 .expect("bridge component should satisfy the requirements of a valid account component")
557}