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