1use super::{
2 AssetError, Felt, Hasher, TokenSymbolError, Word, ZERO,
3 account::AccountType,
4 utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
5};
6use crate::account::AccountIdPrefix;
7
8mod fungible;
9pub use fungible::FungibleAsset;
10
11mod nonfungible;
12pub use nonfungible::{NonFungibleAsset, NonFungibleAssetDetails};
13
14mod token_symbol;
15pub use token_symbol::TokenSymbol;
16
17mod vault;
18pub use vault::{AssetVault, PartialVault};
19
20#[derive(Debug, Copy, Clone, PartialEq, Eq)]
82pub enum Asset {
83 Fungible(FungibleAsset),
84 NonFungible(NonFungibleAsset),
85}
86
87impl Asset {
88 pub(crate) fn new_unchecked(value: Word) -> Asset {
90 if is_not_a_non_fungible_asset(value) {
91 Asset::Fungible(FungibleAsset::new_unchecked(value))
92 } else {
93 Asset::NonFungible(unsafe { NonFungibleAsset::new_unchecked(value) })
94 }
95 }
96
97 pub fn is_same(&self, other: &Self) -> bool {
103 use Asset::*;
104 match (self, other) {
105 (Fungible(l), Fungible(r)) => l.is_from_same_faucet(r),
106 (NonFungible(l), NonFungible(r)) => l == r,
107 _ => false,
108 }
109 }
110
111 pub const fn is_fungible(&self) -> bool {
113 matches!(self, Self::Fungible(_))
114 }
115
116 pub const fn is_non_fungible(&self) -> bool {
118 matches!(self, Self::NonFungible(_))
119 }
120
121 pub fn faucet_id_prefix(&self) -> AccountIdPrefix {
126 match self {
127 Self::Fungible(asset) => asset.faucet_id_prefix(),
128 Self::NonFungible(asset) => asset.faucet_id_prefix(),
129 }
130 }
131
132 pub fn vault_key(&self) -> Word {
134 match self {
135 Self::Fungible(asset) => asset.vault_key(),
136 Self::NonFungible(asset) => asset.vault_key(),
137 }
138 }
139
140 pub fn unwrap_fungible(&self) -> FungibleAsset {
146 match self {
147 Asset::Fungible(asset) => *asset,
148 Asset::NonFungible(_) => panic!("the asset is non-fungible"),
149 }
150 }
151
152 pub fn unwrap_non_fungible(&self) -> NonFungibleAsset {
158 match self {
159 Asset::Fungible(_) => panic!("the asset is fungible"),
160 Asset::NonFungible(asset) => *asset,
161 }
162 }
163}
164
165impl From<Asset> for Word {
166 fn from(asset: Asset) -> Self {
167 match asset {
168 Asset::Fungible(asset) => asset.into(),
169 Asset::NonFungible(asset) => asset.into(),
170 }
171 }
172}
173
174impl From<&Asset> for Word {
175 fn from(value: &Asset) -> Self {
176 (*value).into()
177 }
178}
179
180impl TryFrom<&Word> for Asset {
181 type Error = AssetError;
182
183 fn try_from(value: &Word) -> Result<Self, Self::Error> {
184 (*value).try_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 if is_not_a_non_fungible_asset(value) {
193 FungibleAsset::try_from(value).map(Asset::from)
194 } else {
195 NonFungibleAsset::try_from(value).map(Asset::from)
196 }
197 }
198}
199
200impl Serializable for Asset {
204 fn write_into<W: ByteWriter>(&self, target: &mut W) {
205 match self {
206 Asset::Fungible(fungible_asset) => fungible_asset.write_into(target),
207 Asset::NonFungible(non_fungible_asset) => non_fungible_asset.write_into(target),
208 }
209 }
210
211 fn get_size_hint(&self) -> usize {
212 match self {
213 Asset::Fungible(fungible_asset) => fungible_asset.get_size_hint(),
214 Asset::NonFungible(non_fungible_asset) => non_fungible_asset.get_size_hint(),
215 }
216 }
217}
218
219impl Deserializable for Asset {
220 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
221 let faucet_id_prefix: AccountIdPrefix = source.read()?;
224
225 match faucet_id_prefix.account_type() {
226 AccountType::FungibleFaucet => {
227 FungibleAsset::deserialize_with_faucet_id_prefix(faucet_id_prefix, source)
228 .map(Asset::from)
229 },
230 AccountType::NonFungibleFaucet => {
231 NonFungibleAsset::deserialize_with_faucet_id_prefix(faucet_id_prefix, source)
232 .map(Asset::from)
233 },
234 other_type => Err(DeserializationError::InvalidValue(format!(
235 "failed to deserialize asset: expected an account ID prefix of type faucet, found {other_type:?}"
236 ))),
237 }
238 }
239}
240
241fn is_not_a_non_fungible_asset(asset: Word) -> bool {
249 match AccountIdPrefix::try_from(asset[3]) {
250 Ok(prefix) => {
251 matches!(prefix.account_type(), AccountType::FungibleFaucet)
252 },
253 Err(_err) => {
254 #[cfg(debug_assertions)]
255 panic!("invalid account ID prefix passed to is_not_a_non_fungible_asset: {_err}");
256 #[cfg(not(debug_assertions))]
257 false
258 },
259 }
260}
261
262#[cfg(test)]
266mod tests {
267
268 use miden_crypto::{
269 Word,
270 utils::{Deserializable, Serializable},
271 };
272
273 use super::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails};
274 use crate::{
275 account::{AccountId, AccountIdPrefix},
276 testing::account_id::{
277 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
278 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1,
279 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3,
280 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1,
281 },
282 };
283
284 #[test]
285 fn test_asset_serde() {
286 for fungible_account_id in [
287 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
288 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
289 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1,
290 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
291 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3,
292 ] {
293 let account_id = AccountId::try_from(fungible_account_id).unwrap();
294 let fungible_asset: Asset = FungibleAsset::new(account_id, 10).unwrap().into();
295 assert_eq!(fungible_asset, Asset::read_from_bytes(&fungible_asset.to_bytes()).unwrap());
296 }
297
298 for non_fungible_account_id in [
299 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
300 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
301 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1,
302 ] {
303 let account_id = AccountId::try_from(non_fungible_account_id).unwrap();
304 let details = NonFungibleAssetDetails::new(account_id.prefix(), vec![1, 2, 3]).unwrap();
305 let non_fungible_asset: Asset = NonFungibleAsset::new(&details).unwrap().into();
306 assert_eq!(
307 non_fungible_asset,
308 Asset::read_from_bytes(&non_fungible_asset.to_bytes()).unwrap()
309 );
310 }
311 }
312
313 #[test]
314 fn test_new_unchecked() {
315 for fungible_account_id in [
316 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
317 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
318 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1,
319 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
320 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3,
321 ] {
322 let account_id = AccountId::try_from(fungible_account_id).unwrap();
323 let fungible_asset: Asset = FungibleAsset::new(account_id, 10).unwrap().into();
324 assert_eq!(fungible_asset, Asset::new_unchecked(Word::from(&fungible_asset)));
325 }
326
327 for non_fungible_account_id in [
328 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
329 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
330 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1,
331 ] {
332 let account_id = AccountId::try_from(non_fungible_account_id).unwrap();
333 let details = NonFungibleAssetDetails::new(account_id.prefix(), vec![1, 2, 3]).unwrap();
334 let non_fungible_asset: Asset = NonFungibleAsset::new(&details).unwrap().into();
335 assert_eq!(non_fungible_asset, Asset::new_unchecked(Word::from(non_fungible_asset)));
336 }
337 }
338
339 #[test]
343 fn test_account_id_prefix_is_in_first_serialized_felt() {
344 for asset in [FungibleAsset::mock(300), NonFungibleAsset::mock(&[0xaa, 0xbb])] {
345 let serialized_asset = asset.to_bytes();
346 let prefix = AccountIdPrefix::read_from_bytes(&serialized_asset).unwrap();
347 assert_eq!(prefix, asset.faucet_id_prefix());
348 }
349 }
350}