use super::account::AccountType;
use super::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
use super::{AssetError, Felt, Hasher, TokenSymbolError, Word, ZERO};
use crate::account::AccountIdPrefix;
mod fungible;
use alloc::boxed::Box;
pub use fungible::FungibleAsset;
mod nonfungible;
pub use nonfungible::{NonFungibleAsset, NonFungibleAssetDetails};
mod token_symbol;
pub use token_symbol::TokenSymbol;
mod vault;
pub use vault::{AssetVault, AssetVaultKey, AssetWitness, PartialVault};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Asset {
Fungible(FungibleAsset),
NonFungible(NonFungibleAsset),
}
impl Asset {
pub(crate) fn new_unchecked(value: Word) -> Asset {
if is_not_a_non_fungible_asset(value) {
Asset::Fungible(FungibleAsset::new_unchecked(value))
} else {
Asset::NonFungible(unsafe { NonFungibleAsset::new_unchecked(value) })
}
}
pub fn is_same(&self, other: &Self) -> bool {
use Asset::*;
match (self, other) {
(Fungible(l), Fungible(r)) => l.is_from_same_faucet(r),
(NonFungible(l), NonFungible(r)) => l == r,
_ => false,
}
}
pub const fn is_fungible(&self) -> bool {
matches!(self, Self::Fungible(_))
}
pub const fn is_non_fungible(&self) -> bool {
matches!(self, Self::NonFungible(_))
}
pub fn faucet_id_prefix(&self) -> AccountIdPrefix {
match self {
Self::Fungible(asset) => asset.faucet_id_prefix(),
Self::NonFungible(asset) => asset.faucet_id_prefix(),
}
}
pub fn vault_key(&self) -> AssetVaultKey {
match self {
Self::Fungible(asset) => asset.vault_key(),
Self::NonFungible(asset) => asset.vault_key(),
}
}
pub fn unwrap_fungible(&self) -> FungibleAsset {
match self {
Asset::Fungible(asset) => *asset,
Asset::NonFungible(_) => panic!("the asset is non-fungible"),
}
}
pub fn unwrap_non_fungible(&self) -> NonFungibleAsset {
match self {
Asset::Fungible(_) => panic!("the asset is fungible"),
Asset::NonFungible(asset) => *asset,
}
}
}
impl From<Asset> for Word {
fn from(asset: Asset) -> Self {
match asset {
Asset::Fungible(asset) => asset.into(),
Asset::NonFungible(asset) => asset.into(),
}
}
}
impl From<&Asset> for Word {
fn from(value: &Asset) -> Self {
(*value).into()
}
}
impl TryFrom<&Word> for Asset {
type Error = AssetError;
fn try_from(value: &Word) -> Result<Self, Self::Error> {
(*value).try_into()
}
}
impl TryFrom<Word> for Asset {
type Error = AssetError;
fn try_from(value: Word) -> Result<Self, Self::Error> {
let prefix = AccountIdPrefix::try_from(value[3])
.map_err(|err| AssetError::InvalidFaucetAccountId(Box::from(err)))?;
match prefix.account_type() {
AccountType::FungibleFaucet => FungibleAsset::try_from(value).map(Asset::from),
AccountType::NonFungibleFaucet => NonFungibleAsset::try_from(value).map(Asset::from),
_ => Err(AssetError::InvalidFaucetAccountIdPrefix(prefix)),
}
}
}
impl Serializable for Asset {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
match self {
Asset::Fungible(fungible_asset) => fungible_asset.write_into(target),
Asset::NonFungible(non_fungible_asset) => non_fungible_asset.write_into(target),
}
}
fn get_size_hint(&self) -> usize {
match self {
Asset::Fungible(fungible_asset) => fungible_asset.get_size_hint(),
Asset::NonFungible(non_fungible_asset) => non_fungible_asset.get_size_hint(),
}
}
}
impl Deserializable for Asset {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let faucet_id_prefix: AccountIdPrefix = source.read()?;
match faucet_id_prefix.account_type() {
AccountType::FungibleFaucet => {
FungibleAsset::deserialize_with_faucet_id_prefix(faucet_id_prefix, source)
.map(Asset::from)
},
AccountType::NonFungibleFaucet => {
NonFungibleAsset::deserialize_with_faucet_id_prefix(faucet_id_prefix, source)
.map(Asset::from)
},
other_type => Err(DeserializationError::InvalidValue(format!(
"failed to deserialize asset: expected an account ID prefix of type faucet, found {other_type:?}"
))),
}
}
}
fn is_not_a_non_fungible_asset(asset: Word) -> bool {
match AccountIdPrefix::try_from(asset[3]) {
Ok(prefix) => {
matches!(prefix.account_type(), AccountType::FungibleFaucet)
},
Err(_err) => {
#[cfg(debug_assertions)]
panic!("invalid account ID prefix passed to is_not_a_non_fungible_asset: {_err}");
#[cfg(not(debug_assertions))]
false
},
}
}
#[cfg(test)]
mod tests {
use miden_crypto::Word;
use miden_crypto::utils::{Deserializable, Serializable};
use super::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails};
use crate::account::{AccountId, AccountIdPrefix};
use crate::testing::account_id::{
ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3,
ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1,
};
#[test]
fn test_asset_serde() {
for fungible_account_id in [
ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3,
] {
let account_id = AccountId::try_from(fungible_account_id).unwrap();
let fungible_asset: Asset = FungibleAsset::new(account_id, 10).unwrap().into();
assert_eq!(fungible_asset, Asset::read_from_bytes(&fungible_asset.to_bytes()).unwrap());
}
for non_fungible_account_id in [
ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1,
] {
let account_id = AccountId::try_from(non_fungible_account_id).unwrap();
let details = NonFungibleAssetDetails::new(account_id.prefix(), vec![1, 2, 3]).unwrap();
let non_fungible_asset: Asset = NonFungibleAsset::new(&details).unwrap().into();
assert_eq!(
non_fungible_asset,
Asset::read_from_bytes(&non_fungible_asset.to_bytes()).unwrap()
);
}
}
#[test]
fn test_new_unchecked() {
for fungible_account_id in [
ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3,
] {
let account_id = AccountId::try_from(fungible_account_id).unwrap();
let fungible_asset: Asset = FungibleAsset::new(account_id, 10).unwrap().into();
assert_eq!(fungible_asset, Asset::new_unchecked(Word::from(&fungible_asset)));
}
for non_fungible_account_id in [
ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1,
] {
let account_id = AccountId::try_from(non_fungible_account_id).unwrap();
let details = NonFungibleAssetDetails::new(account_id.prefix(), vec![1, 2, 3]).unwrap();
let non_fungible_asset: Asset = NonFungibleAsset::new(&details).unwrap().into();
assert_eq!(non_fungible_asset, Asset::new_unchecked(Word::from(non_fungible_asset)));
}
}
#[test]
fn test_account_id_prefix_is_in_first_serialized_felt() {
for asset in [FungibleAsset::mock(300), NonFungibleAsset::mock(&[0xaa, 0xbb])] {
let serialized_asset = asset.to_bytes();
let prefix = AccountIdPrefix::read_from_bytes(&serialized_asset).unwrap();
assert_eq!(prefix, asset.faucet_id_prefix());
}
}
}