1#![no_std]
2
3extern crate alloc;
4
5use alloc::collections::BTreeMap;
6
7use miden_core::{Felt, Word};
8use miden_protocol::account::{AccountBuilder, AccountComponent, AccountId};
9use miden_protocol::assembly::Path;
10use miden_protocol::asset::TokenSymbol;
11use miden_protocol::note::NoteScript;
12use miden_protocol::vm::Package;
13use miden_standards::account::access::{
14 Authority,
15 Ownable2Step,
16 Pausable,
17 PausableManager,
18 RoleBasedAccessControl,
19 RoleConfig,
20};
21use miden_standards::account::auth::NetworkAccount;
22use miden_standards::account::fees::{
23 BasicConstantFeePolicy,
24 ConstantFeeManager,
25 FeePolicyManager,
26};
27use miden_standards::account::policies::{
28 BurnPolicy,
29 MintPolicy,
30 TokenPolicyManager,
31 TransferPolicy,
32};
33use miden_utils_sync::LazyLock;
34
35pub mod agglayer_note;
36pub mod b2agg_note;
37pub mod bridge;
38pub mod claim_note;
39pub mod config_note;
40pub mod costs;
41pub mod deregister_note;
42pub mod errors;
43pub mod eth_types;
44pub mod faucet;
45mod ger_note;
46pub mod remove_ger_note;
47#[cfg(any(feature = "testing", test))]
48pub mod testing;
49pub mod update_ger_note;
50pub mod utils;
51
52pub use agglayer_note::AgglayerNote;
53pub use b2agg_note::B2AggNote;
54pub use bridge::{AggLayerBridge, AgglayerBridgeError, BridgeRoles, RemovedGerHashChain};
55pub use claim_note::{
56 CgiChainHash,
57 ClaimNote,
58 ClaimNoteStorage,
59 ExitRoot,
60 LeafData,
61 LeafValue,
62 ProofData,
63 SmtNode,
64};
65pub use config_note::{ConfigAggBridgeNote, ConversionMetadata};
66pub use deregister_note::DeregisterAggFaucetNote;
67#[cfg(any(test, feature = "testing"))]
68pub use eth_types::GlobalIndexExt;
69pub use eth_types::{GlobalIndex, GlobalIndexError, MetadataHash};
70pub use faucet::{AggLayerFaucet, AgglayerFaucetError};
71pub use remove_ger_note::RemoveGerNote;
72pub use update_ger_note::UpdateGerNote;
73pub use utils::Keccak256Output;
74
75static AGGLAYER_PACKAGE: LazyLock<Package> = LazyLock::new(|| {
79 let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/miden-agglayer.masp"));
80 Package::read_from_bytes_trusted(bytes).expect("shipped AggLayer package is well-formed")
81});
82
83static BRIDGE_COMPONENT_PACKAGE: LazyLock<Package> = LazyLock::new(|| {
84 let bytes =
85 include_bytes!(concat!(env!("OUT_DIR"), "/assets/components/miden-agglayer-bridge.masp"));
86 Package::read_from_bytes_trusted(bytes)
87 .expect("shipped bridge component package is well-formed")
88});
89
90static FAUCET_COMPONENT_PACKAGE: LazyLock<Package> = LazyLock::new(|| {
91 let bytes =
92 include_bytes!(concat!(env!("OUT_DIR"), "/assets/components/miden-agglayer-faucet.masp"));
93 Package::read_from_bytes_trusted(bytes)
94 .expect("shipped faucet component package is well-formed")
95});
96
97pub fn agglayer_package() -> Package {
108 AGGLAYER_PACKAGE.clone()
109}
110
111pub(crate) fn note_script(path: &str) -> NoteScript {
116 NoteScript::from_package_reference(&AGGLAYER_PACKAGE, Path::new(path))
117 .expect("agglayer package contains the note script procedure")
118}
119
120fn agglayer_bridge_component_package() -> Package {
122 BRIDGE_COMPONENT_PACKAGE.clone()
123}
124
125fn agglayer_faucet_component_package() -> Package {
127 FAUCET_COMPONENT_PACKAGE.clone()
128}
129
130fn create_agglayer_faucet_component(
150 token_symbol: &str,
151 decimals: u8,
152 max_supply: Felt,
153 initial_supply: Felt,
154) -> AccountComponent {
155 let symbol = TokenSymbol::new(token_symbol).expect("token symbol should be valid");
156 AggLayerFaucet::new(symbol, decimals, max_supply, initial_supply)
157 .expect("agglayer faucet metadata should be valid")
158 .into()
159}
160
161fn assert_basic_constant_fee_policy_manager(fee_policy_manager: &FeePolicyManager) {
162 let policy_root = BasicConstantFeePolicy::root();
163 assert_eq!(
164 fee_policy_manager.active_fee_policy(),
165 policy_root,
166 "AggLayer accounts require BasicConstantFeePolicy as the active fee policy"
167 );
168 assert_eq!(
169 fee_policy_manager.allowed_fee_policies().as_slice(),
170 &[policy_root],
171 "AggLayer accounts do not support additional fee policies"
172 );
173}
174
175impl AggLayerBridge {
176 pub fn account_builder(
186 seed: Word,
187 bridge_admin: AccountId,
188 roles: BridgeRoles,
189 network_id: u32,
190 fee_policy_manager: FeePolicyManager,
191 ) -> AccountBuilder {
192 assert_basic_constant_fee_policy_manager(&fee_policy_manager);
193 NetworkAccount::builder(seed.into(), AggLayerBridge::allowed_notes(), fee_policy_manager)
194 .expect("bridge note allowlist is non-empty")
195 .with_component(AggLayerBridge::new(network_id))
196 .with_component(
197 RoleBasedAccessControl::builder()
198 .role(
199 RoleConfig::new(RoleBasedAccessControl::admin_role())
200 .with_member(bridge_admin),
201 )
202 .roles(roles)
203 .build()
204 .expect("the bridge seeds distinct non-empty roles administered by ADMIN"),
205 )
206 .with_component(Authority::RbacControlled {
207 procedure_roles: AggLayerBridge::procedure_roles(),
208 })
209 .with_component(Pausable::unpaused())
210 .with_component(PausableManager)
211 .with_component(ConstantFeeManager::for_basic_constant_fee_policy())
212 }
213}
214
215impl AggLayerFaucet {
216 #[allow(clippy::too_many_arguments)]
228 pub fn account_builder(
229 seed: Word,
230 token_symbol: &str,
231 decimals: u8,
232 max_supply: Felt,
233 initial_supply: Felt,
234 faucet_admin: AccountId,
235 bridge_account_id: AccountId,
236 fee_policy_manager: FeePolicyManager,
237 ) -> AccountBuilder {
238 assert_basic_constant_fee_policy_manager(&fee_policy_manager);
239 let agglayer_component =
240 create_agglayer_faucet_component(token_symbol, decimals, max_supply, initial_supply);
241
242 let token_policy_manager = TokenPolicyManager::builder()
243 .active_mint_policy(MintPolicy::owner_only())
244 .active_burn_policy(BurnPolicy::owner_only())
245 .active_send_policy(TransferPolicy::allow_all())
246 .active_receive_policy(TransferPolicy::allow_all())
247 .build();
248
249 NetworkAccount::builder(seed.into(), AggLayerFaucet::allowed_notes(), fee_policy_manager)
250 .expect("faucet note allowlist is non-empty")
251 .with_component(agglayer_component)
252 .with_component(Ownable2Step::new(bridge_account_id))
253 .with_component(
254 RoleBasedAccessControl::with_admins([faucet_admin])
255 .expect("the faucet seeds a non-empty ADMIN role"),
256 )
257 .with_component(Authority::RbacControlled { procedure_roles: BTreeMap::new() })
258 .with_components(token_policy_manager)
259 .with_component(ConstantFeeManager::for_basic_constant_fee_policy())
260 }
261}
262
263#[cfg(test)]
267mod tests {
268 use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE;
269 use miden_standards::account::fees::FeePolicy;
270 use miden_standards::tx_script::ExpirationTransactionScript;
271
272 use super::*;
273 use crate::testing::{
274 create_existing_agglayer_faucet,
275 create_existing_bridge_account_with_roles,
276 };
277
278 #[test]
281 fn agglayer_accounts_allowlist_expiration_tx_script() {
282 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
283
284 let bridge = create_existing_bridge_account_with_roles(Word::default(), id, id, id, id, 77);
285 let faucet = create_existing_agglayer_faucet(
286 Word::default(),
287 "AGG",
288 6,
289 Felt::from(1000u32),
290 Felt::ZERO,
291 id,
292 );
293
294 for account in [bridge, faucet] {
295 let network_account = NetworkAccount::try_from(account).unwrap();
296 assert!(network_account.allows_tx_script(&ExpirationTransactionScript::script_root()));
297 }
298 }
299
300 #[test]
301 #[should_panic(expected = "require BasicConstantFeePolicy")]
302 fn agglayer_accounts_reject_a_different_active_fee_policy() {
303 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
304 let policy = FeePolicy::custom(PausableManager::pause_root(), [PausableManager]).unwrap();
305 let manager =
306 FeePolicyManager::builder().fee_faucet_id(id).active_fee_policy(policy).build();
307
308 assert_basic_constant_fee_policy_manager(&manager);
309 }
310
311 #[test]
312 #[should_panic(expected = "do not support additional fee policies")]
313 fn agglayer_accounts_reject_additional_fee_policies() {
314 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
315 let policy = FeePolicy::custom(PausableManager::pause_root(), [PausableManager]).unwrap();
316 let manager = FeePolicyManager::builder()
317 .fee_faucet_id(id)
318 .active_fee_policy(BasicConstantFeePolicy::new().into())
319 .allowed_fee_policy(policy)
320 .build();
321
322 assert_basic_constant_fee_policy_manager(&manager);
323 }
324}