1use super::{
2 account::AccountType,
3 utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
4 AssetError, Felt, Hasher, Word, ZERO,
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;
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 {
142 match self {
143 Asset::Fungible(asset) => *asset,
144 Asset::NonFungible(_) => panic!("the asset is non-fungible"),
145 }
146 }
147
148 pub fn unwrap_non_fungible(&mut self) -> NonFungibleAsset {
150 match self {
151 Asset::Fungible(_) => panic!("the asset is fungible"),
152 Asset::NonFungible(asset) => *asset,
153 }
154 }
155}
156
157impl From<Asset> for Word {
158 fn from(asset: Asset) -> Self {
159 match asset {
160 Asset::Fungible(asset) => asset.into(),
161 Asset::NonFungible(asset) => asset.into(),
162 }
163 }
164}
165
166impl From<&Asset> for Word {
167 fn from(value: &Asset) -> Self {
168 (*value).into()
169 }
170}
171
172impl TryFrom<&Word> for Asset {
173 type Error = AssetError;
174
175 fn try_from(value: &Word) -> Result<Self, Self::Error> {
176 (*value).try_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 if is_not_a_non_fungible_asset(value) {
185 FungibleAsset::try_from(value).map(Asset::from)
186 } else {
187 NonFungibleAsset::try_from(value).map(Asset::from)
188 }
189 }
190}
191
192impl Serializable for Asset {
196 fn write_into<W: ByteWriter>(&self, target: &mut W) {
197 match self {
198 Asset::Fungible(fungible_asset) => fungible_asset.write_into(target),
199 Asset::NonFungible(non_fungible_asset) => non_fungible_asset.write_into(target),
200 }
201 }
202
203 fn get_size_hint(&self) -> usize {
204 match self {
205 Asset::Fungible(fungible_asset) => fungible_asset.get_size_hint(),
206 Asset::NonFungible(non_fungible_asset) => non_fungible_asset.get_size_hint(),
207 }
208 }
209}
210
211impl Deserializable for Asset {
212 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
213 let faucet_id_prefix: AccountIdPrefix = source.read()?;
216
217 match faucet_id_prefix.account_type() {
218 AccountType::FungibleFaucet => {
219 FungibleAsset::deserialize_with_faucet_id_prefix(faucet_id_prefix, source).map(Asset::from)
220 },
221 AccountType::NonFungibleFaucet => {
222 NonFungibleAsset::deserialize_with_faucet_id_prefix (faucet_id_prefix, source).map(Asset::from)
223 },
224 other_type => {
225 Err(DeserializationError::InvalidValue(format!(
226 "failed to deserialize asset: expected an account ID prefix of type faucet, found {other_type:?}"
227 )))
228 },
229 }
230 }
231}
232
233fn is_not_a_non_fungible_asset(asset: Word) -> bool {
241 match AccountIdPrefix::try_from(asset[3]) {
242 Ok(prefix) => {
243 matches!(prefix.account_type(), AccountType::FungibleFaucet)
244 },
245 Err(_err) => {
246 #[cfg(debug_assertions)]
247 panic!("invalid account ID prefix passed to is_not_a_non_fungible_asset: {_err}");
248 #[cfg(not(debug_assertions))]
249 false
250 },
251 }
252}
253
254#[cfg(test)]
258mod tests {
259
260 use miden_crypto::{
261 utils::{Deserializable, Serializable},
262 Word,
263 };
264
265 use super::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails};
266 use crate::{
267 account::{AccountId, AccountIdPrefix},
268 testing::account_id::{
269 ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN,
270 ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2,
271 ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_3, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN,
272 ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN_1,
273 },
274 };
275
276 #[test]
277 fn test_asset_serde() {
278 for fungible_account_id in [
279 ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN,
280 ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN,
281 ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1,
282 ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2,
283 ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_3,
284 ] {
285 let account_id = AccountId::try_from(fungible_account_id).unwrap();
286 let fungible_asset: Asset = FungibleAsset::new(account_id, 10).unwrap().into();
287 assert_eq!(fungible_asset, Asset::read_from_bytes(&fungible_asset.to_bytes()).unwrap());
288 }
289
290 for non_fungible_account_id in [
291 ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN,
292 ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN,
293 ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN_1,
294 ] {
295 let account_id = AccountId::try_from(non_fungible_account_id).unwrap();
296 let details = NonFungibleAssetDetails::new(account_id.prefix(), vec![1, 2, 3]).unwrap();
297 let non_fungible_asset: Asset = NonFungibleAsset::new(&details).unwrap().into();
298 assert_eq!(
299 non_fungible_asset,
300 Asset::read_from_bytes(&non_fungible_asset.to_bytes()).unwrap()
301 );
302 }
303 }
304
305 #[test]
306 fn test_new_unchecked() {
307 for fungible_account_id in [
308 ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN,
309 ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN,
310 ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1,
311 ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2,
312 ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_3,
313 ] {
314 let account_id = AccountId::try_from(fungible_account_id).unwrap();
315 let fungible_asset: Asset = FungibleAsset::new(account_id, 10).unwrap().into();
316 assert_eq!(fungible_asset, Asset::new_unchecked(Word::from(&fungible_asset)));
317 }
318
319 for non_fungible_account_id in [
320 ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN,
321 ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN,
322 ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN_1,
323 ] {
324 let account_id = AccountId::try_from(non_fungible_account_id).unwrap();
325 let details = NonFungibleAssetDetails::new(account_id.prefix(), vec![1, 2, 3]).unwrap();
326 let non_fungible_asset: Asset = NonFungibleAsset::new(&details).unwrap().into();
327 assert_eq!(non_fungible_asset, Asset::new_unchecked(Word::from(non_fungible_asset)));
328 }
329 }
330
331 #[test]
335 fn test_account_id_prefix_is_in_first_serialized_felt() {
336 for asset in [FungibleAsset::mock(300), NonFungibleAsset::mock(&[0xaa, 0xbb])] {
337 let serialized_asset = asset.to_bytes();
338 let prefix = AccountIdPrefix::read_from_bytes(&serialized_asset).unwrap();
339 assert_eq!(prefix, asset.faucet_id_prefix());
340 }
341 }
342}