use alloc::vec::Vec;
use crate::asset::{Asset, FungibleAsset, NonFungibleAsset};
use crate::errors::NoteError;
use crate::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
use crate::{Felt, Hasher, MAX_ASSETS_PER_NOTE, WORD_SIZE, Word, ZERO};
#[derive(Debug, Default, Clone)]
pub struct NoteAssets {
assets: Vec<Asset>,
hash: Word,
}
impl NoteAssets {
pub const MAX_NUM_ASSETS: usize = MAX_ASSETS_PER_NOTE;
pub fn new(assets: Vec<Asset>) -> Result<Self, NoteError> {
if assets.len() > Self::MAX_NUM_ASSETS {
return Err(NoteError::TooManyAssets(assets.len()));
}
for (i, asset) in assets.iter().enumerate().skip(1) {
if assets[..i].iter().any(|a| a.is_same(asset)) {
return Err(match asset {
Asset::Fungible(asset) => NoteError::DuplicateFungibleAsset(asset.faucet_id()),
Asset::NonFungible(asset) => NoteError::DuplicateNonFungibleAsset(*asset),
});
}
}
let hash = compute_asset_commitment(&assets);
Ok(Self { assets, hash })
}
pub fn commitment(&self) -> Word {
self.hash
}
pub fn num_assets(&self) -> usize {
self.assets.len()
}
pub fn is_empty(&self) -> bool {
self.assets.is_empty()
}
pub fn iter(&self) -> core::slice::Iter<'_, Asset> {
self.assets.iter()
}
pub fn to_padded_assets(&self) -> Vec<Felt> {
let padded_len = if self.assets.len().is_multiple_of(2) {
self.assets.len() * WORD_SIZE
} else {
(self.assets.len() + 1) * WORD_SIZE
};
let mut padded_assets = Vec::with_capacity(padded_len * WORD_SIZE);
padded_assets.extend(self.assets.iter().flat_map(|asset| Word::from(*asset)));
padded_assets.resize(padded_len, ZERO);
padded_assets
}
pub fn iter_fungible(&self) -> impl Iterator<Item = FungibleAsset> {
self.assets.iter().filter_map(|asset| match asset {
Asset::Fungible(fungible_asset) => Some(*fungible_asset),
Asset::NonFungible(_) => None,
})
}
pub fn iter_non_fungible(&self) -> impl Iterator<Item = NonFungibleAsset> {
self.assets.iter().filter_map(|asset| match asset {
Asset::Fungible(_) => None,
Asset::NonFungible(non_fungible_asset) => Some(*non_fungible_asset),
})
}
pub fn add_asset(&mut self, asset: Asset) -> Result<(), NoteError> {
if let Some(own_asset) = self.assets.iter_mut().find(|a| a.is_same(&asset)) {
match own_asset {
Asset::Fungible(f_own_asset) => {
let new_asset = f_own_asset
.add(asset.unwrap_fungible())
.map_err(NoteError::AddFungibleAssetBalanceError)?;
*own_asset = Asset::Fungible(new_asset);
},
Asset::NonFungible(nf_asset) => {
return Err(NoteError::DuplicateNonFungibleAsset(*nf_asset));
},
}
} else {
self.assets.push(asset);
if self.assets.len() > Self::MAX_NUM_ASSETS {
return Err(NoteError::TooManyAssets(self.assets.len()));
}
}
self.hash = compute_asset_commitment(&self.assets);
Ok(())
}
}
impl PartialEq for NoteAssets {
fn eq(&self, other: &Self) -> bool {
self.assets == other.assets
}
}
impl Eq for NoteAssets {}
fn compute_asset_commitment(assets: &[Asset]) -> Word {
if assets.is_empty() {
return Word::empty();
}
let word_capacity = if assets.len().is_multiple_of(2) {
assets.len()
} else {
assets.len() + 1
};
let mut asset_elements = Vec::with_capacity(word_capacity * WORD_SIZE);
for asset in assets.iter() {
let asset_word: Word = (*asset).into();
asset_elements.extend_from_slice(asset_word.as_elements());
}
if assets.len() % 2 == 1 {
asset_elements.extend_from_slice(Word::empty().as_elements());
}
Hasher::hash_elements(&asset_elements)
}
impl Serializable for NoteAssets {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
const _: () = assert!(NoteAssets::MAX_NUM_ASSETS <= u8::MAX as usize);
debug_assert!(self.assets.len() <= NoteAssets::MAX_NUM_ASSETS);
target.write_u8(self.assets.len().try_into().expect("Asset number must fit into `u8`"));
target.write_many(&self.assets);
}
}
impl Deserializable for NoteAssets {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let count = source.read_u8()?;
let assets = source.read_many::<Asset>(count.into())?;
Self::new(assets).map_err(|e| DeserializationError::InvalidValue(format!("{e:?}")))
}
}
#[cfg(test)]
mod tests {
use super::{NoteAssets, compute_asset_commitment};
use crate::Word;
use crate::account::AccountId;
use crate::asset::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails};
use crate::testing::account_id::{
ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
};
#[test]
fn add_asset() {
let faucet_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap();
let asset1 = Asset::Fungible(FungibleAsset::new(faucet_id, 100).unwrap());
let asset2 = Asset::Fungible(FungibleAsset::new(faucet_id, 50).unwrap());
let mut assets = NoteAssets::default();
assert_eq!(assets.hash, Word::empty());
assert!(assets.add_asset(asset1).is_ok());
assert_eq!(assets.assets, vec![asset1]);
assert_eq!(assets.hash, compute_asset_commitment(&[asset1]));
assert!(assets.add_asset(asset2).is_ok());
let expected_asset = Asset::Fungible(FungibleAsset::new(faucet_id, 150).unwrap());
assert_eq!(assets.assets, vec![expected_asset]);
assert_eq!(assets.hash, compute_asset_commitment(&[expected_asset]));
}
#[test]
fn iter_fungible_asset() {
let faucet_id_1 = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap();
let faucet_id_2 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET).unwrap();
let details = NonFungibleAssetDetails::new(account_id.prefix(), vec![1, 2, 3]).unwrap();
let asset1 = Asset::Fungible(FungibleAsset::new(faucet_id_1, 100).unwrap());
let asset2 = Asset::Fungible(FungibleAsset::new(faucet_id_2, 50).unwrap());
let non_fungible_asset = Asset::NonFungible(NonFungibleAsset::new(&details).unwrap());
let assets = NoteAssets::new([asset1, asset2, non_fungible_asset].to_vec()).unwrap();
let mut fungible_assets = assets.iter_fungible();
assert_eq!(fungible_assets.next().unwrap(), asset1.unwrap_fungible());
assert_eq!(fungible_assets.next().unwrap(), asset2.unwrap_fungible());
assert_eq!(fungible_assets.next(), None);
}
}