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}