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