diff --git a/objects/src/assets/fungible.rs b/objects/src/assets/fungible.rs index 1622ed1d0..9b6e8267d 100644 --- a/objects/src/assets/fungible.rs +++ b/objects/src/assets/fungible.rs @@ -1,11 +1,14 @@ use alloc::string::ToString; use core::fmt; -use vm_core::FieldElement; +use vm_core::{ + utils::{ByteReader, ByteWriter, Deserializable, Serializable}, + FieldElement, +}; +use vm_processor::DeserializationError; use super::{ - is_not_a_non_fungible_asset, parse_word, AccountId, AccountType, Asset, AssetError, Felt, Word, - ZERO, + is_not_a_non_fungible_asset, AccountId, AccountType, Asset, AssetError, Felt, Word, ZERO, }; // FUNGIBLE ASSET @@ -145,16 +148,6 @@ impl From for Word { } } -impl From for [u8; 32] { - fn from(asset: FungibleAsset) -> Self { - let mut result = [0_u8; 32]; - let id_bytes: [u8; 8] = asset.faucet_id.into(); - result[..8].copy_from_slice(&asset.amount.to_le_bytes()); - result[24..].copy_from_slice(&id_bytes); - result - } -} - impl From for Asset { fn from(asset: FungibleAsset) -> Self { Asset::Fungible(asset) @@ -175,17 +168,44 @@ impl TryFrom for FungibleAsset { } } -impl TryFrom<[u8; 32]> for FungibleAsset { - type Error = AssetError; +impl fmt::Display for FungibleAsset { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} - fn try_from(value: [u8; 32]) -> Result { - let word = parse_word(value)?; - Self::try_from(word) +// SERIALIZATION +// ================================================================================================ + +impl Serializable for FungibleAsset { + fn write_into(&self, target: &mut W) { + // All assets should serialize their faucet ID at the first position to allow them to be + // easily distinguishable during deserialization. + target.write(self.faucet_id); + target.write(self.amount); + } + + fn get_size_hint(&self) -> usize { + self.faucet_id.get_size_hint() + self.amount.get_size_hint() } } -impl fmt::Display for FungibleAsset { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) +impl Deserializable for FungibleAsset { + fn read_from(source: &mut R) -> Result { + let faucet_id: AccountId = source.read()?; + FungibleAsset::deserialize_with_account_id(faucet_id, source) + } +} + +impl FungibleAsset { + /// Deserializes a [`FungibleAsset`] from an [`AccountId`] and the remaining data from the given + /// `source`. + pub(super) fn deserialize_with_account_id( + faucet_id: AccountId, + source: &mut R, + ) -> Result { + let amount: u64 = source.read()?; + FungibleAsset::new(faucet_id, amount) + .map_err(|err| DeserializationError::InvalidValue(err.to_string())) } } diff --git a/objects/src/assets/mod.rs b/objects/src/assets/mod.rs index 59251acd6..1b8c046d1 100644 --- a/objects/src/assets/mod.rs +++ b/objects/src/assets/mod.rs @@ -1,5 +1,3 @@ -use alloc::string::ToString; - use super::{ accounts::{AccountId, AccountType, ACCOUNT_ISFAUCET_MASK}, utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, @@ -152,21 +150,6 @@ impl From<&Asset> for Word { } } -impl From for [u8; Asset::SERIALIZED_SIZE] { - fn from(asset: Asset) -> Self { - match asset { - Asset::Fungible(asset) => asset.into(), - Asset::NonFungible(asset) => asset.into(), - } - } -} - -impl From<&Asset> for [u8; Asset::SERIALIZED_SIZE] { - fn from(value: &Asset) -> Self { - (*value).into() - } -} - impl TryFrom<&Word> for Asset { type Error = AssetError; @@ -187,64 +170,51 @@ impl TryFrom for Asset { } } -impl TryFrom<[u8; Asset::SERIALIZED_SIZE]> for Asset { - type Error = AssetError; - - fn try_from(value: [u8; Asset::SERIALIZED_SIZE]) -> Result { - parse_word(value)?.try_into() - } -} - -impl TryFrom<&[u8; Asset::SERIALIZED_SIZE]> for Asset { - type Error = AssetError; - - fn try_from(value: &[u8; Asset::SERIALIZED_SIZE]) -> Result { - (*value).try_into() - } -} - // SERIALIZATION // ================================================================================================ impl Serializable for Asset { fn write_into(&self, target: &mut W) { - let data: [u8; Asset::SERIALIZED_SIZE] = self.into(); - target.write_bytes(&data); + match self { + Asset::Fungible(fungible_asset) => fungible_asset.write_into(target), + Asset::NonFungible(non_fungible_asset) => non_fungible_asset.write_into(target), + } } fn get_size_hint(&self) -> usize { - Asset::SERIALIZED_SIZE + match self { + Asset::Fungible(fungible_asset) => fungible_asset.get_size_hint(), + Asset::NonFungible(non_fungible_asset) => non_fungible_asset.get_size_hint(), + } } } impl Deserializable for Asset { fn read_from(source: &mut R) -> Result { - let data_vec = source.read_vec(Asset::SERIALIZED_SIZE)?; - let data_array: [u8; Asset::SERIALIZED_SIZE] = - data_vec.try_into().expect("Vec must be of size 32"); - - let asset = Asset::try_from(&data_array) - .map_err(|error| DeserializationError::InvalidValue(format!("{error}")))?; - Ok(asset) + // Both asset types have their faucet ID as the first element, so we can use it to inspect + // what type of asset it is. + let account_id: AccountId = source.read()?; + let account_type = account_id.account_type(); + + match account_type { + AccountType::FungibleFaucet => { + FungibleAsset::deserialize_with_account_id(account_id, source).map(Asset::from) + }, + AccountType::NonFungibleFaucet => { + NonFungibleAsset::deserialize_with_account_id(account_id, source).map(Asset::from) + }, + other_type => { + Err(DeserializationError::InvalidValue(format!( + "failed to deserialize asset: expected an account ID of type faucet, found {other_type:?}" + ))) + }, + } } } // HELPER FUNCTIONS // ================================================================================================ -fn parse_word(bytes: [u8; Asset::SERIALIZED_SIZE]) -> Result { - Ok([ - parse_felt(&bytes[..8])?, - parse_felt(&bytes[8..16])?, - parse_felt(&bytes[16..24])?, - parse_felt(&bytes[24..])?, - ]) -} - -fn parse_felt(bytes: &[u8]) -> Result { - Felt::try_from(bytes).map_err(|err| AssetError::InvalidFieldElement(err.to_string())) -} - /// Returns `true` if asset in [Word] is not a non-fungible asset. /// /// Note: this does not mean that the word is a fungible asset as the word may contain an value @@ -260,6 +230,7 @@ fn is_not_a_non_fungible_asset(asset: Word) -> bool { #[cfg(test)] mod tests { + use miden_crypto::{ utils::{Deserializable, Serializable}, Word, diff --git a/objects/src/assets/nonfungible.rs b/objects/src/assets/nonfungible.rs index 7de393e26..fa9c2a3a2 100644 --- a/objects/src/assets/nonfungible.rs +++ b/objects/src/assets/nonfungible.rs @@ -3,10 +3,7 @@ use core::fmt; use vm_core::{FieldElement, WORD_SIZE}; -use super::{ - parse_word, AccountId, AccountType, Asset, AssetError, Felt, Hasher, Word, - ACCOUNT_ISFAUCET_MASK, -}; +use super::{AccountId, AccountType, Asset, AssetError, Felt, Hasher, Word, ACCOUNT_ISFAUCET_MASK}; use crate::{ utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, Digest, @@ -149,17 +146,6 @@ impl From for Word { } } -impl From for [u8; 32] { - fn from(asset: NonFungibleAsset) -> Self { - let mut result = [0_u8; 32]; - result[..8].copy_from_slice(&asset.0[0].as_int().to_le_bytes()); - result[8..16].copy_from_slice(&asset.0[FAUCET_ID_POS].as_int().to_le_bytes()); - result[16..24].copy_from_slice(&asset.0[2].as_int().to_le_bytes()); - result[24..].copy_from_slice(&asset.0[3].as_int().to_le_bytes()); - result - } -} - impl From for Asset { fn from(asset: NonFungibleAsset) -> Self { Asset::NonFungible(asset) @@ -176,24 +162,23 @@ impl TryFrom for NonFungibleAsset { } } -impl TryFrom<[u8; 32]> for NonFungibleAsset { - type Error = AssetError; - - fn try_from(value: [u8; 32]) -> Result { - let word = parse_word(value)?; - Self::try_from(word) - } -} - impl fmt::Display for NonFungibleAsset { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) } } +// SERIALIZATION +// ================================================================================================ + impl Serializable for NonFungibleAsset { fn write_into(&self, target: &mut W) { - target.write(self.0) + // All assets should serialize their faucet ID at the first position to allow them to be + // easily distinguishable during deserialization. + target.write(self.0[FAUCET_ID_POS]); + target.write(self.0[0]); + target.write(self.0[2]); + target.write(self.0[3]); } fn get_size_hint(&self) -> usize { @@ -204,11 +189,26 @@ impl Serializable for NonFungibleAsset { impl Deserializable for NonFungibleAsset { fn read_from(source: &mut R) -> Result { let value: Word = source.read()?; - Self::try_from(value).map_err(|err| DeserializationError::InvalidValue(err.to_string())) } } +impl NonFungibleAsset { + /// Deserializes a [`NonFungibleAsset`] from an [`AccountId`] and the remaining data from the + /// given `source`. + pub(super) fn deserialize_with_account_id( + faucet_id: AccountId, + source: &mut R, + ) -> Result { + let hash_0: Felt = source.read()?; + let hash_2: Felt = source.read()?; + let hash_3: Felt = source.read()?; + + NonFungibleAsset::from_parts(faucet_id, [hash_0, Felt::ZERO, hash_2, hash_3]) + .map_err(|err| DeserializationError::InvalidValue(err.to_string())) + } +} + // NON-FUNGIBLE ASSET DETAILS // ================================================================================================