1extern crate alloc;
2
3use alloc::vec;
4use alloc::vec::Vec;
5
6use miden_core::{Felt, ONE, Word, ZERO};
7use miden_protocol::account::component::AccountComponentMetadata;
8use miden_protocol::account::{
9 Account,
10 AccountComponent,
11 AccountId,
12 AccountType,
13 StorageSlot,
14 StorageSlotName,
15};
16use miden_protocol::block::account_tree::AccountIdKey;
17use miden_protocol::crypto::hash::poseidon2::Poseidon2;
18use miden_utils_sync::LazyLock;
19use thiserror::Error;
20
21use super::agglayer_bridge_component_library;
22use crate::claim_note::CgiChainHash;
23pub use crate::{
24 B2AggNote,
25 ClaimNoteStorage,
26 ConfigAggBridgeNote,
27 EthAddress,
28 EthAmount,
29 EthAmountError,
30 EthEmbeddedAccountId,
31 ExitRoot,
32 GlobalIndex,
33 GlobalIndexError,
34 LeafData,
35 MetadataHash,
36 ProofData,
37 SmtNode,
38 UpdateGerNote,
39 create_claim_note,
40};
41
42include!(concat!(env!("OUT_DIR"), "/agglayer_constants.rs"));
46
47static BRIDGE_ADMIN_ID_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
54 StorageSlotName::new("agglayer::bridge::admin_account_id")
55 .expect("bridge admin account ID storage slot name should be valid")
56});
57static GER_MANAGER_ID_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
58 StorageSlotName::new("agglayer::bridge::ger_manager_account_id")
59 .expect("GER manager account ID storage slot name should be valid")
60});
61static GER_MAP_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
62 StorageSlotName::new("agglayer::bridge::ger_map")
63 .expect("GER map storage slot name should be valid")
64});
65static FAUCET_REGISTRY_MAP_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
66 StorageSlotName::new("agglayer::bridge::faucet_registry_map")
67 .expect("faucet registry map storage slot name should be valid")
68});
69static TOKEN_REGISTRY_MAP_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
70 StorageSlotName::new("agglayer::bridge::token_registry_map")
71 .expect("token registry map storage slot name should be valid")
72});
73
74static CLAIM_NULLIFIERS_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
78 StorageSlotName::new("agglayer::bridge::claim_nullifiers")
79 .expect("claim nullifiers storage slot name should be valid")
80});
81static CGI_CHAIN_HASH_LO_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
82 StorageSlotName::new("agglayer::bridge::cgi_chain_hash_lo")
83 .expect("CGI chain hash_lo storage slot name should be valid")
84});
85static CGI_CHAIN_HASH_HI_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
86 StorageSlotName::new("agglayer::bridge::cgi_chain_hash_hi")
87 .expect("CGI chain hash_hi storage slot name should be valid")
88});
89
90static LET_FRONTIER_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
94 StorageSlotName::new("agglayer::bridge::let_frontier")
95 .expect("LET frontier storage slot name should be valid")
96});
97static LET_ROOT_LO_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
98 StorageSlotName::new("agglayer::bridge::let_root_lo")
99 .expect("LET root_lo storage slot name should be valid")
100});
101static LET_ROOT_HI_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
102 StorageSlotName::new("agglayer::bridge::let_root_hi")
103 .expect("LET root_hi storage slot name should be valid")
104});
105static LET_NUM_LEAVES_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
106 StorageSlotName::new("agglayer::bridge::let_num_leaves")
107 .expect("LET num_leaves storage slot name should be valid")
108});
109
110#[derive(Debug, Clone)]
140pub struct AggLayerBridge {
141 bridge_admin_id: AccountId,
142 ger_manager_id: AccountId,
143}
144
145impl AggLayerBridge {
146 const REGISTERED_GER_MAP_VALUE: Word = Word::new([ONE, ZERO, ZERO, ZERO]);
150
151 pub fn new(bridge_admin_id: AccountId, ger_manager_id: AccountId) -> Self {
156 Self { bridge_admin_id, ger_manager_id }
157 }
158
159 pub fn bridge_admin_id_slot_name() -> &'static StorageSlotName {
166 &BRIDGE_ADMIN_ID_SLOT_NAME
167 }
168
169 pub fn ger_manager_id_slot_name() -> &'static StorageSlotName {
171 &GER_MANAGER_ID_SLOT_NAME
172 }
173
174 pub fn ger_map_slot_name() -> &'static StorageSlotName {
176 &GER_MAP_SLOT_NAME
177 }
178
179 pub fn faucet_registry_map_slot_name() -> &'static StorageSlotName {
181 &FAUCET_REGISTRY_MAP_SLOT_NAME
182 }
183
184 pub fn token_registry_map_slot_name() -> &'static StorageSlotName {
186 &TOKEN_REGISTRY_MAP_SLOT_NAME
187 }
188
189 pub fn claim_nullifiers_slot_name() -> &'static StorageSlotName {
193 &CLAIM_NULLIFIERS_SLOT_NAME
194 }
195
196 pub fn cgi_chain_hash_lo_slot_name() -> &'static StorageSlotName {
198 &CGI_CHAIN_HASH_LO_SLOT_NAME
199 }
200
201 pub fn cgi_chain_hash_hi_slot_name() -> &'static StorageSlotName {
203 &CGI_CHAIN_HASH_HI_SLOT_NAME
204 }
205
206 pub fn let_frontier_slot_name() -> &'static StorageSlotName {
210 &LET_FRONTIER_SLOT_NAME
211 }
212
213 pub fn let_root_lo_slot_name() -> &'static StorageSlotName {
215 &LET_ROOT_LO_SLOT_NAME
216 }
217
218 pub fn let_root_hi_slot_name() -> &'static StorageSlotName {
220 &LET_ROOT_HI_SLOT_NAME
221 }
222
223 pub fn let_num_leaves_slot_name() -> &'static StorageSlotName {
225 &LET_NUM_LEAVES_SLOT_NAME
226 }
227
228 pub fn is_ger_registered(
236 ger: ExitRoot,
237 bridge_account: Account,
238 ) -> Result<bool, AgglayerBridgeError> {
239 Self::assert_bridge_account(&bridge_account)?;
241
242 let ger_lower: Word = ger.to_elements()[0..4].try_into().unwrap();
244 let ger_upper: Word = ger.to_elements()[4..8].try_into().unwrap();
245 let ger_hash = Poseidon2::merge(&[ger_lower, ger_upper]);
246
247 let stored_value = bridge_account
250 .storage()
251 .get_map_item(AggLayerBridge::ger_map_slot_name(), ger_hash)
252 .expect("provided account should have AggLayer Bridge specific storage slots");
253
254 if stored_value == Self::REGISTERED_GER_MAP_VALUE {
255 Ok(true)
256 } else {
257 Ok(false)
258 }
259 }
260
261 pub fn read_local_exit_root(account: &Account) -> Result<Vec<Felt>, AgglayerBridgeError> {
275 Self::assert_bridge_account(account)?;
277
278 let root_lo_slot = AggLayerBridge::let_root_lo_slot_name();
279 let root_hi_slot = AggLayerBridge::let_root_hi_slot_name();
280
281 let root_lo = account
282 .storage()
283 .get_item(root_lo_slot)
284 .expect("should be able to read LET root lo");
285 let root_hi = account
286 .storage()
287 .get_item(root_hi_slot)
288 .expect("should be able to read LET root hi");
289
290 let mut root = Vec::with_capacity(8);
291 root.extend(root_lo.to_vec());
292 root.extend(root_hi.to_vec());
293
294 Ok(root)
295 }
296
297 pub fn read_let_num_leaves(account: &Account) -> u64 {
299 let num_leaves_slot = AggLayerBridge::let_num_leaves_slot_name();
300 let value = account
301 .storage()
302 .get_item(num_leaves_slot)
303 .expect("should be able to read LET num leaves");
304 value.to_vec()[0].as_canonical_u64()
305 }
306
307 pub fn cgi_chain_hash(bridge_account: &Account) -> Result<CgiChainHash, AgglayerBridgeError> {
314 Self::assert_bridge_account(bridge_account)?;
316
317 let cgi_chain_hash_lo = bridge_account
318 .storage()
319 .get_item(AggLayerBridge::cgi_chain_hash_lo_slot_name())
320 .expect("failed to get CGI hash chain lo slot");
321 let cgi_chain_hash_hi = bridge_account
322 .storage()
323 .get_item(AggLayerBridge::cgi_chain_hash_hi_slot_name())
324 .expect("failed to get CGI hash chain hi slot");
325
326 let cgi_chain_hash_bytes = cgi_chain_hash_lo
327 .iter()
328 .chain(cgi_chain_hash_hi.iter())
329 .flat_map(|felt| {
330 (u32::try_from(felt.as_canonical_u64()).expect("Felt value does not fit into u32"))
331 .to_le_bytes()
332 })
333 .collect::<Vec<u8>>();
334
335 Ok(CgiChainHash::new(
336 cgi_chain_hash_bytes
337 .try_into()
338 .expect("keccak hash should consist of exactly 32 bytes"),
339 ))
340 }
341
342 fn assert_bridge_account(account: &Account) -> Result<(), AgglayerBridgeError> {
354 Self::assert_storage_slots(account)?;
356
357 Self::assert_code_commitment(account)?;
359
360 Ok(())
361 }
362
363 fn assert_storage_slots(account: &Account) -> Result<(), AgglayerBridgeError> {
370 let account_storage_slot_names: Vec<&StorageSlotName> = account
372 .storage()
373 .slots()
374 .iter()
375 .map(|storage_slot| storage_slot.name())
376 .collect::<Vec<&StorageSlotName>>();
377
378 let are_slots_present = Self::slot_names()
380 .iter()
381 .all(|slot_name| account_storage_slot_names.contains(slot_name));
382 if !are_slots_present {
383 return Err(AgglayerBridgeError::StorageSlotsMismatch);
384 }
385
386 Ok(())
387 }
388
389 fn assert_code_commitment(account: &Account) -> Result<(), AgglayerBridgeError> {
398 if BRIDGE_CODE_COMMITMENT != account.code().commitment() {
399 return Err(AgglayerBridgeError::CodeCommitmentMismatch);
400 }
401
402 Ok(())
403 }
404
405 fn slot_names() -> Vec<&'static StorageSlotName> {
407 vec![
408 &*GER_MAP_SLOT_NAME,
409 &*LET_FRONTIER_SLOT_NAME,
410 &*LET_ROOT_LO_SLOT_NAME,
411 &*LET_ROOT_HI_SLOT_NAME,
412 &*LET_NUM_LEAVES_SLOT_NAME,
413 &*FAUCET_REGISTRY_MAP_SLOT_NAME,
414 &*TOKEN_REGISTRY_MAP_SLOT_NAME,
415 &*BRIDGE_ADMIN_ID_SLOT_NAME,
416 &*GER_MANAGER_ID_SLOT_NAME,
417 &*CGI_CHAIN_HASH_LO_SLOT_NAME,
418 &*CGI_CHAIN_HASH_HI_SLOT_NAME,
419 &*CLAIM_NULLIFIERS_SLOT_NAME,
420 ]
421 }
422}
423
424impl From<AggLayerBridge> for AccountComponent {
425 fn from(bridge: AggLayerBridge) -> Self {
426 let bridge_admin_word = AccountIdKey::new(bridge.bridge_admin_id).as_word();
427 let ger_manager_word = AccountIdKey::new(bridge.ger_manager_id).as_word();
428
429 let bridge_storage_slots = vec![
430 StorageSlot::with_empty_map(GER_MAP_SLOT_NAME.clone()),
431 StorageSlot::with_empty_map(LET_FRONTIER_SLOT_NAME.clone()),
432 StorageSlot::with_value(LET_ROOT_LO_SLOT_NAME.clone(), Word::empty()),
433 StorageSlot::with_value(LET_ROOT_HI_SLOT_NAME.clone(), Word::empty()),
434 StorageSlot::with_value(LET_NUM_LEAVES_SLOT_NAME.clone(), Word::empty()),
435 StorageSlot::with_empty_map(FAUCET_REGISTRY_MAP_SLOT_NAME.clone()),
436 StorageSlot::with_empty_map(TOKEN_REGISTRY_MAP_SLOT_NAME.clone()),
437 StorageSlot::with_value(BRIDGE_ADMIN_ID_SLOT_NAME.clone(), bridge_admin_word),
438 StorageSlot::with_value(GER_MANAGER_ID_SLOT_NAME.clone(), ger_manager_word),
439 StorageSlot::with_value(CGI_CHAIN_HASH_LO_SLOT_NAME.clone(), Word::empty()),
440 StorageSlot::with_value(CGI_CHAIN_HASH_HI_SLOT_NAME.clone(), Word::empty()),
441 StorageSlot::with_empty_map(CLAIM_NULLIFIERS_SLOT_NAME.clone()),
442 ];
443 bridge_component(bridge_storage_slots)
444 }
445}
446
447#[derive(Debug, Error)]
452pub enum AgglayerBridgeError {
453 #[error(
454 "provided account does not have storage slots required for the AggLayer Bridge account"
455 )]
456 StorageSlotsMismatch,
457 #[error(
458 "the code commitment of the provided account does not match the code commitment of the AggLayer Bridge account"
459 )]
460 CodeCommitmentMismatch,
461}
462
463fn bridge_component(storage_slots: Vec<StorageSlot>) -> AccountComponent {
468 let library = agglayer_bridge_component_library();
469 let metadata = AccountComponentMetadata::new("agglayer::bridge", AccountType::all())
470 .with_description("Bridge component for AggLayer");
471
472 AccountComponent::new(library, storage_slots, metadata)
473 .expect("bridge component should satisfy the requirements of a valid account component")
474}