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::access::AccessControl;
use crate::account::auth::NoAuth;
use crate::account::components::network_fungible_faucet_library;
use crate::account::interface::{AccountComponentInterface, AccountInterface, AccountInterfaceExt};
use crate::account::mint_policies::OwnerControlled;
use crate::procedure_digest;
const TOKEN_SYMBOL_TYPE: &str = "miden::standards::fungible_faucets::metadata::token_symbol";
procedure_digest!(
NETWORK_FUNGIBLE_FAUCET_MINT_AND_SEND,
NetworkFungibleFaucet::NAME,
NetworkFungibleFaucet::MINT_PROC_NAME,
network_fungible_faucet_library
);
procedure_digest!(
NETWORK_FUNGIBLE_FAUCET_BURN,
NetworkFungibleFaucet::NAME,
NetworkFungibleFaucet::BURN_PROC_NAME,
network_fungible_faucet_library
);
pub struct NetworkFungibleFaucet {
metadata: TokenMetadata,
}
impl NetworkFungibleFaucet {
pub const NAME: &'static str = "miden::standards::components::faucets::network_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::NetworkFungibleFaucet)
{
return Err(FungibleFaucetError::MissingNetworkFungibleFaucetInterface);
}
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 {
*NETWORK_FUNGIBLE_FAUCET_MINT_AND_SEND
}
pub fn burn_digest() -> Word {
*NETWORK_FUNGIBLE_FAUCET_BURN
}
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 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("Network fungible faucet component for minting and burning tokens")
.with_storage_schema(storage_schema)
}
}
impl From<NetworkFungibleFaucet> for AccountComponent {
fn from(network_faucet: NetworkFungibleFaucet) -> Self {
let metadata_slot = network_faucet.metadata.into();
let metadata = NetworkFungibleFaucet::component_metadata();
AccountComponent::new(
network_fungible_faucet_library(),
vec![metadata_slot],
metadata,
)
.expect("network fungible faucet component should satisfy the requirements of a valid account component")
}
}
impl TryFrom<Account> for NetworkFungibleFaucet {
type Error = FungibleFaucetError;
fn try_from(account: Account) -> Result<Self, Self::Error> {
let account_interface = AccountInterface::from_account(&account);
NetworkFungibleFaucet::try_from_interface(account_interface, account.storage())
}
}
impl TryFrom<&Account> for NetworkFungibleFaucet {
type Error = FungibleFaucetError;
fn try_from(account: &Account) -> Result<Self, Self::Error> {
let account_interface = AccountInterface::from_account(account);
NetworkFungibleFaucet::try_from_interface(account_interface, account.storage())
}
}
pub fn create_network_fungible_faucet(
init_seed: [u8; 32],
symbol: TokenSymbol,
decimals: u8,
max_supply: Felt,
access_control: AccessControl,
) -> Result<Account, FungibleFaucetError> {
match access_control {
AccessControl::Ownable2Step { .. } => {},
#[allow(unreachable_patterns)]
_ => {
return Err(FungibleFaucetError::UnsupportedAccessControl(
"network fungible faucets require Ownable2Step access control".into(),
));
},
}
let auth_component: AccountComponent = NoAuth::new().into();
let account = AccountBuilder::new(init_seed)
.account_type(AccountType::FungibleFaucet)
.storage_mode(AccountStorageMode::Network)
.with_auth_component(auth_component)
.with_component(NetworkFungibleFaucet::new(symbol, decimals, max_supply)?)
.with_component(access_control)
.with_component(OwnerControlled::owner_only())
.build()
.map_err(FungibleFaucetError::AccountError)?;
Ok(account)
}
#[cfg(test)]
mod tests {
use miden_protocol::account::{AccountId, AccountIdVersion, AccountStorageMode, AccountType};
use super::*;
use crate::account::access::Ownable2Step;
#[test]
fn test_create_network_fungible_faucet() {
let init_seed = [7u8; 32];
let symbol = TokenSymbol::new("NET").expect("token symbol should be valid");
let decimals = 8u8;
let max_supply = Felt::new(1_000);
let owner = AccountId::dummy(
[1u8; 15],
AccountIdVersion::Version0,
AccountType::RegularAccountImmutableCode,
AccountStorageMode::Private,
);
let account = create_network_fungible_faucet(
init_seed,
symbol.clone(),
decimals,
max_supply,
AccessControl::Ownable2Step { owner },
)
.expect("network faucet creation should succeed");
let expected_owner_word = Ownable2Step::new(owner).to_word();
assert_eq!(
account.storage().get_item(Ownable2Step::slot_name()).unwrap(),
expected_owner_word
);
let faucet = NetworkFungibleFaucet::try_from(&account)
.expect("network fungible faucet should be extractable from account");
assert_eq!(faucet.symbol(), &symbol);
assert_eq!(faucet.decimals(), decimals);
assert_eq!(faucet.max_supply(), max_supply);
assert_eq!(faucet.token_supply(), Felt::ZERO);
}
}