miden_protocol/account/delta/
vault.rs1use alloc::collections::BTreeMap;
2use alloc::collections::btree_map::Entry;
3use alloc::string::ToString;
4use alloc::vec::Vec;
5
6use super::{
7 AccountDeltaError,
8 ByteReader,
9 ByteWriter,
10 Deserializable,
11 DeserializationError,
12 Serializable,
13};
14use crate::account::delta::AssetDeltaOperation;
15use crate::asset::{Asset, AssetId};
16use crate::{Felt, Word};
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub struct AssetDelta {
26 delta_op: AssetDeltaOperation,
27 asset: Asset,
28}
29
30impl AssetDelta {
31 pub fn new(delta_op: AssetDeltaOperation, asset: Asset) -> Self {
33 Self { delta_op, asset }
34 }
35
36 pub fn delta_op(&self) -> AssetDeltaOperation {
38 self.delta_op
39 }
40
41 pub fn asset(&self) -> Asset {
43 self.asset
44 }
45
46 pub fn asset_id(&self) -> AssetId {
48 self.asset.id()
49 }
50}
51
52#[derive(Clone, Debug, Default, PartialEq, Eq)]
74pub struct AccountVaultDelta {
75 delta: BTreeMap<AssetId, AssetDelta>,
76}
77
78impl AccountVaultDelta {
79 pub(in crate::account) const DOMAIN: Felt = Felt::new_unchecked(1);
81
82 pub const MAX_ASSETS_PER_DELTA_OP: u16 = 1024;
84
85 pub fn new(
93 asset_deltas: impl IntoIterator<Item = AssetDelta>,
94 ) -> Result<Self, AccountDeltaError> {
95 let mut delta = BTreeMap::new();
96 let mut num_added_assets = 0usize;
97 let mut num_removed_assets = 0usize;
98
99 for asset_delta in asset_deltas {
100 match asset_delta.delta_op() {
101 AssetDeltaOperation::Add => num_added_assets += 1,
102 AssetDeltaOperation::Remove => num_removed_assets += 1,
103 }
104
105 match delta.entry(asset_delta.asset_id()) {
106 Entry::Vacant(entry) => {
107 entry.insert(asset_delta);
108 },
109 Entry::Occupied(_) => {
110 return Err(AccountDeltaError::DuplicateAssetDelta(asset_delta.asset_id()));
111 },
112 }
113 }
114
115 Self::validate_asset_count(AssetDeltaOperation::Add, num_added_assets)?;
116 Self::validate_asset_count(AssetDeltaOperation::Remove, num_removed_assets)?;
117
118 Ok(Self { delta })
119 }
120
121 pub fn is_empty(&self) -> bool {
123 self.delta.is_empty()
124 }
125
126 pub fn num_assets(&self) -> usize {
128 self.delta.len()
129 }
130
131 pub fn iter(&self) -> impl Iterator<Item = &AssetDelta> {
133 self.delta.values()
134 }
135
136 pub fn added_assets(&self) -> impl Iterator<Item = Asset> + '_ {
138 self.filter_by_op(AssetDeltaOperation::Add)
139 }
140
141 pub fn removed_assets(&self) -> impl Iterator<Item = Asset> + '_ {
143 self.filter_by_op(AssetDeltaOperation::Remove)
144 }
145
146 pub(super) fn append_delta_elements(&self, elements: &mut Vec<Felt>) {
149 self.append_asset_section(AssetDeltaOperation::Add, elements);
150 self.append_asset_section(AssetDeltaOperation::Remove, elements);
151 }
152
153 fn num_assets_by_op(&self, delta_op: AssetDeltaOperation) -> u16 {
160 let num_assets = self.filter_by_op(delta_op).count();
161 u16::try_from(num_assets).expect("number of changed assets is validated on construction")
162 }
163
164 fn num_added_assets(&self) -> u16 {
166 self.num_assets_by_op(AssetDeltaOperation::Add)
167 }
168
169 fn num_removed_assets(&self) -> u16 {
171 self.num_assets_by_op(AssetDeltaOperation::Remove)
172 }
173
174 fn validate_asset_count(
177 delta_op: AssetDeltaOperation,
178 num_ops: usize,
179 ) -> Result<(), AccountDeltaError> {
180 if num_ops > usize::from(Self::MAX_ASSETS_PER_DELTA_OP) {
181 return Err(AccountDeltaError::TooManyVaultAssetDeltas { delta_op, num_ops });
182 }
183
184 Ok(())
185 }
186
187 fn filter_by_op(&self, delta_op: AssetDeltaOperation) -> impl Iterator<Item = Asset> + '_ {
189 self.delta
190 .values()
191 .filter(move |asset_delta| asset_delta.delta_op() == delta_op)
192 .map(AssetDelta::asset)
193 }
194
195 fn append_asset_section(&self, delta_op: AssetDeltaOperation, elements: &mut Vec<Felt>) {
199 let mut num_changed_assets = 0;
200 for asset in self.filter_by_op(delta_op) {
201 elements.extend_from_slice(&asset.as_elements());
202 num_changed_assets += 1;
203 }
204
205 if num_changed_assets != 0 {
206 let num_changed_assets = Felt::try_from(num_changed_assets as u64)
207 .expect("number of changed assets should not exceed max representable felt");
208
209 elements.extend_from_slice(&[
210 Self::DOMAIN,
211 Felt::from(delta_op.as_u8()),
212 num_changed_assets,
213 Felt::ZERO,
214 ]);
215 elements.extend_from_slice(Word::empty().as_elements());
216 }
217 }
218}
219
220impl Serializable for AccountVaultDelta {
221 fn write_into<W: ByteWriter>(&self, target: &mut W) {
222 target.write(self.num_added_assets());
223 target.write_many(self.added_assets());
224
225 target.write(self.num_removed_assets());
226 target.write_many(self.removed_assets());
227 }
228
229 fn get_size_hint(&self) -> usize {
230 let added_size: usize = self.added_assets().map(|asset| asset.get_size_hint()).sum();
231 let removed_size: usize = self.removed_assets().map(|asset| asset.get_size_hint()).sum();
232
233 2 * 0u16.get_size_hint() + added_size + removed_size
234 }
235}
236
237impl Deserializable for AccountVaultDelta {
238 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
239 let num_added_assets: u16 = source.read()?;
240 if num_added_assets > Self::MAX_ASSETS_PER_DELTA_OP {
241 return Err(DeserializationError::InvalidValue(
242 AccountDeltaError::TooManyVaultAssetDeltas {
243 delta_op: AssetDeltaOperation::Add,
244 num_ops: usize::from(num_added_assets),
245 }
246 .to_string(),
247 ));
248 }
249
250 let mut asset_deltas = Vec::new();
253 for asset in source.read_many_iter::<Asset>(usize::from(num_added_assets))? {
254 asset_deltas.push(AssetDelta::new(AssetDeltaOperation::Add, asset?));
255 }
256
257 let num_removed_assets: u16 = source.read()?;
258 if num_removed_assets > Self::MAX_ASSETS_PER_DELTA_OP {
259 return Err(DeserializationError::InvalidValue(
260 AccountDeltaError::TooManyVaultAssetDeltas {
261 delta_op: AssetDeltaOperation::Remove,
262 num_ops: usize::from(num_removed_assets),
263 }
264 .to_string(),
265 ));
266 }
267 for asset in source.read_many_iter::<Asset>(usize::from(num_removed_assets))? {
268 asset_deltas.push(AssetDelta::new(AssetDeltaOperation::Remove, asset?));
269 }
270
271 Self::new(asset_deltas).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
272 }
273}
274
275#[cfg(test)]
279mod tests {
280 use alloc::string::ToString;
281 use alloc::vec::Vec;
282
283 use assert_matches::assert_matches;
284 use rstest::rstest;
285
286 use super::{AccountVaultDelta, Deserializable, DeserializationError, Serializable};
287 use crate::account::delta::AssetDeltaOperation;
288 use crate::account::{AccountId, AssetDelta};
289 use crate::asset::{Asset, FungibleAsset, NonFungibleAsset};
290 use crate::errors::AccountDeltaError;
291 use crate::utils::serde::ByteWriter;
292
293 #[test]
294 fn account_vault_delta_serde() -> anyhow::Result<()> {
295 let empty_delta = AccountVaultDelta::default();
296 assert!(empty_delta.is_empty());
297 let serialized = empty_delta.to_bytes();
298 assert_eq!(AccountVaultDelta::read_from_bytes(&serialized)?, empty_delta);
299 assert_eq!(empty_delta.get_size_hint(), serialized.len());
300
301 let delta = AccountVaultDelta::from_iters(
302 [FungibleAsset::mock(100), NonFungibleAsset::mock(&[10, 21, 32, 43])],
303 [NonFungibleAsset::mock(&[54, 65])],
304 );
305 assert!(!delta.is_empty());
306
307 let serialized = delta.to_bytes();
308 assert_eq!(AccountVaultDelta::read_from_bytes(&serialized)?, delta);
309 assert_eq!(delta.get_size_hint(), serialized.len());
310
311 Ok(())
312 }
313
314 fn generate_asset_deltas(delta_op: AssetDeltaOperation, num_deltas: usize) -> Vec<AssetDelta> {
315 (0..num_deltas)
316 .map(|_| {
317 let asset =
318 FungibleAsset::new(AccountId::builder().build_with_seed(rand::random()), 42)
319 .unwrap();
320 AssetDelta::new(delta_op, Asset::from(asset))
321 })
322 .collect::<Vec<_>>()
323 }
324
325 #[rstest]
326 #[case::add(AssetDeltaOperation::Add)]
327 #[case::remove(AssetDeltaOperation::Remove)]
328 fn account_vault_delta_accepts_max_num_changed_assets(
329 #[case] expected_delta_op: AssetDeltaOperation,
330 ) -> anyhow::Result<()> {
331 let asset_deltas = generate_asset_deltas(
332 expected_delta_op,
333 usize::from(AccountVaultDelta::MAX_ASSETS_PER_DELTA_OP),
334 );
335
336 AccountVaultDelta::new(asset_deltas)?;
337
338 Ok(())
339 }
340
341 #[rstest]
342 #[case::add(AssetDeltaOperation::Add)]
343 #[case::remove(AssetDeltaOperation::Remove)]
344 fn account_vault_delta_rejects_more_than_max_num_changed_assets(
345 #[case] expected_delta_op: AssetDeltaOperation,
346 ) -> anyhow::Result<()> {
347 let expected_num_ops = usize::from(AccountVaultDelta::MAX_ASSETS_PER_DELTA_OP) + 1;
348 let asset_deltas = generate_asset_deltas(expected_delta_op, expected_num_ops);
349
350 let err = AccountVaultDelta::new(asset_deltas).unwrap_err();
351 assert_matches!(err, AccountDeltaError::TooManyVaultAssetDeltas { delta_op, num_ops } => {
352 assert_eq!(delta_op, expected_delta_op);
353 assert_eq!(num_ops, expected_num_ops);
354 });
355
356 Ok(())
357 }
358
359 #[test]
361 fn account_vault_delta_rejects_duplicate_asset() -> anyhow::Result<()> {
362 let asset = NonFungibleAsset::mock(&[10, 21, 32, 43]);
363 let asset_deltas = [
364 AssetDelta::new(AssetDeltaOperation::Add, asset),
365 AssetDelta::new(AssetDeltaOperation::Remove, asset),
366 ];
367
368 let err = AccountVaultDelta::new(asset_deltas).unwrap_err();
369 assert_matches!(err, AccountDeltaError::DuplicateAssetDelta(asset_id) => {
370 assert_eq!(asset_id, asset.id());
371 });
372
373 Ok(())
374 }
375
376 #[test]
379 fn account_vault_delta_deserialization_rejects_duplicate_asset() -> anyhow::Result<()> {
380 let asset = NonFungibleAsset::mock(&[10, 21, 32, 43]);
381
382 let mut bytes = Vec::new();
383 bytes.write(1u16);
384 bytes.write(asset);
385 bytes.write(1u16);
386 bytes.write(asset);
387
388 let error = AccountVaultDelta::read_from_bytes(&bytes)
389 .expect_err("delta with a duplicate asset should not deserialize");
390
391 let expected = AccountDeltaError::DuplicateAssetDelta(asset.id()).to_string();
392 assert_matches!(error, DeserializationError::InvalidValue(message) if message == expected);
393
394 Ok(())
395 }
396}