#![no_std]
extern crate alloc;
use miden_core::Word;
use miden_protocol::account::{AccountBuilder, AccountId};
use miden_protocol::assembly::Path;
use miden_protocol::asset::{AssetAmount, TokenSymbol};
use miden_protocol::note::NoteScript;
use miden_protocol::vm::Package;
use miden_standards::account::access::{
Authority,
Ownable2Step,
Pausable,
PausableManager,
RoleBasedAccessControl,
RoleConfig,
};
use miden_standards::account::auth::NetworkAccount;
use miden_standards::account::faucets::{FungibleFaucet, TokenName};
use miden_standards::account::fees::{
BasicConstantFeePolicy,
ConstantFeeManager,
FeePolicyManager,
};
use miden_standards::account::policies::{BurnPolicy, MintPolicy, TokenPolicyManager};
use miden_utils_sync::LazyLock;
pub mod agglayer_note;
pub mod b2agg_note;
pub mod bridge;
pub mod claim_note;
pub mod config_note;
pub mod costs;
pub mod deregister_note;
pub mod errors;
pub mod eth_types;
pub mod faucet;
mod ger_note;
pub mod remove_ger_note;
#[cfg(any(feature = "testing", test))]
pub mod testing;
pub mod update_ger_note;
pub mod utils;
pub use agglayer_note::AgglayerNote;
pub use b2agg_note::B2AggNote;
pub use bridge::{AggLayerBridge, AgglayerBridgeError, BridgeRoles, RemovedGerHashChain};
pub use claim_note::{
CgiChainHash,
ClaimNote,
ClaimNoteStorage,
ExitRoot,
LeafData,
LeafValue,
ProofData,
SmtNode,
};
pub use config_note::{ConfigAggBridgeNote, ConversionMetadata};
pub use deregister_note::DeregisterAggFaucetNote;
#[cfg(any(test, feature = "testing"))]
pub use eth_types::GlobalIndexExt;
pub use eth_types::{GlobalIndex, GlobalIndexError, MetadataHash};
pub use faucet::AggLayerFaucet;
pub use remove_ger_note::RemoveGerNote;
pub use update_ger_note::UpdateGerNote;
pub use utils::Keccak256Output;
static AGGLAYER_PACKAGE: LazyLock<Package> = LazyLock::new(|| {
let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/miden-agglayer.masp"));
Package::read_from_bytes_trusted(bytes).expect("shipped AggLayer package is well-formed")
});
static BRIDGE_COMPONENT_PACKAGE: LazyLock<Package> = LazyLock::new(|| {
let bytes =
include_bytes!(concat!(env!("OUT_DIR"), "/assets/components/miden-agglayer-bridge.masp"));
Package::read_from_bytes_trusted(bytes)
.expect("shipped bridge component package is well-formed")
});
pub fn agglayer_package() -> Package {
AGGLAYER_PACKAGE.clone()
}
pub(crate) fn note_script(path: &str) -> NoteScript {
NoteScript::from_package_reference(&AGGLAYER_PACKAGE, Path::new(path))
.expect("agglayer package contains the note script procedure")
}
fn agglayer_bridge_component_package() -> Package {
BRIDGE_COMPONENT_PACKAGE.clone()
}
impl AggLayerBridge {
pub fn account_builder(
seed: Word,
bridge_admin: AccountId,
roles: BridgeRoles,
network_id: u32,
fee_faucet_id: AccountId,
fee_policy: BasicConstantFeePolicy,
) -> AccountBuilder {
let fee_policy_manager = FeePolicyManager::builder()
.fee_faucet_id(fee_faucet_id)
.active_fee_policy(fee_policy.into())
.build();
NetworkAccount::builder(seed.into(), AggLayerBridge::allowed_notes(), fee_policy_manager)
.expect("bridge note allowlist is non-empty")
.with_component(AggLayerBridge::new(network_id))
.with_component(
RoleBasedAccessControl::builder()
.role(
RoleConfig::new(RoleBasedAccessControl::admin_role())
.with_member(bridge_admin),
)
.roles(roles)
.build()
.expect("the bridge seeds distinct non-empty roles administered by ADMIN"),
)
.with_component(Authority::RbacControlled {
procedure_roles: AggLayerBridge::procedure_roles(),
})
.with_component(Pausable::unpaused())
.with_component(PausableManager)
.with_component(ConstantFeeManager::for_basic_constant_fee_policy())
}
}
impl AggLayerFaucet {
#[allow(clippy::too_many_arguments)]
pub fn account_builder(
seed: Word,
token_name: TokenName,
token_symbol: TokenSymbol,
decimals: u8,
max_supply: AssetAmount,
initial_supply: AssetAmount,
faucet_admin: AccountId,
fee_manager: AccountId,
bridge_account_id: AccountId,
fee_faucet_id: AccountId,
fee_policy: BasicConstantFeePolicy,
) -> AccountBuilder {
let fee_policy_manager = FeePolicyManager::builder()
.fee_faucet_id(fee_faucet_id)
.active_fee_policy(fee_policy.into())
.build();
let faucet = FungibleFaucet::builder()
.name(token_name)
.symbol(token_symbol)
.decimals(decimals)
.max_supply(max_supply)
.token_supply(initial_supply)
.build()
.expect("agglayer faucet decimals and supplies should be within their valid ranges");
let token_policy_manager = TokenPolicyManager::builder()
.active_mint_policy(MintPolicy::owner_only())
.active_burn_policy(BurnPolicy::owner_only())
.build();
let rbac = RoleBasedAccessControl::builder()
.role(RoleConfig::new(RoleBasedAccessControl::admin_role()).with_member(faucet_admin))
.role(RoleConfig::new(AggLayerFaucet::fee_manager_role()).with_member(fee_manager))
.build()
.expect("the faucet seeds non-empty roles administered by ADMIN");
NetworkAccount::builder(seed.into(), AggLayerFaucet::allowed_notes(), fee_policy_manager)
.expect("faucet note allowlist is non-empty")
.with_component(faucet)
.with_component(Ownable2Step::new(bridge_account_id))
.with_component(rbac)
.with_component(Authority::RbacControlled {
procedure_roles: AggLayerFaucet::procedure_roles(),
})
.with_components(token_policy_manager)
.with_component(ConstantFeeManager::for_basic_constant_fee_policy())
}
}
#[cfg(test)]
mod tests {
use miden_core::Felt;
use miden_protocol::account::AssetCallbackFlag;
use miden_protocol::asset::AssetCallbacks;
use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE;
use miden_standards::tx_script::ExpirationTransactionScript;
use super::*;
use crate::testing::{
create_existing_agglayer_faucet,
create_existing_bridge_account_with_roles,
};
#[test]
fn agglayer_faucet_has_asset_callbacks_disabled() {
let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
let faucet = create_existing_agglayer_faucet(
Word::default(),
"AggLayer Token",
"AGG",
6,
Felt::from(1000u32),
Felt::ZERO,
id,
id,
);
for slot_name in AssetCallbacks::slot_names() {
assert!(
faucet.storage().get(slot_name).is_none(),
"faucet should not install the {slot_name} callback slot"
);
}
assert_eq!(faucet.id().asset_callback_flag(), AssetCallbackFlag::Disabled);
}
#[test]
fn agglayer_accounts_allowlist_expiration_tx_script() {
let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
let bridge =
create_existing_bridge_account_with_roles(Word::default(), id, id, id, id, id, id, 77);
let faucet = create_existing_agglayer_faucet(
Word::default(),
"AggLayer Token",
"AGG",
6,
Felt::from(1000u32),
Felt::ZERO,
id,
id,
);
for account in [bridge, faucet] {
let network_account = NetworkAccount::try_from(account).unwrap();
assert!(network_account.allows_tx_script(&ExpirationTransactionScript::script_root()));
}
}
}