1#![no_std]
2
3extern crate alloc;
4
5use miden_core::Word;
6use miden_protocol::account::{AccountBuilder, AccountId};
7use miden_protocol::assembly::Path;
8use miden_protocol::asset::{AssetAmount, TokenSymbol};
9use miden_protocol::note::NoteScript;
10use miden_protocol::vm::Package;
11use miden_standards::account::access::{
12 Authority,
13 Ownable2Step,
14 Pausable,
15 PausableManager,
16 RoleBasedAccessControl,
17 RoleConfig,
18};
19use miden_standards::account::auth::NetworkAccount;
20use miden_standards::account::faucets::{FungibleFaucet, TokenName};
21use miden_standards::account::fees::{
22 BasicConstantFeePolicy,
23 ConstantFeeManager,
24 FeePolicyManager,
25};
26use miden_standards::account::policies::{BurnPolicy, MintPolicy, TokenPolicyManager};
27use miden_utils_sync::LazyLock;
28
29pub mod agglayer_note;
30pub mod b2agg_note;
31pub mod bridge;
32pub mod claim_note;
33pub mod config_note;
34pub mod costs;
35pub mod deregister_note;
36pub mod errors;
37pub mod eth_types;
38pub mod faucet;
39mod ger_note;
40pub mod remove_ger_note;
41#[cfg(any(feature = "testing", test))]
42pub mod testing;
43pub mod update_ger_note;
44pub mod utils;
45
46pub use agglayer_note::AgglayerNote;
47pub use b2agg_note::B2AggNote;
48pub use bridge::{AggLayerBridge, AgglayerBridgeError, BridgeRoles, RemovedGerHashChain};
49pub use claim_note::{
50 CgiChainHash,
51 ClaimNote,
52 ClaimNoteStorage,
53 ExitRoot,
54 LeafData,
55 LeafValue,
56 ProofData,
57 SmtNode,
58};
59pub use config_note::{ConfigAggBridgeNote, ConversionMetadata};
60pub use deregister_note::DeregisterAggFaucetNote;
61#[cfg(any(test, feature = "testing"))]
62pub use eth_types::GlobalIndexExt;
63pub use eth_types::{GlobalIndex, GlobalIndexError, MetadataHash};
64pub use faucet::AggLayerFaucet;
65pub use remove_ger_note::RemoveGerNote;
66pub use update_ger_note::UpdateGerNote;
67pub use utils::Keccak256Output;
68
69static AGGLAYER_PACKAGE: LazyLock<Package> = LazyLock::new(|| {
73 let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/miden-agglayer.masp"));
74 Package::read_from_bytes_trusted(bytes).expect("shipped AggLayer package is well-formed")
75});
76
77static BRIDGE_COMPONENT_PACKAGE: LazyLock<Package> = LazyLock::new(|| {
78 let bytes =
79 include_bytes!(concat!(env!("OUT_DIR"), "/assets/components/miden-agglayer-bridge.masp"));
80 Package::read_from_bytes_trusted(bytes)
81 .expect("shipped bridge component package is well-formed")
82});
83
84pub fn agglayer_package() -> Package {
95 AGGLAYER_PACKAGE.clone()
96}
97
98pub(crate) fn note_script(path: &str) -> NoteScript {
103 NoteScript::from_package_reference(&AGGLAYER_PACKAGE, Path::new(path))
104 .expect("agglayer package contains the note script procedure")
105}
106
107fn agglayer_bridge_component_package() -> Package {
109 BRIDGE_COMPONENT_PACKAGE.clone()
110}
111
112impl AggLayerBridge {
116 pub fn account_builder(
122 seed: Word,
123 bridge_admin: AccountId,
124 roles: BridgeRoles,
125 network_id: u32,
126 fee_faucet_id: AccountId,
127 fee_policy: BasicConstantFeePolicy,
128 ) -> AccountBuilder {
129 let fee_policy_manager = FeePolicyManager::builder()
130 .fee_faucet_id(fee_faucet_id)
131 .active_fee_policy(fee_policy.into())
132 .build();
133 NetworkAccount::builder(seed.into(), AggLayerBridge::allowed_notes(), fee_policy_manager)
134 .expect("bridge note allowlist is non-empty")
135 .with_component(AggLayerBridge::new(network_id))
136 .with_component(
137 RoleBasedAccessControl::builder()
138 .role(
139 RoleConfig::new(RoleBasedAccessControl::admin_role())
140 .with_member(bridge_admin),
141 )
142 .roles(roles)
143 .build()
144 .expect("the bridge seeds distinct non-empty roles administered by ADMIN"),
145 )
146 .with_component(Authority::RbacControlled {
147 procedure_roles: AggLayerBridge::procedure_roles(),
148 })
149 .with_component(Pausable::unpaused())
150 .with_component(PausableManager)
151 .with_component(ConstantFeeManager::for_basic_constant_fee_policy())
152 }
153}
154
155impl AggLayerFaucet {
156 #[allow(clippy::too_many_arguments)]
179 pub fn account_builder(
180 seed: Word,
181 token_name: TokenName,
182 token_symbol: TokenSymbol,
183 decimals: u8,
184 max_supply: AssetAmount,
185 initial_supply: AssetAmount,
186 faucet_admin: AccountId,
187 fee_manager: AccountId,
188 bridge_account_id: AccountId,
189 fee_faucet_id: AccountId,
190 fee_policy: BasicConstantFeePolicy,
191 ) -> AccountBuilder {
192 let fee_policy_manager = FeePolicyManager::builder()
193 .fee_faucet_id(fee_faucet_id)
194 .active_fee_policy(fee_policy.into())
195 .build();
196 let faucet = FungibleFaucet::builder()
197 .name(token_name)
198 .symbol(token_symbol)
199 .decimals(decimals)
200 .max_supply(max_supply)
201 .token_supply(initial_supply)
202 .build()
203 .expect("agglayer faucet decimals and supplies should be within their valid ranges");
204
205 let token_policy_manager = TokenPolicyManager::builder()
206 .active_mint_policy(MintPolicy::owner_only())
207 .active_burn_policy(BurnPolicy::owner_only())
208 .build();
209
210 let rbac = RoleBasedAccessControl::builder()
211 .role(RoleConfig::new(RoleBasedAccessControl::admin_role()).with_member(faucet_admin))
212 .role(RoleConfig::new(AggLayerFaucet::fee_manager_role()).with_member(fee_manager))
213 .build()
214 .expect("the faucet seeds non-empty roles administered by ADMIN");
215
216 NetworkAccount::builder(seed.into(), AggLayerFaucet::allowed_notes(), fee_policy_manager)
217 .expect("faucet note allowlist is non-empty")
218 .with_component(faucet)
219 .with_component(Ownable2Step::new(bridge_account_id))
220 .with_component(rbac)
221 .with_component(Authority::RbacControlled {
222 procedure_roles: AggLayerFaucet::procedure_roles(),
223 })
224 .with_components(token_policy_manager)
225 .with_component(ConstantFeeManager::for_basic_constant_fee_policy())
226 }
227}
228
229#[cfg(test)]
233mod tests {
234 use miden_core::Felt;
235 use miden_protocol::account::AssetCallbackFlag;
236 use miden_protocol::asset::AssetCallbacks;
237 use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE;
238 use miden_standards::tx_script::ExpirationTransactionScript;
239
240 use super::*;
241 use crate::testing::{
242 create_existing_agglayer_faucet,
243 create_existing_bridge_account_with_roles,
244 };
245
246 #[test]
249 fn agglayer_faucet_has_asset_callbacks_disabled() {
250 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
251
252 let faucet = create_existing_agglayer_faucet(
253 Word::default(),
254 "AggLayer Token",
255 "AGG",
256 6,
257 Felt::from(1000u32),
258 Felt::ZERO,
259 id,
260 id,
261 );
262
263 for slot_name in AssetCallbacks::slot_names() {
264 assert!(
265 faucet.storage().get(slot_name).is_none(),
266 "faucet should not install the {slot_name} callback slot"
267 );
268 }
269 assert_eq!(faucet.id().asset_callback_flag(), AssetCallbackFlag::Disabled);
270 }
271
272 #[test]
275 fn agglayer_accounts_allowlist_expiration_tx_script() {
276 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
277
278 let bridge =
279 create_existing_bridge_account_with_roles(Word::default(), id, id, id, id, id, id, 77);
280 let faucet = create_existing_agglayer_faucet(
281 Word::default(),
282 "AggLayer Token",
283 "AGG",
284 6,
285 Felt::from(1000u32),
286 Felt::ZERO,
287 id,
288 id,
289 );
290
291 for account in [bridge, faucet] {
292 let network_account = NetworkAccount::try_from(account).unwrap();
293 assert!(network_account.allows_tx_script(&ExpirationTransactionScript::script_root()));
294 }
295 }
296}