use miden_protocol::account::component::{
AccountComponentMetadata,
FeltSchema,
SchemaType,
StorageSchema,
StorageSlotSchema,
};
use miden_protocol::account::{
Account,
AccountBuilder,
AccountComponent,
AccountStorage,
AccountStorageMode,
AccountType,
StorageSlotName,
};
use miden_protocol::asset::TokenSymbol;
use miden_protocol::{Felt, Word};
use super::{FungibleFaucetError, TokenMetadata};
use crate::account::AuthMethod;
use crate::account::auth::{AuthSingleSigAcl, AuthSingleSigAclConfig};
use crate::account::components::basic_fungible_faucet_library;
use crate::account::mint_policies::AuthControlled;
const TOKEN_SYMBOL_TYPE: &str = "miden::standards::fungible_faucets::metadata::token_symbol";
use crate::account::interface::{AccountComponentInterface, AccountInterface, AccountInterfaceExt};
use crate::procedure_digest;
procedure_digest!(
BASIC_FUNGIBLE_FAUCET_MINT_AND_SEND,
BasicFungibleFaucet::NAME,
BasicFungibleFaucet::MINT_PROC_NAME,
basic_fungible_faucet_library
);
procedure_digest!(
BASIC_FUNGIBLE_FAUCET_BURN,
BasicFungibleFaucet::NAME,
BasicFungibleFaucet::BURN_PROC_NAME,
basic_fungible_faucet_library
);
pub struct BasicFungibleFaucet {
metadata: TokenMetadata,
}
impl BasicFungibleFaucet {
pub const NAME: &'static str = "miden::standards::components::faucets::basic_fungible_faucet";
pub const MAX_DECIMALS: u8 = TokenMetadata::MAX_DECIMALS;
const MINT_PROC_NAME: &str = "mint_and_send";
const BURN_PROC_NAME: &str = "burn";
pub fn new(
symbol: TokenSymbol,
decimals: u8,
max_supply: Felt,
) -> Result<Self, FungibleFaucetError> {
let metadata = TokenMetadata::new(symbol, decimals, max_supply)?;
Ok(Self { metadata })
}
pub fn from_metadata(metadata: TokenMetadata) -> Self {
Self { metadata }
}
fn try_from_interface(
interface: AccountInterface,
storage: &AccountStorage,
) -> Result<Self, FungibleFaucetError> {
if !interface.components().contains(&AccountComponentInterface::BasicFungibleFaucet) {
return Err(FungibleFaucetError::MissingBasicFungibleFaucetInterface);
}
let metadata = TokenMetadata::try_from(storage)?;
Ok(Self { metadata })
}
pub fn metadata_slot() -> &'static StorageSlotName {
TokenMetadata::metadata_slot()
}
pub fn metadata_slot_schema() -> (StorageSlotName, StorageSlotSchema) {
let token_symbol_type = SchemaType::new(TOKEN_SYMBOL_TYPE).expect("valid type");
(
Self::metadata_slot().clone(),
StorageSlotSchema::value(
"Token metadata",
[
FeltSchema::felt("token_supply").with_default(Felt::new(0)),
FeltSchema::felt("max_supply"),
FeltSchema::u8("decimals"),
FeltSchema::new_typed(token_symbol_type, "symbol"),
],
),
)
}
pub fn metadata(&self) -> &TokenMetadata {
&self.metadata
}
pub fn symbol(&self) -> &TokenSymbol {
self.metadata.symbol()
}
pub fn decimals(&self) -> u8 {
self.metadata.decimals()
}
pub fn max_supply(&self) -> Felt {
self.metadata.max_supply()
}
pub fn token_supply(&self) -> Felt {
self.metadata.token_supply()
}
pub fn mint_and_send_digest() -> Word {
*BASIC_FUNGIBLE_FAUCET_MINT_AND_SEND
}
pub fn burn_digest() -> Word {
*BASIC_FUNGIBLE_FAUCET_BURN
}
pub fn component_metadata() -> AccountComponentMetadata {
let storage_schema = StorageSchema::new([Self::metadata_slot_schema()])
.expect("storage schema should be valid");
AccountComponentMetadata::new(Self::NAME, [AccountType::FungibleFaucet])
.with_description("Basic fungible faucet component for minting and burning tokens")
.with_storage_schema(storage_schema)
}
pub fn with_token_supply(mut self, token_supply: Felt) -> Result<Self, FungibleFaucetError> {
self.metadata = self.metadata.with_token_supply(token_supply)?;
Ok(self)
}
}
impl From<BasicFungibleFaucet> for AccountComponent {
fn from(faucet: BasicFungibleFaucet) -> Self {
let storage_slot = faucet.metadata.into();
let metadata = BasicFungibleFaucet::component_metadata();
AccountComponent::new(basic_fungible_faucet_library(), vec![storage_slot], metadata)
.expect("basic fungible faucet component should satisfy the requirements of a valid account component")
}
}
impl TryFrom<Account> for BasicFungibleFaucet {
type Error = FungibleFaucetError;
fn try_from(account: Account) -> Result<Self, Self::Error> {
let account_interface = AccountInterface::from_account(&account);
BasicFungibleFaucet::try_from_interface(account_interface, account.storage())
}
}
impl TryFrom<&Account> for BasicFungibleFaucet {
type Error = FungibleFaucetError;
fn try_from(account: &Account) -> Result<Self, Self::Error> {
let account_interface = AccountInterface::from_account(account);
BasicFungibleFaucet::try_from_interface(account_interface, account.storage())
}
}
pub fn create_basic_fungible_faucet(
init_seed: [u8; 32],
symbol: TokenSymbol,
decimals: u8,
max_supply: Felt,
account_storage_mode: AccountStorageMode,
auth_method: AuthMethod,
) -> Result<Account, FungibleFaucetError> {
let mint_proc_root = BasicFungibleFaucet::mint_and_send_digest();
let auth_component: AccountComponent = match auth_method {
AuthMethod::SingleSig { approver: (pub_key, auth_scheme) } => AuthSingleSigAcl::new(
pub_key,
auth_scheme,
AuthSingleSigAclConfig::new()
.with_auth_trigger_procedures(vec![mint_proc_root])
.with_allow_unauthorized_input_notes(true),
)
.map_err(FungibleFaucetError::AccountError)?
.into(),
AuthMethod::NoAuth => {
return Err(FungibleFaucetError::UnsupportedAuthMethod(
"basic fungible faucets cannot be created with NoAuth authentication method".into(),
));
},
AuthMethod::Unknown => {
return Err(FungibleFaucetError::UnsupportedAuthMethod(
"basic fungible faucets cannot be created with Unknown authentication method"
.into(),
));
},
AuthMethod::Multisig { .. } => {
return Err(FungibleFaucetError::UnsupportedAuthMethod(
"basic fungible faucets do not support Multisig authentication".into(),
));
},
};
let account = AccountBuilder::new(init_seed)
.account_type(AccountType::FungibleFaucet)
.storage_mode(account_storage_mode)
.with_auth_component(auth_component)
.with_component(BasicFungibleFaucet::new(symbol, decimals, max_supply)?)
.with_component(AuthControlled::allow_all())
.build()
.map_err(FungibleFaucetError::AccountError)?;
Ok(account)
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use miden_protocol::Word;
use miden_protocol::account::auth::{AuthScheme, PublicKeyCommitment};
use super::{
AccountBuilder,
AccountStorageMode,
AccountType,
AuthMethod,
BasicFungibleFaucet,
Felt,
FungibleFaucetError,
TokenSymbol,
create_basic_fungible_faucet,
};
use crate::account::auth::{AuthSingleSig, AuthSingleSigAcl};
use crate::account::wallets::BasicWallet;
#[test]
fn faucet_contract_creation() {
let pub_key_word = Word::new([Felt::ONE; 4]);
let auth_method: AuthMethod = AuthMethod::SingleSig {
approver: (pub_key_word.into(), AuthScheme::Falcon512Poseidon2),
};
let init_seed: [u8; 32] = [
90, 110, 209, 94, 84, 105, 250, 242, 223, 203, 216, 124, 22, 159, 14, 132, 215, 85,
183, 204, 149, 90, 166, 68, 100, 73, 106, 168, 125, 237, 138, 16,
];
let max_supply = Felt::new(123);
let token_symbol_string = "POL";
let token_symbol = TokenSymbol::try_from(token_symbol_string).unwrap();
let decimals = 2u8;
let storage_mode = AccountStorageMode::Private;
let token_symbol_felt = token_symbol.as_element();
let faucet_account = create_basic_fungible_faucet(
init_seed,
token_symbol.clone(),
decimals,
max_supply,
storage_mode,
auth_method,
)
.unwrap();
assert_eq!(
faucet_account.storage().get_item(AuthSingleSigAcl::public_key_slot()).unwrap(),
pub_key_word
);
assert_eq!(
faucet_account.storage().get_item(AuthSingleSigAcl::config_slot()).unwrap(),
[Felt::ONE, Felt::ZERO, Felt::ONE, Felt::ZERO].into()
);
let mint_root = BasicFungibleFaucet::mint_and_send_digest();
assert_eq!(
faucet_account
.storage()
.get_map_item(
AuthSingleSigAcl::trigger_procedure_roots_slot(),
[Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ZERO].into()
)
.unwrap(),
mint_root
);
assert_eq!(
faucet_account.storage().get_item(BasicFungibleFaucet::metadata_slot()).unwrap(),
[Felt::ZERO, Felt::new(123), Felt::new(2), token_symbol_felt].into()
);
assert!(faucet_account.is_faucet());
assert_eq!(faucet_account.account_type(), AccountType::FungibleFaucet);
let faucet_component = BasicFungibleFaucet::try_from(faucet_account.clone()).unwrap();
assert_eq!(faucet_component.symbol(), &token_symbol);
assert_eq!(faucet_component.decimals(), decimals);
assert_eq!(faucet_component.max_supply(), max_supply);
assert_eq!(faucet_component.token_supply(), Felt::ZERO);
}
#[test]
fn faucet_create_from_account() {
let mock_word = Word::from([0, 1, 2, 3u32]);
let mock_public_key = PublicKeyCommitment::from(mock_word);
let mock_seed = mock_word.as_bytes();
let token_symbol = TokenSymbol::new("POL").expect("invalid token symbol");
let faucet_account = AccountBuilder::new(mock_seed)
.account_type(AccountType::FungibleFaucet)
.with_component(
BasicFungibleFaucet::new(token_symbol.clone(), 10, Felt::new(100))
.expect("failed to create a fungible faucet component"),
)
.with_auth_component(AuthSingleSig::new(
mock_public_key,
AuthScheme::Falcon512Poseidon2,
))
.build_existing()
.expect("failed to create wallet account");
let basic_ff = BasicFungibleFaucet::try_from(faucet_account)
.expect("basic fungible faucet creation failed");
assert_eq!(basic_ff.symbol(), &token_symbol);
assert_eq!(basic_ff.decimals(), 10);
assert_eq!(basic_ff.max_supply(), Felt::new(100));
assert_eq!(basic_ff.token_supply(), Felt::ZERO);
let invalid_faucet_account = AccountBuilder::new(mock_seed)
.account_type(AccountType::FungibleFaucet)
.with_auth_component(AuthSingleSig::new(mock_public_key, AuthScheme::Falcon512Poseidon2))
.with_component(BasicWallet)
.build_existing()
.expect("failed to create wallet account");
let err = BasicFungibleFaucet::try_from(invalid_faucet_account)
.err()
.expect("basic fungible faucet creation should fail");
assert_matches!(err, FungibleFaucetError::MissingBasicFungibleFaucetInterface);
}
#[test]
fn get_faucet_procedures() {
let _mint_and_send_digest = BasicFungibleFaucet::mint_and_send_digest();
let _burn_digest = BasicFungibleFaucet::burn_digest();
}
}