miden_objects/note/
assets.rs1use alloc::vec::Vec;
2
3use crate::{
4 Digest, Felt, Hasher, MAX_ASSETS_PER_NOTE, WORD_SIZE, Word, ZERO,
5 asset::{Asset, FungibleAsset, NonFungibleAsset},
6 errors::NoteError,
7 utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
8};
9
10#[derive(Debug, Default, Clone)]
21pub struct NoteAssets {
22 assets: Vec<Asset>,
23 hash: Digest,
24}
25
26impl NoteAssets {
27 pub const MAX_NUM_ASSETS: usize = MAX_ASSETS_PER_NOTE;
32
33 pub fn new(assets: Vec<Asset>) -> Result<Self, NoteError> {
43 if assets.len() > Self::MAX_NUM_ASSETS {
44 return Err(NoteError::TooManyAssets(assets.len()));
45 }
46
47 for (i, asset) in assets.iter().enumerate().skip(1) {
49 if assets[..i].iter().any(|a| a.is_same(asset)) {
52 return Err(match asset {
53 Asset::Fungible(asset) => NoteError::DuplicateFungibleAsset(asset.faucet_id()),
54 Asset::NonFungible(asset) => NoteError::DuplicateNonFungibleAsset(*asset),
55 });
56 }
57 }
58
59 let hash = compute_asset_commitment(&assets);
60 Ok(Self { assets, hash })
61 }
62
63 pub fn commitment(&self) -> Digest {
68 self.hash
69 }
70
71 pub fn num_assets(&self) -> usize {
73 self.assets.len()
74 }
75
76 pub fn is_empty(&self) -> bool {
78 self.assets.is_empty()
79 }
80
81 pub fn iter(&self) -> core::slice::Iter<Asset> {
83 self.assets.iter()
84 }
85
86 pub fn to_padded_assets(&self) -> Vec<Felt> {
91 let padded_len = if self.assets.len() % 2 == 0 {
93 self.assets.len() * WORD_SIZE
94 } else {
95 (self.assets.len() + 1) * WORD_SIZE
96 };
97
98 let mut padded_assets = Vec::with_capacity(padded_len * WORD_SIZE);
100
101 padded_assets.extend(self.assets.iter().flat_map(|asset| <[Felt; 4]>::from(*asset)));
103
104 padded_assets.resize(padded_len, ZERO);
106
107 padded_assets
108 }
109
110 pub fn iter_fungible(&self) -> impl Iterator<Item = FungibleAsset> {
112 self.assets.iter().filter_map(|asset| match asset {
113 Asset::Fungible(fungible_asset) => Some(*fungible_asset),
114 Asset::NonFungible(_) => None,
115 })
116 }
117
118 pub fn iter_non_fungible(&self) -> impl Iterator<Item = NonFungibleAsset> {
120 self.assets.iter().filter_map(|asset| match asset {
121 Asset::Fungible(_) => None,
122 Asset::NonFungible(non_fungible_asset) => Some(*non_fungible_asset),
123 })
124 }
125
126 pub fn add_asset(&mut self, asset: Asset) -> Result<(), NoteError> {
138 if let Some(own_asset) = self.assets.iter_mut().find(|a| a.is_same(&asset)) {
141 match own_asset {
142 Asset::Fungible(f_own_asset) => {
143 let new_asset = f_own_asset
146 .add(asset.unwrap_fungible())
147 .map_err(NoteError::AddFungibleAssetBalanceError)?;
148 *own_asset = Asset::Fungible(new_asset);
149 },
150 Asset::NonFungible(nf_asset) => {
151 return Err(NoteError::DuplicateNonFungibleAsset(*nf_asset));
152 },
153 }
154 } else {
155 self.assets.push(asset);
157 if self.assets.len() > Self::MAX_NUM_ASSETS {
158 return Err(NoteError::TooManyAssets(self.assets.len()));
159 }
160 }
161
162 self.hash = compute_asset_commitment(&self.assets);
163
164 Ok(())
165 }
166}
167
168impl PartialEq for NoteAssets {
169 fn eq(&self, other: &Self) -> bool {
170 self.assets == other.assets
171 }
172}
173
174impl Eq for NoteAssets {}
175
176fn compute_asset_commitment(assets: &[Asset]) -> Digest {
185 if assets.is_empty() {
186 return Digest::default();
187 }
188
189 let word_capacity = if assets.len() % 2 == 0 {
192 assets.len()
193 } else {
194 assets.len() + 1
195 };
196 let mut asset_elements = Vec::with_capacity(word_capacity * WORD_SIZE);
197
198 for asset in assets.iter() {
199 let asset_word: Word = (*asset).into();
201 asset_elements.extend_from_slice(&asset_word);
202 }
203
204 if assets.len() % 2 == 1 {
208 asset_elements.extend_from_slice(&Word::default());
209 }
210
211 Hasher::hash_elements(&asset_elements)
212}
213
214impl Serializable for NoteAssets {
218 fn write_into<W: ByteWriter>(&self, target: &mut W) {
219 const _: () = assert!(NoteAssets::MAX_NUM_ASSETS <= u8::MAX as usize);
220 debug_assert!(self.assets.len() <= NoteAssets::MAX_NUM_ASSETS);
221 target.write_u8(self.assets.len().try_into().expect("Asset number must fit into `u8`"));
222 target.write_many(&self.assets);
223 }
224}
225
226impl Deserializable for NoteAssets {
227 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
228 let count = source.read_u8()?;
229 let assets = source.read_many::<Asset>(count.into())?;
230 Self::new(assets).map_err(|e| DeserializationError::InvalidValue(format!("{e:?}")))
231 }
232}
233
234#[cfg(test)]
238mod tests {
239 use super::{NoteAssets, compute_asset_commitment};
240 use crate::{
241 Digest,
242 account::AccountId,
243 asset::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails},
244 testing::account_id::{
245 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
246 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
247 },
248 };
249
250 #[test]
251 fn add_asset() {
252 let faucet_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap();
253
254 let asset1 = Asset::Fungible(FungibleAsset::new(faucet_id, 100).unwrap());
255 let asset2 = Asset::Fungible(FungibleAsset::new(faucet_id, 50).unwrap());
256
257 let mut assets = NoteAssets::default();
259
260 assert_eq!(assets.hash, Digest::default());
261
262 assert!(assets.add_asset(asset1).is_ok());
264 assert_eq!(assets.assets, vec![asset1]);
265 assert_eq!(assets.hash, compute_asset_commitment(&[asset1]));
266
267 assert!(assets.add_asset(asset2).is_ok());
269 let expected_asset = Asset::Fungible(FungibleAsset::new(faucet_id, 150).unwrap());
270 assert_eq!(assets.assets, vec![expected_asset]);
271 assert_eq!(assets.hash, compute_asset_commitment(&[expected_asset]));
272 }
273 #[test]
274 fn iter_fungible_asset() {
275 let faucet_id_1 = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap();
276 let faucet_id_2 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
277 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET).unwrap();
278 let details = NonFungibleAssetDetails::new(account_id.prefix(), vec![1, 2, 3]).unwrap();
279
280 let asset1 = Asset::Fungible(FungibleAsset::new(faucet_id_1, 100).unwrap());
281 let asset2 = Asset::Fungible(FungibleAsset::new(faucet_id_2, 50).unwrap());
282 let non_fungible_asset = Asset::NonFungible(NonFungibleAsset::new(&details).unwrap());
283
284 let assets = NoteAssets::new([asset1, asset2, non_fungible_asset].to_vec()).unwrap();
286
287 let mut fungible_assets = assets.iter_fungible();
288 assert_eq!(fungible_assets.next().unwrap(), asset1.unwrap_fungible());
289 assert_eq!(fungible_assets.next().unwrap(), asset2.unwrap_fungible());
290 assert_eq!(fungible_assets.next(), None);
291 }
292}