miden_objects/note/
assets.rs1use alloc::vec::Vec;
2
3use crate::asset::{Asset, FungibleAsset, NonFungibleAsset};
4use crate::errors::NoteError;
5use crate::utils::serde::{
6 ByteReader,
7 ByteWriter,
8 Deserializable,
9 DeserializationError,
10 Serializable,
11};
12use crate::{Felt, Hasher, MAX_ASSETS_PER_NOTE, WORD_SIZE, Word, ZERO};
13
14#[derive(Debug, Default, Clone)]
25pub struct NoteAssets {
26 assets: Vec<Asset>,
27 hash: Word,
28}
29
30impl NoteAssets {
31 pub const MAX_NUM_ASSETS: usize = MAX_ASSETS_PER_NOTE;
36
37 pub fn new(assets: Vec<Asset>) -> Result<Self, NoteError> {
47 if assets.len() > Self::MAX_NUM_ASSETS {
48 return Err(NoteError::TooManyAssets(assets.len()));
49 }
50
51 for (i, asset) in assets.iter().enumerate().skip(1) {
53 if assets[..i].iter().any(|a| a.is_same(asset)) {
56 return Err(match asset {
57 Asset::Fungible(asset) => NoteError::DuplicateFungibleAsset(asset.faucet_id()),
58 Asset::NonFungible(asset) => NoteError::DuplicateNonFungibleAsset(*asset),
59 });
60 }
61 }
62
63 let hash = compute_asset_commitment(&assets);
64 Ok(Self { assets, hash })
65 }
66
67 pub fn commitment(&self) -> Word {
72 self.hash
73 }
74
75 pub fn num_assets(&self) -> usize {
77 self.assets.len()
78 }
79
80 pub fn is_empty(&self) -> bool {
82 self.assets.is_empty()
83 }
84
85 pub fn iter(&self) -> core::slice::Iter<'_, Asset> {
87 self.assets.iter()
88 }
89
90 pub fn to_padded_assets(&self) -> Vec<Felt> {
95 let padded_len = if self.assets.len() % 2 == 0 {
97 self.assets.len() * WORD_SIZE
98 } else {
99 (self.assets.len() + 1) * WORD_SIZE
100 };
101
102 let mut padded_assets = Vec::with_capacity(padded_len * WORD_SIZE);
104
105 padded_assets.extend(self.assets.iter().flat_map(|asset| Word::from(*asset)));
107
108 padded_assets.resize(padded_len, ZERO);
110
111 padded_assets
112 }
113
114 pub fn iter_fungible(&self) -> impl Iterator<Item = FungibleAsset> {
116 self.assets.iter().filter_map(|asset| match asset {
117 Asset::Fungible(fungible_asset) => Some(*fungible_asset),
118 Asset::NonFungible(_) => None,
119 })
120 }
121
122 pub fn iter_non_fungible(&self) -> impl Iterator<Item = NonFungibleAsset> {
124 self.assets.iter().filter_map(|asset| match asset {
125 Asset::Fungible(_) => None,
126 Asset::NonFungible(non_fungible_asset) => Some(*non_fungible_asset),
127 })
128 }
129
130 pub fn add_asset(&mut self, asset: Asset) -> Result<(), NoteError> {
142 if let Some(own_asset) = self.assets.iter_mut().find(|a| a.is_same(&asset)) {
145 match own_asset {
146 Asset::Fungible(f_own_asset) => {
147 let new_asset = f_own_asset
150 .add(asset.unwrap_fungible())
151 .map_err(NoteError::AddFungibleAssetBalanceError)?;
152 *own_asset = Asset::Fungible(new_asset);
153 },
154 Asset::NonFungible(nf_asset) => {
155 return Err(NoteError::DuplicateNonFungibleAsset(*nf_asset));
156 },
157 }
158 } else {
159 self.assets.push(asset);
161 if self.assets.len() > Self::MAX_NUM_ASSETS {
162 return Err(NoteError::TooManyAssets(self.assets.len()));
163 }
164 }
165
166 self.hash = compute_asset_commitment(&self.assets);
167
168 Ok(())
169 }
170}
171
172impl PartialEq for NoteAssets {
173 fn eq(&self, other: &Self) -> bool {
174 self.assets == other.assets
175 }
176}
177
178impl Eq for NoteAssets {}
179
180fn compute_asset_commitment(assets: &[Asset]) -> Word {
189 if assets.is_empty() {
190 return Word::empty();
191 }
192
193 let word_capacity = if assets.len() % 2 == 0 {
196 assets.len()
197 } else {
198 assets.len() + 1
199 };
200 let mut asset_elements = Vec::with_capacity(word_capacity * WORD_SIZE);
201
202 for asset in assets.iter() {
203 let asset_word: Word = (*asset).into();
205 asset_elements.extend_from_slice(asset_word.as_elements());
206 }
207
208 if assets.len() % 2 == 1 {
212 asset_elements.extend_from_slice(Word::empty().as_elements());
213 }
214
215 Hasher::hash_elements(&asset_elements)
216}
217
218impl Serializable for NoteAssets {
222 fn write_into<W: ByteWriter>(&self, target: &mut W) {
223 const _: () = assert!(NoteAssets::MAX_NUM_ASSETS <= u8::MAX as usize);
224 debug_assert!(self.assets.len() <= NoteAssets::MAX_NUM_ASSETS);
225 target.write_u8(self.assets.len().try_into().expect("Asset number must fit into `u8`"));
226 target.write_many(&self.assets);
227 }
228}
229
230impl Deserializable for NoteAssets {
231 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
232 let count = source.read_u8()?;
233 let assets = source.read_many::<Asset>(count.into())?;
234 Self::new(assets).map_err(|e| DeserializationError::InvalidValue(format!("{e:?}")))
235 }
236}
237
238#[cfg(test)]
242mod tests {
243 use super::{NoteAssets, compute_asset_commitment};
244 use crate::Word;
245 use crate::account::AccountId;
246 use crate::asset::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails};
247 use crate::testing::account_id::{
248 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
249 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
250 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
251 };
252
253 #[test]
254 fn add_asset() {
255 let faucet_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap();
256
257 let asset1 = Asset::Fungible(FungibleAsset::new(faucet_id, 100).unwrap());
258 let asset2 = Asset::Fungible(FungibleAsset::new(faucet_id, 50).unwrap());
259
260 let mut assets = NoteAssets::default();
262
263 assert_eq!(assets.hash, Word::empty());
264
265 assert!(assets.add_asset(asset1).is_ok());
267 assert_eq!(assets.assets, vec![asset1]);
268 assert_eq!(assets.hash, compute_asset_commitment(&[asset1]));
269
270 assert!(assets.add_asset(asset2).is_ok());
272 let expected_asset = Asset::Fungible(FungibleAsset::new(faucet_id, 150).unwrap());
273 assert_eq!(assets.assets, vec![expected_asset]);
274 assert_eq!(assets.hash, compute_asset_commitment(&[expected_asset]));
275 }
276 #[test]
277 fn iter_fungible_asset() {
278 let faucet_id_1 = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap();
279 let faucet_id_2 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
280 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET).unwrap();
281 let details = NonFungibleAssetDetails::new(account_id.prefix(), vec![1, 2, 3]).unwrap();
282
283 let asset1 = Asset::Fungible(FungibleAsset::new(faucet_id_1, 100).unwrap());
284 let asset2 = Asset::Fungible(FungibleAsset::new(faucet_id_2, 50).unwrap());
285 let non_fungible_asset = Asset::NonFungible(NonFungibleAsset::new(&details).unwrap());
286
287 let assets = NoteAssets::new([asset1, asset2, non_fungible_asset].to_vec()).unwrap();
289
290 let mut fungible_assets = assets.iter_fungible();
291 assert_eq!(fungible_assets.next().unwrap(), asset1.unwrap_fungible());
292 assert_eq!(fungible_assets.next().unwrap(), asset2.unwrap_fungible());
293 assert_eq!(fungible_assets.next(), None);
294 }
295}