miden_protocol/note/
assets.rs1use alloc::vec::Vec;
2
3use miden_crypto::SequentialCommit;
4
5use crate::asset::{Asset, FungibleAsset};
6use crate::errors::NoteError;
7use crate::utils::serde::{
8 ByteReader,
9 ByteWriter,
10 Deserializable,
11 DeserializationError,
12 Serializable,
13};
14use crate::{Felt, Hasher, MAX_ASSETS_PER_NOTE, WORD_SIZE, Word};
15
16#[derive(Debug, Default, Clone)]
28pub struct NoteAssets {
29 assets: Vec<Asset>,
30 commitment: Word,
31}
32
33impl NoteAssets {
34 pub const MAX_NUM_ASSETS: usize = MAX_ASSETS_PER_NOTE;
39
40 pub fn new(assets: Vec<Asset>) -> Result<Self, NoteError> {
50 if assets.len() > Self::MAX_NUM_ASSETS {
51 return Err(NoteError::TooManyAssets(assets.len()));
52 }
53
54 for (i, asset) in assets.iter().enumerate().skip(1) {
56 if assets[..i].iter().any(|a| a.is_same(asset)) {
59 return Err(match asset.as_fungible() {
60 Some(fungible_asset) => {
61 NoteError::DuplicateFungibleAsset(fungible_asset.faucet_id())
62 },
63 None => NoteError::DuplicateNonFungibleAsset(*asset),
64 });
65 }
66 }
67
68 let commitment = to_commitment(&assets);
69
70 Ok(Self { assets, commitment })
71 }
72
73 pub fn commitment(&self) -> Word {
78 self.commitment
79 }
80
81 pub fn as_slice(&self) -> &[Asset] {
83 &self.assets
84 }
85
86 pub fn num_assets(&self) -> usize {
88 self.assets.len()
89 }
90
91 pub fn is_empty(&self) -> bool {
93 self.assets.is_empty()
94 }
95
96 pub fn iter(&self) -> core::slice::Iter<'_, Asset> {
98 self.assets.iter()
99 }
100
101 pub fn to_elements(&self) -> Vec<Felt> {
103 <Self as SequentialCommit>::to_elements(self)
104 }
105
106 pub fn iter_fungible(&self) -> impl Iterator<Item = FungibleAsset> {
108 self.assets.iter().filter_map(Asset::as_fungible)
109 }
110
111 pub fn into_vec(self) -> Vec<Asset> {
113 self.assets
114 }
115}
116
117impl PartialEq for NoteAssets {
118 fn eq(&self, other: &Self) -> bool {
119 self.assets == other.assets
120 }
121}
122
123impl Eq for NoteAssets {}
124
125impl SequentialCommit for NoteAssets {
126 type Commitment = Word;
127
128 fn to_elements(&self) -> Vec<Felt> {
130 to_elements(&self.assets)
131 }
132
133 fn to_commitment(&self) -> Self::Commitment {
135 to_commitment(&self.assets)
136 }
137}
138
139fn to_elements(assets: &[Asset]) -> Vec<Felt> {
140 let mut elements = Vec::with_capacity(assets.len() * 2 * WORD_SIZE);
141 elements.extend(assets.iter().flat_map(Asset::as_elements));
142 elements
143}
144
145fn to_commitment(assets: &[Asset]) -> Word {
146 Hasher::hash_elements(&to_elements(assets))
147}
148
149impl Serializable for NoteAssets {
153 fn write_into<W: ByteWriter>(&self, target: &mut W) {
154 const _: () = assert!(NoteAssets::MAX_NUM_ASSETS <= u8::MAX as usize);
155 debug_assert!(self.assets.len() <= NoteAssets::MAX_NUM_ASSETS);
156 target.write_u8(self.assets.len().try_into().expect("Asset number must fit into `u8`"));
157 target.write_many(&self.assets);
158 }
159
160 fn get_size_hint(&self) -> usize {
161 let u8_size = 0u8.get_size_hint();
163
164 let assets_size: usize = self.assets.iter().map(|asset| asset.get_size_hint()).sum();
165
166 u8_size + assets_size
167 }
168}
169
170impl Deserializable for NoteAssets {
171 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
172 let count = source.read_u8()?;
173 let assets = source.read_many_iter::<Asset>(count.into())?.collect::<Result<_, _>>()?;
174 Self::new(assets).map_err(|e| DeserializationError::InvalidValue(format!("{e:?}")))
175 }
176}
177
178#[cfg(test)]
182mod tests {
183 use alloc::vec;
184 use alloc::vec::Vec;
185
186 use assert_matches::assert_matches;
187
188 use super::NoteAssets;
189 use crate::account::AccountId;
190 use crate::asset::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails};
191 use crate::errors::NoteError;
192 use crate::testing::account_id::{
193 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
194 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
195 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
196 };
197
198 fn make_non_fungible_assets(n: usize) -> Vec<Asset> {
200 let faucet_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET).unwrap();
201 (0..n)
202 .map(|i| {
203 let data = (i as u64).to_le_bytes().to_vec();
205 let details = NonFungibleAssetDetails::new(faucet_id, data);
206 Asset::from(NonFungibleAsset::new(&details))
207 })
208 .collect()
209 }
210
211 #[test]
212 fn iter_fungible_asset() {
213 let faucet_id_1 = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap();
214 let faucet_id_2 = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
215 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET).unwrap();
216 let details = NonFungibleAssetDetails::new(account_id, vec![1, 2, 3]);
217
218 let asset1 = Asset::from(FungibleAsset::new(faucet_id_1, 100).unwrap());
219 let asset2 = Asset::from(FungibleAsset::new(faucet_id_2, 50).unwrap());
220 let non_fungible_asset = Asset::from(NonFungibleAsset::new(&details));
221
222 let assets = NoteAssets::new([asset1, asset2, non_fungible_asset].to_vec()).unwrap();
224
225 let mut fungible_assets = assets.iter_fungible();
226 assert_eq!(fungible_assets.next().unwrap(), asset1.unwrap_fungible());
227 assert_eq!(fungible_assets.next().unwrap(), asset2.unwrap_fungible());
228 assert_eq!(fungible_assets.next(), None);
229 }
230
231 #[test]
232 fn note_assets_at_max_succeeds() {
233 assert_eq!(NoteAssets::MAX_NUM_ASSETS, 16);
234
235 let assets = make_non_fungible_assets(NoteAssets::MAX_NUM_ASSETS);
236 assert_eq!(assets.len(), NoteAssets::MAX_NUM_ASSETS);
237
238 let note_assets = NoteAssets::new(assets).unwrap();
239 assert_eq!(note_assets.num_assets(), NoteAssets::MAX_NUM_ASSETS);
240 }
241
242 #[test]
243 fn note_assets_exceeding_max_fails() {
244 let assets = make_non_fungible_assets(NoteAssets::MAX_NUM_ASSETS + 1);
245 assert_eq!(assets.len(), NoteAssets::MAX_NUM_ASSETS + 1);
246
247 let result = NoteAssets::new(assets);
248 assert_matches!(result, Err(NoteError::TooManyAssets(n)) if n == NoteAssets::MAX_NUM_ASSETS + 1);
249 }
250}