#![no_std]
extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
use miden_assembly::Library;
use miden_assembly::utils::Deserializable;
use miden_core::{Felt, FieldElement, Program, Word};
use miden_protocol::account::{
Account,
AccountBuilder,
AccountComponent,
AccountId,
AccountStorageMode,
AccountType,
StorageSlot,
StorageSlotName,
};
use miden_protocol::asset::TokenSymbol;
use miden_protocol::crypto::rand::FeltRng;
use miden_protocol::errors::NoteError;
use miden_protocol::note::{
Note,
NoteAssets,
NoteInputs,
NoteMetadata,
NoteRecipient,
NoteScript,
NoteTag,
NoteType,
};
use miden_standards::account::auth::NoAuth;
use miden_standards::account::faucets::NetworkFungibleFaucet;
use miden_utils_sync::LazyLock;
pub mod errors;
pub mod eth_address;
pub mod utils;
pub use eth_address::EthAddressFormat;
use utils::bytes32_to_felts;
static B2AGG_SCRIPT: LazyLock<Program> = LazyLock::new(|| {
let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/B2AGG.masb"));
Program::read_from_bytes(bytes).expect("Shipped B2AGG script is well-formed")
});
pub fn b2agg_script() -> Program {
B2AGG_SCRIPT.clone()
}
static CLAIM_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/CLAIM.masb"));
let program = Program::read_from_bytes(bytes).expect("Shipped CLAIM script is well-formed");
NoteScript::new(program)
});
pub fn claim_script() -> NoteScript {
CLAIM_SCRIPT.clone()
}
static AGGLAYER_LIBRARY: LazyLock<Library> = LazyLock::new(|| {
let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/agglayer.masl"));
Library::read_from_bytes(bytes).expect("Shipped AggLayer library is well-formed")
});
pub fn agglayer_library() -> Library {
AGGLAYER_LIBRARY.clone()
}
pub fn bridge_out_library() -> Library {
agglayer_library()
}
pub fn local_exit_tree_library() -> Library {
agglayer_library()
}
pub fn local_exit_tree_component(storage_slots: Vec<StorageSlot>) -> AccountComponent {
let library = local_exit_tree_library();
AccountComponent::new(library, storage_slots)
.expect("local_exit_tree component should satisfy the requirements of a valid account component")
.with_supports_all_types()
}
pub fn bridge_out_component(storage_slots: Vec<StorageSlot>) -> AccountComponent {
let library = bridge_out_library();
AccountComponent::new(library, storage_slots)
.expect("bridge_out component should satisfy the requirements of a valid account component")
.with_supports_all_types()
}
pub fn bridge_in_library() -> Library {
agglayer_library()
}
pub fn bridge_in_component(storage_slots: Vec<StorageSlot>) -> AccountComponent {
let library = bridge_in_library();
AccountComponent::new(library, storage_slots)
.expect("bridge_in component should satisfy the requirements of a valid account component")
.with_supports_all_types()
}
pub fn agglayer_faucet_library() -> Library {
agglayer_library()
}
pub fn agglayer_faucet_component(storage_slots: Vec<StorageSlot>) -> AccountComponent {
let library = agglayer_faucet_library();
AccountComponent::new(library, storage_slots)
.expect("agglayer_faucet component should satisfy the requirements of a valid account component")
.with_supports_all_types()
}
pub fn bridge_out_with_local_exit_tree_component(
storage_slots: Vec<StorageSlot>,
) -> Vec<AccountComponent> {
vec![
bridge_out_component(storage_slots.clone()),
local_exit_tree_component(vec![]), ]
}
pub fn asset_conversion_component(storage_slots: Vec<StorageSlot>) -> AccountComponent {
let library = agglayer_library();
AccountComponent::new(library, storage_slots)
.expect("asset_conversion component should satisfy the requirements of a valid account component")
.with_supports_all_types()
}
pub fn create_bridge_account_component() -> AccountComponent {
let bridge_storage_slot_name = StorageSlotName::new("miden::agglayer::bridge")
.expect("Bridge storage slot name should be valid");
let bridge_storage_slots = vec![StorageSlot::with_empty_map(bridge_storage_slot_name)];
bridge_out_component(bridge_storage_slots)
}
pub fn create_agglayer_faucet_component(
token_symbol: &str,
decimals: u8,
max_supply: Felt,
bridge_account_id: AccountId,
) -> AccountComponent {
let token_symbol = TokenSymbol::new(token_symbol).expect("Token symbol should be valid");
let metadata_word =
Word::new([max_supply, Felt::from(decimals), token_symbol.into(), FieldElement::ZERO]);
let metadata_slot =
StorageSlot::with_value(NetworkFungibleFaucet::metadata_slot().clone(), metadata_word);
let bridge_account_id_word = Word::new([
Felt::new(0),
Felt::new(0),
bridge_account_id.suffix(),
bridge_account_id.prefix().as_felt(),
]);
let agglayer_storage_slot_name = StorageSlotName::new("miden::agglayer::faucet")
.expect("Agglayer faucet storage slot name should be valid");
let bridge_slot = StorageSlot::with_value(agglayer_storage_slot_name, bridge_account_id_word);
let agglayer_storage_slots = vec![metadata_slot, bridge_slot];
agglayer_faucet_component(agglayer_storage_slots)
}
pub fn create_bridge_account_builder(seed: Word) -> AccountBuilder {
let bridge_component = create_bridge_account_component();
Account::builder(seed.into())
.storage_mode(AccountStorageMode::Public)
.with_component(bridge_component)
}
pub fn create_bridge_account(seed: Word) -> Account {
create_bridge_account_builder(seed)
.with_auth_component(AccountComponent::from(NoAuth))
.build()
.expect("Bridge account should be valid")
}
#[cfg(any(feature = "testing", test))]
pub fn create_existing_bridge_account(seed: Word) -> Account {
create_bridge_account_builder(seed)
.with_auth_component(AccountComponent::from(NoAuth))
.build_existing()
.expect("Bridge account should be valid")
}
pub fn create_agglayer_faucet_builder(
seed: Word,
token_symbol: &str,
decimals: u8,
max_supply: Felt,
bridge_account_id: AccountId,
) -> AccountBuilder {
let agglayer_component =
create_agglayer_faucet_component(token_symbol, decimals, max_supply, bridge_account_id);
Account::builder(seed.into())
.account_type(AccountType::FungibleFaucet)
.storage_mode(AccountStorageMode::Network)
.with_component(agglayer_component)
}
pub fn create_agglayer_faucet(
seed: Word,
token_symbol: &str,
decimals: u8,
max_supply: Felt,
bridge_account_id: AccountId,
) -> Account {
create_agglayer_faucet_builder(seed, token_symbol, decimals, max_supply, bridge_account_id)
.with_auth_component(AccountComponent::from(NoAuth))
.build()
.expect("Agglayer faucet account should be valid")
}
#[cfg(any(feature = "testing", test))]
pub fn create_existing_agglayer_faucet(
seed: Word,
token_symbol: &str,
decimals: u8,
max_supply: Felt,
bridge_account_id: AccountId,
) -> Account {
create_agglayer_faucet_builder(seed, token_symbol, decimals, max_supply, bridge_account_id)
.with_auth_component(AccountComponent::from(NoAuth))
.build_existing()
.expect("Agglayer faucet account should be valid")
}
pub struct ClaimNoteParams<'a, R: FeltRng> {
pub smt_proof_local_exit_root: Vec<Felt>,
pub smt_proof_rollup_exit_root: Vec<Felt>,
pub global_index: [Felt; 8],
pub mainnet_exit_root: &'a [u8; 32],
pub rollup_exit_root: &'a [u8; 32],
pub origin_network: Felt,
pub origin_token_address: &'a [u8; 20],
pub destination_network: Felt,
pub destination_address: &'a [u8; 20],
pub amount: [Felt; 8],
pub metadata: [Felt; 8],
pub claim_note_creator_account_id: AccountId,
pub agglayer_faucet_account_id: AccountId,
pub output_note_tag: NoteTag,
pub p2id_serial_number: Word,
pub destination_account_id: AccountId,
pub rng: &'a mut R,
}
pub fn create_claim_note<R: FeltRng>(params: ClaimNoteParams<'_, R>) -> Result<Note, NoteError> {
if params.smt_proof_local_exit_root.len() != 256 {
return Err(NoteError::other(alloc::format!(
"SMT proof local exit root must be exactly 256 felts, got {}",
params.smt_proof_local_exit_root.len()
)));
}
if params.smt_proof_rollup_exit_root.len() != 256 {
return Err(NoteError::other(alloc::format!(
"SMT proof rollup exit root must be exactly 256 felts, got {}",
params.smt_proof_rollup_exit_root.len()
)));
}
let mut claim_inputs = vec![];
claim_inputs.extend(params.smt_proof_local_exit_root);
claim_inputs.extend(params.smt_proof_rollup_exit_root);
claim_inputs.extend(params.global_index);
let mainnet_exit_root_felts = bytes32_to_felts(params.mainnet_exit_root);
claim_inputs.extend(mainnet_exit_root_felts);
let rollup_exit_root_felts = bytes32_to_felts(params.rollup_exit_root);
claim_inputs.extend(rollup_exit_root_felts);
claim_inputs.push(params.origin_network);
let origin_token_address_felts =
EthAddressFormat::new(*params.origin_token_address).to_elements().to_vec();
claim_inputs.extend(origin_token_address_felts);
claim_inputs.push(params.destination_network);
let destination_address_felts = vec![
params.destination_account_id.prefix().as_felt(),
params.destination_account_id.suffix(),
Felt::new(0),
Felt::new(0),
Felt::new(0),
];
claim_inputs.extend(destination_address_felts);
claim_inputs.extend(params.amount);
claim_inputs.extend(params.metadata);
let padding = vec![Felt::ZERO; 4];
claim_inputs.extend(padding);
claim_inputs.extend(params.p2id_serial_number);
claim_inputs.push(params.agglayer_faucet_account_id.prefix().as_felt());
claim_inputs.push(params.agglayer_faucet_account_id.suffix());
claim_inputs.push(params.output_note_tag.as_u32().into());
let inputs = NoteInputs::new(claim_inputs)?;
let tag = NoteTag::with_account_target(params.agglayer_faucet_account_id);
let claim_script = claim_script();
let serial_num = params.rng.draw_word();
let note_type = NoteType::Public;
let metadata = NoteMetadata::new(params.claim_note_creator_account_id, note_type, tag);
let assets = NoteAssets::new(vec![])?;
let recipient = NoteRecipient::new(serial_num, claim_script, inputs);
Ok(Note::new(assets, metadata, recipient))
}
#[cfg(any(feature = "testing", test))]
pub type ClaimNoteTestInputs = (
Vec<Felt>,
Vec<Felt>,
[Felt; 8],
[u8; 32],
[u8; 32],
Felt,
[u8; 20],
Felt,
[u8; 20],
[Felt; 8],
[Felt; 8],
);
#[cfg(any(feature = "testing", test))]
pub fn claim_note_test_inputs(
amount: Felt,
destination_account_id: AccountId,
) -> ClaimNoteTestInputs {
let smt_proof_local_exit_root = vec![Felt::new(0); 256];
let smt_proof_rollup_exit_root = vec![Felt::new(0); 256];
let global_index = [
Felt::new(12345),
Felt::new(0),
Felt::new(0),
Felt::new(0),
Felt::new(0),
Felt::new(0),
Felt::new(0),
Felt::new(0),
];
let mainnet_exit_root: [u8; 32] = [
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
0x77, 0x88,
];
let rollup_exit_root: [u8; 32] = [
0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99,
];
let origin_network = Felt::new(1);
let origin_token_address: [u8; 20] = [
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xaa, 0xbb, 0xcc,
];
let destination_network = Felt::new(2);
let destination_address =
EthAddressFormat::from_account_id(destination_account_id).into_bytes();
let amount_u256 = [
amount,
Felt::new(0),
Felt::new(0),
Felt::new(0),
Felt::new(0),
Felt::new(0),
Felt::new(0),
Felt::new(0),
];
let metadata: [Felt; 8] = [Felt::new(0); 8];
(
smt_proof_local_exit_root,
smt_proof_rollup_exit_root,
global_index,
mainnet_exit_root,
rollup_exit_root,
origin_network,
origin_token_address,
destination_network,
destination_address,
amount_u256,
metadata,
)
}