1use super::errors::{AssetError, TokenSymbolError};
2use super::utils::serde::{
3 ByteReader,
4 ByteWriter,
5 Deserializable,
6 DeserializationError,
7 Serializable,
8};
9use super::{Felt, Word};
10use crate::account::AccountId;
11
12mod asset_amount;
13pub use asset_amount::AssetAmount;
14
15mod fungible;
16
17pub use fungible::FungibleAsset;
18
19mod nonfungible;
20
21pub use nonfungible::{NonFungibleAsset, NonFungibleAssetDetails};
22
23mod token_symbol;
24pub use token_symbol::TokenSymbol;
25
26mod asset_callbacks;
27pub use asset_callbacks::AssetCallbacks;
28
29mod asset_callbacks_flag;
30pub use asset_callbacks_flag::AssetCallbackFlag;
31
32mod asset_composition;
33pub use asset_composition::AssetComposition;
34
35mod vault;
36pub use vault::{AssetId, AssetVault, AssetVaultKey, AssetWitness, PartialVault};
37
38#[derive(Debug, Copy, Clone, PartialEq, Eq)]
100pub enum Asset {
101 Fungible(FungibleAsset),
102 NonFungible(NonFungibleAsset),
103}
104
105impl Asset {
106 pub fn from_key_value(key: AssetVaultKey, value: Word) -> Result<Self, AssetError> {
113 match key.composition() {
114 AssetComposition::Fungible => {
115 FungibleAsset::from_key_value(key, value).map(Asset::Fungible)
116 },
117 AssetComposition::None => {
118 NonFungibleAsset::from_key_value(key, value).map(Asset::NonFungible)
119 },
120 AssetComposition::Custom => {
121 Err(AssetError::UnsupportedAssetComposition(AssetComposition::Custom))
122 },
123 }
124 }
125
126 pub fn from_key_value_words(key: Word, value: Word) -> Result<Self, AssetError> {
136 let vault_key = AssetVaultKey::try_from(key)?;
137 Self::from_key_value(vault_key, value)
138 }
139
140 pub fn with_callbacks(self, callbacks: AssetCallbackFlag) -> Self {
142 match self {
143 Asset::Fungible(fungible_asset) => fungible_asset.with_callbacks(callbacks).into(),
144 Asset::NonFungible(non_fungible_asset) => {
145 non_fungible_asset.with_callbacks(callbacks).into()
146 },
147 }
148 }
149
150 pub fn is_same(&self, other: &Self) -> bool {
154 self.vault_key() == other.vault_key()
155 }
156
157 pub fn is_fungible(&self) -> bool {
159 matches!(self, Self::Fungible(_))
160 }
161
162 pub fn is_non_fungible(&self) -> bool {
164 matches!(self, Self::NonFungible(_))
165 }
166
167 pub fn faucet_id(&self) -> AccountId {
169 match self {
170 Self::Fungible(asset) => asset.faucet_id(),
171 Self::NonFungible(asset) => asset.faucet_id(),
172 }
173 }
174
175 pub fn vault_key(&self) -> AssetVaultKey {
177 match self {
178 Self::Fungible(asset) => asset.vault_key(),
179 Self::NonFungible(asset) => asset.vault_key(),
180 }
181 }
182
183 pub fn to_key_word(&self) -> Word {
185 self.vault_key().to_word()
186 }
187
188 pub fn to_value_word(&self) -> Word {
190 match self {
191 Asset::Fungible(fungible_asset) => fungible_asset.to_value_word(),
192 Asset::NonFungible(non_fungible_asset) => non_fungible_asset.to_value_word(),
193 }
194 }
195
196 pub fn as_elements(&self) -> [Felt; 8] {
201 let mut elements = [Felt::ZERO; 8];
202 elements[0..4].copy_from_slice(self.to_key_word().as_elements());
203 elements[4..8].copy_from_slice(self.to_value_word().as_elements());
204 elements
205 }
206
207 pub fn unwrap_fungible(&self) -> FungibleAsset {
213 match self {
214 Asset::Fungible(asset) => *asset,
215 Asset::NonFungible(_) => panic!("the asset is non-fungible"),
216 }
217 }
218
219 pub fn unwrap_non_fungible(&self) -> NonFungibleAsset {
225 match self {
226 Asset::Fungible(_) => panic!("the asset is fungible"),
227 Asset::NonFungible(asset) => *asset,
228 }
229 }
230}
231
232impl Serializable for Asset {
236 fn write_into<W: ByteWriter>(&self, target: &mut W) {
237 match self {
238 Asset::Fungible(fungible_asset) => fungible_asset.write_into(target),
239 Asset::NonFungible(non_fungible_asset) => non_fungible_asset.write_into(target),
240 }
241 }
242
243 fn get_size_hint(&self) -> usize {
244 match self {
245 Asset::Fungible(fungible_asset) => fungible_asset.get_size_hint(),
246 Asset::NonFungible(non_fungible_asset) => non_fungible_asset.get_size_hint(),
247 }
248 }
249}
250
251impl Deserializable for Asset {
252 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
253 let composition: AssetComposition = source.read()?;
256 match composition {
257 AssetComposition::Fungible => FungibleAsset::deserialize_body(source).map(Asset::from),
258 AssetComposition::None => NonFungibleAsset::deserialize_body(source).map(Asset::from),
259 AssetComposition::Custom => Err(DeserializationError::InvalidValue(
260 "Custom asset composition is not supported".into(),
261 )),
262 }
263 }
264}
265
266#[cfg(test)]
270mod tests {
271
272 use assert_matches::assert_matches;
273 use miden_core::Word;
274 use miden_crypto::utils::{Deserializable, Serializable};
275
276 use super::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails};
277 use crate::Felt;
278 use crate::account::AccountId;
279 use crate::asset::{AssetCallbackFlag, AssetComposition, AssetId, AssetVaultKey};
280 use crate::errors::AssetError;
281 use crate::testing::account_id::{
282 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
283 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
284 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
285 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1,
286 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
287 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3,
288 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
289 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1,
290 };
291
292 pub(super) fn asset_metadata(key: AssetVaultKey) -> u8 {
294 (key.to_word()[2].as_canonical_u64() & AssetVaultKey::METADATA_BYTE_MASK as u64) as u8
295 }
296
297 pub(super) fn set_asset_metadata(key: AssetVaultKey, byte: u8) -> Word {
299 let mut key = key.to_word();
300 let raw = key[2].as_canonical_u64();
301 let new_raw = (raw & !(AssetVaultKey::METADATA_BYTE_MASK as u64)) | byte as u64;
302 key[2] = Felt::try_from(new_raw).expect("clearing lower bits should produce a valid felt");
303 key
304 }
305
306 #[test]
308 fn test_asset_serde() -> anyhow::Result<()> {
309 for fungible_account_id in [
310 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
311 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
312 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1,
313 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
314 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3,
315 ] {
316 let account_id = AccountId::try_from(fungible_account_id).unwrap();
317 let fungible_asset: Asset = FungibleAsset::new(account_id, 10).unwrap().into();
318 assert_eq!(fungible_asset, Asset::read_from_bytes(&fungible_asset.to_bytes()).unwrap());
319 assert_eq!(
320 fungible_asset,
321 Asset::from_key_value_words(
322 fungible_asset.to_key_word(),
323 fungible_asset.to_value_word()
324 )?,
325 );
326 }
327
328 for non_fungible_account_id in [
329 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
330 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
331 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1,
332 ] {
333 let account_id = AccountId::try_from(non_fungible_account_id).unwrap();
334 let details = NonFungibleAssetDetails::new(account_id, vec![1, 2, 3]);
335 let non_fungible_asset: Asset = NonFungibleAsset::new(&details).into();
336 assert_eq!(
337 non_fungible_asset,
338 Asset::read_from_bytes(&non_fungible_asset.to_bytes()).unwrap()
339 );
340 assert_eq!(
341 non_fungible_asset,
342 Asset::from_key_value_words(
343 non_fungible_asset.to_key_word(),
344 non_fungible_asset.to_value_word()
345 )?
346 );
347 }
348
349 Ok(())
350 }
351
352 #[test]
355 fn test_composition_byte_is_serialized_first() {
356 let fungible_bytes = FungibleAsset::mock(300).to_bytes();
357 assert_eq!(fungible_bytes[0], AssetComposition::Fungible.as_u8());
358
359 let non_fungible_bytes = NonFungibleAsset::mock(&[0xaa, 0xbb]).to_bytes();
360 assert_eq!(non_fungible_bytes[0], AssetComposition::None.as_u8());
361 }
362
363 #[test]
366 fn test_from_key_value_rejects_custom_composition() -> anyhow::Result<()> {
367 let err = AssetVaultKey::new(
368 AssetId::default(),
369 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET.try_into()?,
370 AssetComposition::Custom,
371 AssetCallbackFlag::Disabled,
372 )
373 .unwrap_err();
374
375 assert_matches!(err, AssetError::UnsupportedAssetComposition(AssetComposition::Custom));
376
377 Ok(())
378 }
379}