Skip to content

Commit

Permalink
feat(objects): Implement Serializable for FungibleAsset
Browse files Browse the repository at this point in the history
  • Loading branch information
PhilippGackstatter committed Oct 9, 2024
1 parent 8231598 commit 80e71f5
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 103 deletions.
62 changes: 41 additions & 21 deletions objects/src/assets/fungible.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -145,16 +148,6 @@ impl From<FungibleAsset> for Word {
}
}

impl From<FungibleAsset> 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<FungibleAsset> for Asset {
fn from(asset: FungibleAsset) -> Self {
Asset::Fungible(asset)
Expand All @@ -175,17 +168,44 @@ impl TryFrom<Word> 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<Self, Self::Error> {
let word = parse_word(value)?;
Self::try_from(word)
// SERIALIZATION
// ================================================================================================

impl Serializable for FungibleAsset {
fn write_into<W: ByteWriter>(&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<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
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<R: ByteReader>(
faucet_id: AccountId,
source: &mut R,
) -> Result<Self, DeserializationError> {
let amount: u64 = source.read()?;
FungibleAsset::new(faucet_id, amount)
.map_err(|err| DeserializationError::InvalidValue(err.to_string()))
}
}
83 changes: 27 additions & 56 deletions objects/src/assets/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use alloc::string::ToString;

use super::{
accounts::{AccountId, AccountType, ACCOUNT_ISFAUCET_MASK},
utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
Expand Down Expand Up @@ -152,21 +150,6 @@ impl From<&Asset> for Word {
}
}

impl From<Asset> 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;

Expand All @@ -187,64 +170,51 @@ impl TryFrom<Word> for Asset {
}
}

impl TryFrom<[u8; Asset::SERIALIZED_SIZE]> for Asset {
type Error = AssetError;

fn try_from(value: [u8; Asset::SERIALIZED_SIZE]) -> Result<Self, Self::Error> {
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<Self, Self::Error> {
(*value).try_into()
}
}

// SERIALIZATION
// ================================================================================================

impl Serializable for Asset {
fn write_into<W: ByteWriter>(&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<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
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<Word, AssetError> {
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, AssetError> {
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
Expand All @@ -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,
Expand Down
52 changes: 26 additions & 26 deletions objects/src/assets/nonfungible.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -149,17 +146,6 @@ impl From<NonFungibleAsset> for Word {
}
}

impl From<NonFungibleAsset> 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<NonFungibleAsset> for Asset {
fn from(asset: NonFungibleAsset) -> Self {
Asset::NonFungible(asset)
Expand All @@ -176,24 +162,23 @@ impl TryFrom<Word> for NonFungibleAsset {
}
}

impl TryFrom<[u8; 32]> for NonFungibleAsset {
type Error = AssetError;

fn try_from(value: [u8; 32]) -> Result<Self, Self::Error> {
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<W: ByteWriter>(&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 {
Expand All @@ -204,11 +189,26 @@ impl Serializable for NonFungibleAsset {
impl Deserializable for NonFungibleAsset {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
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<R: ByteReader>(
faucet_id: AccountId,
source: &mut R,
) -> Result<Self, DeserializationError> {
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
// ================================================================================================

Expand Down

0 comments on commit 80e71f5

Please sign in to comment.