extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
use miden_core::{Felt, Word};
use miden_protocol::account::component::AccountComponentMetadata;
use miden_protocol::account::{
Account,
AccountComponent,
AccountId,
AccountType,
StorageSlot,
StorageSlotName,
};
use miden_protocol::asset::TokenSymbol;
use miden_protocol::errors::AccountIdError;
use miden_standards::account::access::Ownable2Step;
use miden_standards::account::faucets::{FungibleFaucetError, TokenMetadata};
use miden_standards::account::mint_policies::OwnerControlled;
use miden_utils_sync::LazyLock;
use thiserror::Error;
use super::agglayer_faucet_component_library;
pub use crate::{
AggLayerBridge,
B2AggNote,
ClaimNoteStorage,
ConfigAggBridgeNote,
EthAddress,
EthAmount,
EthAmountError,
EthEmbeddedAccountId,
ExitRoot,
GlobalIndex,
GlobalIndexError,
LeafData,
MetadataHash,
ProofData,
SmtNode,
UpdateGerNote,
create_claim_note,
};
include!(concat!(env!("OUT_DIR"), "/agglayer_constants.rs"));
static CONVERSION_INFO_1_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
StorageSlotName::new("agglayer::faucet::conversion_info_1")
.expect("conversion info 1 storage slot name should be valid")
});
static CONVERSION_INFO_2_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
StorageSlotName::new("agglayer::faucet::conversion_info_2")
.expect("conversion info 2 storage slot name should be valid")
});
static METADATA_HASH_LO_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
StorageSlotName::new("agglayer::faucet::metadata_hash_lo")
.expect("metadata hash lo storage slot name should be valid")
});
static METADATA_HASH_HI_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
StorageSlotName::new("agglayer::faucet::metadata_hash_hi")
.expect("metadata hash hi storage slot name should be valid")
});
#[derive(Debug, Clone)]
pub struct AggLayerFaucet {
metadata: TokenMetadata,
origin_token_address: EthAddress,
origin_network: u32,
scale: u8,
metadata_hash: MetadataHash,
}
impl AggLayerFaucet {
#[allow(clippy::too_many_arguments)]
pub fn new(
symbol: TokenSymbol,
decimals: u8,
max_supply: Felt,
token_supply: Felt,
origin_token_address: EthAddress,
origin_network: u32,
scale: u8,
metadata_hash: MetadataHash,
) -> Result<Self, FungibleFaucetError> {
let metadata = TokenMetadata::with_supply(symbol, decimals, max_supply, token_supply)?;
Ok(Self {
metadata,
origin_token_address,
origin_network,
scale,
metadata_hash,
})
}
pub fn with_token_supply(mut self, token_supply: Felt) -> Result<Self, FungibleFaucetError> {
self.metadata = self.metadata.with_token_supply(token_supply)?;
Ok(self)
}
pub fn metadata_slot() -> &'static StorageSlotName {
TokenMetadata::metadata_slot()
}
pub fn conversion_info_1_slot() -> &'static StorageSlotName {
&CONVERSION_INFO_1_SLOT_NAME
}
pub fn conversion_info_2_slot() -> &'static StorageSlotName {
&CONVERSION_INFO_2_SLOT_NAME
}
pub fn metadata_hash_lo_slot() -> &'static StorageSlotName {
&METADATA_HASH_LO_SLOT_NAME
}
pub fn metadata_hash_hi_slot() -> &'static StorageSlotName {
&METADATA_HASH_HI_SLOT_NAME
}
pub fn owner_config_slot() -> &'static StorageSlotName {
Ownable2Step::slot_name()
}
pub fn metadata(faucet_account: &Account) -> Result<TokenMetadata, AgglayerFaucetError> {
Self::assert_faucet_account(faucet_account)?;
let metadata_word = faucet_account
.storage()
.get_item(TokenMetadata::metadata_slot())
.expect("should be able to read metadata slot");
TokenMetadata::try_from(metadata_word).map_err(AgglayerFaucetError::FungibleFaucetError)
}
pub fn owner_account_id(faucet_account: &Account) -> Result<AccountId, AgglayerFaucetError> {
Self::assert_faucet_account(faucet_account)?;
let ownership = Ownable2Step::try_from_storage(faucet_account.storage())
.map_err(AgglayerFaucetError::Ownable2StepError)?;
ownership.owner().ok_or(AgglayerFaucetError::OwnershipRenounced)
}
pub fn origin_token_address(
faucet_account: &Account,
) -> Result<EthAddress, AgglayerFaucetError> {
Self::assert_faucet_account(faucet_account)?;
let conversion_info_1 = faucet_account
.storage()
.get_item(&CONVERSION_INFO_1_SLOT_NAME)
.expect("should be able to read the first conversion info slot");
let conversion_info_2 = faucet_account
.storage()
.get_item(&CONVERSION_INFO_2_SLOT_NAME)
.expect("should be able to read the second conversion info slot");
let addr_bytes_vec = conversion_info_1
.iter()
.chain([&conversion_info_2[0]])
.flat_map(|felt| {
u32::try_from(felt.as_canonical_u64())
.expect("Felt value does not fit into u32")
.to_le_bytes()
})
.collect::<Vec<u8>>();
Ok(EthAddress::new(
addr_bytes_vec
.try_into()
.expect("origin token addr vector should consist of exactly 20 bytes"),
))
}
pub fn origin_network(faucet_account: &Account) -> Result<u32, AgglayerFaucetError> {
Self::assert_faucet_account(faucet_account)?;
let conversion_info_2 = faucet_account
.storage()
.get_item(&CONVERSION_INFO_2_SLOT_NAME)
.expect("should be able to read the second conversion info slot");
Ok(conversion_info_2[1]
.as_canonical_u64()
.try_into()
.expect("origin network ID should fit into u32"))
}
pub fn scale(faucet_account: &Account) -> Result<u8, AgglayerFaucetError> {
Self::assert_faucet_account(faucet_account)?;
let conversion_info_2 = faucet_account
.storage()
.get_item(&CONVERSION_INFO_2_SLOT_NAME)
.expect("should be able to read the second conversion info slot");
Ok(conversion_info_2[2]
.as_canonical_u64()
.try_into()
.expect("scaling factor should fit into u8"))
}
fn assert_faucet_account(account: &Account) -> Result<(), AgglayerFaucetError> {
Self::assert_storage_slots(account)?;
Self::assert_code_commitment(account)?;
Ok(())
}
fn assert_storage_slots(account: &Account) -> Result<(), AgglayerFaucetError> {
let account_storage_slot_names: Vec<&StorageSlotName> = account
.storage()
.slots()
.iter()
.map(|storage_slot| storage_slot.name())
.collect::<Vec<&StorageSlotName>>();
let are_slots_present = Self::slot_names()
.iter()
.all(|slot_name| account_storage_slot_names.contains(slot_name));
if !are_slots_present {
return Err(AgglayerFaucetError::StorageSlotsMismatch);
}
Ok(())
}
fn assert_code_commitment(account: &Account) -> Result<(), AgglayerFaucetError> {
if FAUCET_CODE_COMMITMENT != account.code().commitment() {
return Err(AgglayerFaucetError::CodeCommitmentMismatch);
}
Ok(())
}
fn slot_names() -> Vec<&'static StorageSlotName> {
vec![
&*CONVERSION_INFO_1_SLOT_NAME,
&*CONVERSION_INFO_2_SLOT_NAME,
&*METADATA_HASH_LO_SLOT_NAME,
&*METADATA_HASH_HI_SLOT_NAME,
TokenMetadata::metadata_slot(),
Ownable2Step::slot_name(),
OwnerControlled::active_policy_proc_root_slot(),
OwnerControlled::allowed_policy_proc_roots_slot(),
OwnerControlled::policy_authority_slot(),
]
}
}
impl From<AggLayerFaucet> for AccountComponent {
fn from(faucet: AggLayerFaucet) -> Self {
let metadata_slot = StorageSlot::from(faucet.metadata);
let (conversion_slot1_word, conversion_slot2_word) = agglayer_faucet_conversion_slots(
&faucet.origin_token_address,
faucet.origin_network,
faucet.scale,
);
let conversion_slot1 =
StorageSlot::with_value(CONVERSION_INFO_1_SLOT_NAME.clone(), conversion_slot1_word);
let conversion_slot2 =
StorageSlot::with_value(CONVERSION_INFO_2_SLOT_NAME.clone(), conversion_slot2_word);
let hash_elements = faucet.metadata_hash.to_elements();
let metadata_hash_lo = StorageSlot::with_value(
METADATA_HASH_LO_SLOT_NAME.clone(),
Word::new([hash_elements[0], hash_elements[1], hash_elements[2], hash_elements[3]]),
);
let metadata_hash_hi = StorageSlot::with_value(
METADATA_HASH_HI_SLOT_NAME.clone(),
Word::new([hash_elements[4], hash_elements[5], hash_elements[6], hash_elements[7]]),
);
let agglayer_storage_slots = vec![
metadata_slot,
conversion_slot1,
conversion_slot2,
metadata_hash_lo,
metadata_hash_hi,
];
agglayer_faucet_component(agglayer_storage_slots)
}
}
#[derive(Debug, Error)]
pub enum AgglayerFaucetError {
#[error(
"provided account does not have storage slots required for the AggLayer Faucet account"
)]
StorageSlotsMismatch,
#[error("provided account does not have procedures required for the AggLayer Faucet account")]
CodeCommitmentMismatch,
#[error("fungible faucet error")]
FungibleFaucetError(#[source] FungibleFaucetError),
#[error("account ID error")]
AccountIdError(#[source] AccountIdError),
#[error("ownable2step error")]
Ownable2StepError(#[source] miden_standards::account::access::Ownable2StepError),
#[error("faucet ownership has been renounced")]
OwnershipRenounced,
}
fn agglayer_faucet_conversion_slots(
origin_token_address: &EthAddress,
origin_network: u32,
scale: u8,
) -> (Word, Word) {
let addr_elements = origin_token_address.to_elements();
let slot1 = Word::new([addr_elements[0], addr_elements[1], addr_elements[2], addr_elements[3]]);
let slot2 =
Word::new([addr_elements[4], Felt::from(origin_network), Felt::from(scale), Felt::ZERO]);
(slot1, slot2)
}
fn agglayer_faucet_component(storage_slots: Vec<StorageSlot>) -> AccountComponent {
let library = agglayer_faucet_component_library();
let metadata = AccountComponentMetadata::new("agglayer::faucet", [AccountType::FungibleFaucet])
.with_description("AggLayer faucet component with bridge validation");
AccountComponent::new(library, storage_slots, metadata).expect(
"agglayer_faucet component should satisfy the requirements of a valid account component",
)
}