1use super::account::AccountType;
2use super::utils::serde::{
3    ByteReader,
4    ByteWriter,
5    Deserializable,
6    DeserializationError,
7    Serializable,
8};
9use super::{AssetError, Felt, Hasher, TokenSymbolError, Word, ZERO};
10use crate::account::AccountIdPrefix;
11
12mod fungible;
13use alloc::boxed::Box;
14
15pub use fungible::FungibleAsset;
16
17mod nonfungible;
18pub use nonfungible::{NonFungibleAsset, NonFungibleAssetDetails};
19
20mod token_symbol;
21pub use token_symbol::TokenSymbol;
22
23mod vault;
24pub use vault::{AssetVault, PartialVault};
25
26#[derive(Debug, Copy, Clone, PartialEq, Eq)]
88pub enum Asset {
89    Fungible(FungibleAsset),
90    NonFungible(NonFungibleAsset),
91}
92
93impl Asset {
94    pub(crate) fn new_unchecked(value: Word) -> Asset {
96        if is_not_a_non_fungible_asset(value) {
97            Asset::Fungible(FungibleAsset::new_unchecked(value))
98        } else {
99            Asset::NonFungible(unsafe { NonFungibleAsset::new_unchecked(value) })
100        }
101    }
102
103    pub fn is_same(&self, other: &Self) -> bool {
109        use Asset::*;
110        match (self, other) {
111            (Fungible(l), Fungible(r)) => l.is_from_same_faucet(r),
112            (NonFungible(l), NonFungible(r)) => l == r,
113            _ => false,
114        }
115    }
116
117    pub const fn is_fungible(&self) -> bool {
119        matches!(self, Self::Fungible(_))
120    }
121
122    pub const fn is_non_fungible(&self) -> bool {
124        matches!(self, Self::NonFungible(_))
125    }
126
127    pub fn faucet_id_prefix(&self) -> AccountIdPrefix {
132        match self {
133            Self::Fungible(asset) => asset.faucet_id_prefix(),
134            Self::NonFungible(asset) => asset.faucet_id_prefix(),
135        }
136    }
137
138    pub fn vault_key(&self) -> Word {
140        match self {
141            Self::Fungible(asset) => asset.vault_key(),
142            Self::NonFungible(asset) => asset.vault_key(),
143        }
144    }
145
146    pub fn unwrap_fungible(&self) -> FungibleAsset {
152        match self {
153            Asset::Fungible(asset) => *asset,
154            Asset::NonFungible(_) => panic!("the asset is non-fungible"),
155        }
156    }
157
158    pub fn unwrap_non_fungible(&self) -> NonFungibleAsset {
164        match self {
165            Asset::Fungible(_) => panic!("the asset is fungible"),
166            Asset::NonFungible(asset) => *asset,
167        }
168    }
169}
170
171impl From<Asset> for Word {
172    fn from(asset: Asset) -> Self {
173        match asset {
174            Asset::Fungible(asset) => asset.into(),
175            Asset::NonFungible(asset) => asset.into(),
176        }
177    }
178}
179
180impl From<&Asset> for Word {
181    fn from(value: &Asset) -> Self {
182        (*value).into()
183    }
184}
185
186impl TryFrom<&Word> for Asset {
187    type Error = AssetError;
188
189    fn try_from(value: &Word) -> Result<Self, Self::Error> {
190        (*value).try_into()
191    }
192}
193
194impl TryFrom<Word> for Asset {
195    type Error = AssetError;
196
197    fn try_from(value: Word) -> Result<Self, Self::Error> {
198        AccountIdPrefix::try_from(value[3])
201            .map_err(|err| AssetError::InvalidFaucetAccountId(Box::from(err)))?;
202
203        if is_not_a_non_fungible_asset(value) {
204            FungibleAsset::try_from(value).map(Asset::from)
205        } else {
206            NonFungibleAsset::try_from(value).map(Asset::from)
207        }
208    }
209}
210
211impl Serializable for Asset {
215    fn write_into<W: ByteWriter>(&self, target: &mut W) {
216        match self {
217            Asset::Fungible(fungible_asset) => fungible_asset.write_into(target),
218            Asset::NonFungible(non_fungible_asset) => non_fungible_asset.write_into(target),
219        }
220    }
221
222    fn get_size_hint(&self) -> usize {
223        match self {
224            Asset::Fungible(fungible_asset) => fungible_asset.get_size_hint(),
225            Asset::NonFungible(non_fungible_asset) => non_fungible_asset.get_size_hint(),
226        }
227    }
228}
229
230impl Deserializable for Asset {
231    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
232        let faucet_id_prefix: AccountIdPrefix = source.read()?;
235
236        match faucet_id_prefix.account_type() {
237            AccountType::FungibleFaucet => {
238                FungibleAsset::deserialize_with_faucet_id_prefix(faucet_id_prefix, source)
239                    .map(Asset::from)
240            },
241            AccountType::NonFungibleFaucet => {
242                NonFungibleAsset::deserialize_with_faucet_id_prefix(faucet_id_prefix, source)
243                    .map(Asset::from)
244            },
245            other_type => Err(DeserializationError::InvalidValue(format!(
246                "failed to deserialize asset: expected an account ID prefix of type faucet, found {other_type:?}"
247            ))),
248        }
249    }
250}
251
252fn is_not_a_non_fungible_asset(asset: Word) -> bool {
260    match AccountIdPrefix::try_from(asset[3]) {
261        Ok(prefix) => {
262            matches!(prefix.account_type(), AccountType::FungibleFaucet)
263        },
264        Err(_err) => {
265            #[cfg(debug_assertions)]
266            panic!("invalid account ID prefix passed to is_not_a_non_fungible_asset: {_err}");
267            #[cfg(not(debug_assertions))]
268            false
269        },
270    }
271}
272
273#[cfg(test)]
277mod tests {
278
279    use miden_crypto::Word;
280    use miden_crypto::utils::{Deserializable, Serializable};
281
282    use super::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails};
283    use crate::account::{AccountId, AccountIdPrefix};
284    use crate::testing::account_id::{
285        ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
286        ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
287        ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
288        ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1,
289        ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
290        ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3,
291        ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
292        ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1,
293    };
294
295    #[test]
296    fn test_asset_serde() {
297        for fungible_account_id in [
298            ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
299            ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
300            ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1,
301            ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
302            ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3,
303        ] {
304            let account_id = AccountId::try_from(fungible_account_id).unwrap();
305            let fungible_asset: Asset = FungibleAsset::new(account_id, 10).unwrap().into();
306            assert_eq!(fungible_asset, Asset::read_from_bytes(&fungible_asset.to_bytes()).unwrap());
307        }
308
309        for non_fungible_account_id in [
310            ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
311            ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
312            ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1,
313        ] {
314            let account_id = AccountId::try_from(non_fungible_account_id).unwrap();
315            let details = NonFungibleAssetDetails::new(account_id.prefix(), vec![1, 2, 3]).unwrap();
316            let non_fungible_asset: Asset = NonFungibleAsset::new(&details).unwrap().into();
317            assert_eq!(
318                non_fungible_asset,
319                Asset::read_from_bytes(&non_fungible_asset.to_bytes()).unwrap()
320            );
321        }
322    }
323
324    #[test]
325    fn test_new_unchecked() {
326        for fungible_account_id in [
327            ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
328            ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
329            ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1,
330            ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
331            ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3,
332        ] {
333            let account_id = AccountId::try_from(fungible_account_id).unwrap();
334            let fungible_asset: Asset = FungibleAsset::new(account_id, 10).unwrap().into();
335            assert_eq!(fungible_asset, Asset::new_unchecked(Word::from(&fungible_asset)));
336        }
337
338        for non_fungible_account_id in [
339            ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
340            ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
341            ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1,
342        ] {
343            let account_id = AccountId::try_from(non_fungible_account_id).unwrap();
344            let details = NonFungibleAssetDetails::new(account_id.prefix(), vec![1, 2, 3]).unwrap();
345            let non_fungible_asset: Asset = NonFungibleAsset::new(&details).unwrap().into();
346            assert_eq!(non_fungible_asset, Asset::new_unchecked(Word::from(non_fungible_asset)));
347        }
348    }
349
350    #[test]
354    fn test_account_id_prefix_is_in_first_serialized_felt() {
355        for asset in [FungibleAsset::mock(300), NonFungibleAsset::mock(&[0xaa, 0xbb])] {
356            let serialized_asset = asset.to_bytes();
357            let prefix = AccountIdPrefix::read_from_bytes(&serialized_asset).unwrap();
358            assert_eq!(prefix, asset.faucet_id_prefix());
359        }
360    }
361}