1use 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::AccountType;
15use crate::asset::{Asset, AssetVaultKey, FungibleAsset, NonFungibleAsset};
16use crate::{Felt, ONE, ZERO};
17
18const DOMAIN_ASSET: Felt = Felt::new(1);
23
24#[derive(Clone, Debug, Default, PartialEq, Eq)]
31pub struct AccountVaultDelta {
32 fungible: FungibleAssetDelta,
33 non_fungible: NonFungibleAssetDelta,
34}
35
36impl AccountVaultDelta {
37 pub const fn new(fungible: FungibleAssetDelta, non_fungible: NonFungibleAssetDelta) -> Self {
43 Self { fungible, non_fungible }
44 }
45
46 pub fn fungible(&self) -> &FungibleAssetDelta {
48 &self.fungible
49 }
50
51 pub fn non_fungible(&self) -> &NonFungibleAssetDelta {
53 &self.non_fungible
54 }
55
56 pub fn is_empty(&self) -> bool {
58 self.fungible.is_empty() && self.non_fungible.is_empty()
59 }
60
61 pub fn add_asset(&mut self, asset: Asset) -> Result<(), AccountDeltaError> {
63 match asset {
64 Asset::Fungible(asset) => self.fungible.add(asset),
65 Asset::NonFungible(asset) => self.non_fungible.add(asset),
66 }
67 }
68
69 pub fn remove_asset(&mut self, asset: Asset) -> Result<(), AccountDeltaError> {
71 match asset {
72 Asset::Fungible(asset) => self.fungible.remove(asset),
73 Asset::NonFungible(asset) => self.non_fungible.remove(asset),
74 }
75 }
76
77 pub fn merge(&mut self, other: Self) -> Result<(), AccountDeltaError> {
84 self.non_fungible.merge(other.non_fungible)?;
85 self.fungible.merge(other.fungible)
86 }
87
88 pub(super) fn append_delta_elements(&self, elements: &mut Vec<Felt>) {
91 self.fungible().append_delta_elements(elements);
92 self.non_fungible().append_delta_elements(elements);
93 }
94}
95
96#[cfg(any(feature = "testing", test))]
97impl AccountVaultDelta {
98 pub fn from_iters(
100 added_assets: impl IntoIterator<Item = crate::asset::Asset>,
101 removed_assets: impl IntoIterator<Item = crate::asset::Asset>,
102 ) -> Self {
103 let mut fungible = FungibleAssetDelta::default();
104 let mut non_fungible = NonFungibleAssetDelta::default();
105
106 for asset in added_assets {
107 match asset {
108 Asset::Fungible(asset) => {
109 fungible.add(asset).unwrap();
110 },
111 Asset::NonFungible(asset) => {
112 non_fungible.add(asset).unwrap();
113 },
114 }
115 }
116
117 for asset in removed_assets {
118 match asset {
119 Asset::Fungible(asset) => {
120 fungible.remove(asset).unwrap();
121 },
122 Asset::NonFungible(asset) => {
123 non_fungible.remove(asset).unwrap();
124 },
125 }
126 }
127
128 Self { fungible, non_fungible }
129 }
130
131 pub fn added_assets(&self) -> impl Iterator<Item = crate::asset::Asset> + '_ {
133 self.fungible
134 .0
135 .iter()
136 .filter(|&(_, &value)| value >= 0)
137 .map(|(vault_key, &diff)| {
138 Asset::Fungible(
139 FungibleAsset::new(vault_key.faucet_id(), diff.unsigned_abs())
140 .unwrap()
141 .with_callbacks(vault_key.callback_flag()),
142 )
143 })
144 .chain(
145 self.non_fungible
146 .filter_by_action(NonFungibleDeltaAction::Add)
147 .map(Asset::NonFungible),
148 )
149 }
150
151 pub fn removed_assets(&self) -> impl Iterator<Item = crate::asset::Asset> + '_ {
153 self.fungible
154 .0
155 .iter()
156 .filter(|&(_, &value)| value < 0)
157 .map(|(vault_key, &diff)| {
158 Asset::Fungible(
159 FungibleAsset::new(vault_key.faucet_id(), diff.unsigned_abs())
160 .unwrap()
161 .with_callbacks(vault_key.callback_flag()),
162 )
163 })
164 .chain(
165 self.non_fungible
166 .filter_by_action(NonFungibleDeltaAction::Remove)
167 .map(Asset::NonFungible),
168 )
169 }
170}
171
172impl Serializable for AccountVaultDelta {
173 fn write_into<W: ByteWriter>(&self, target: &mut W) {
174 target.write(&self.fungible);
175 target.write(&self.non_fungible);
176 }
177
178 fn get_size_hint(&self) -> usize {
179 self.fungible.get_size_hint() + self.non_fungible.get_size_hint()
180 }
181}
182
183impl Deserializable for AccountVaultDelta {
184 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
185 let fungible = source.read()?;
186 let non_fungible = source.read()?;
187
188 Ok(Self::new(fungible, non_fungible))
189 }
190}
191
192#[derive(Clone, Debug, Default, PartialEq, Eq)]
200pub struct FungibleAssetDelta(BTreeMap<AssetVaultKey, i64>);
201
202impl FungibleAssetDelta {
203 pub fn new(map: BTreeMap<AssetVaultKey, i64>) -> Result<Self, AccountDeltaError> {
208 let delta = Self(map);
209 delta.validate()?;
210
211 Ok(delta)
212 }
213
214 pub fn add(&mut self, asset: FungibleAsset) -> Result<(), AccountDeltaError> {
219 let amount: i64 = asset.amount().try_into().expect("Amount it too high");
220 self.add_delta(asset.vault_key(), amount)
221 }
222
223 pub fn remove(&mut self, asset: FungibleAsset) -> Result<(), AccountDeltaError> {
228 let amount: i64 = asset.amount().try_into().expect("Amount it too high");
229 self.add_delta(asset.vault_key(), -amount)
230 }
231
232 pub fn amount(&self, vault_key: &AssetVaultKey) -> Option<i64> {
234 self.0.get(vault_key).copied()
235 }
236
237 pub fn num_assets(&self) -> usize {
239 self.0.len()
240 }
241
242 pub fn is_empty(&self) -> bool {
244 self.0.is_empty()
245 }
246
247 pub fn iter(&self) -> impl Iterator<Item = (&AssetVaultKey, &i64)> {
249 self.0.iter()
250 }
251
252 pub fn merge(&mut self, other: Self) -> Result<(), AccountDeltaError> {
259 for (&vault_key, &amount) in other.0.iter() {
265 self.add_delta(vault_key, amount)?;
266 }
267
268 Ok(())
269 }
270
271 fn add_delta(&mut self, vault_key: AssetVaultKey, delta: i64) -> Result<(), AccountDeltaError> {
280 match self.0.entry(vault_key) {
281 Entry::Vacant(entry) => {
282 if delta != 0 {
284 entry.insert(delta);
285 }
286 },
287 Entry::Occupied(mut entry) => {
288 let old = *entry.get();
289 let new = old.checked_add(delta).ok_or(
290 AccountDeltaError::FungibleAssetDeltaOverflow {
291 faucet_id: vault_key.faucet_id(),
292 current: old,
293 delta,
294 },
295 )?;
296
297 if new == 0 {
298 entry.remove();
299 } else {
300 *entry.get_mut() = new;
301 }
302 },
303 }
304
305 Ok(())
306 }
307
308 fn validate(&self) -> Result<(), AccountDeltaError> {
313 for vault_key in self.0.keys() {
314 if !matches!(vault_key.faucet_id().account_type(), AccountType::FungibleFaucet) {
315 return Err(AccountDeltaError::NotAFungibleFaucetId(vault_key.faucet_id()));
316 }
317 }
318
319 Ok(())
320 }
321
322 pub(super) fn append_delta_elements(&self, elements: &mut Vec<Felt>) {
333 for (vault_key, amount_delta) in self.iter() {
334 debug_assert_ne!(
337 *amount_delta, 0,
338 "fungible asset iterator should never yield amount deltas of 0"
339 );
340
341 let was_added = if *amount_delta > 0 { ONE } else { ZERO };
342 let amount_delta = Felt::try_from(amount_delta.unsigned_abs())
343 .expect("amount delta should be less than i64::MAX");
344
345 let key_word = vault_key.to_word();
346 elements.extend_from_slice(&[
347 DOMAIN_ASSET,
348 was_added,
349 key_word[2], key_word[3], ]);
352 elements.extend_from_slice(&[amount_delta, ZERO, ZERO, ZERO]);
353 }
354 }
355}
356
357impl Serializable for FungibleAssetDelta {
358 fn write_into<W: ByteWriter>(&self, target: &mut W) {
359 target.write_usize(self.0.len());
360 target.write_many(self.0.iter().map(|(vault_key, &delta)| (*vault_key, delta as u64)));
365 }
366
367 fn get_size_hint(&self) -> usize {
368 const ENTRY_SIZE: usize = AssetVaultKey::SERIALIZED_SIZE + core::mem::size_of::<u64>();
369 self.0.len().get_size_hint() + self.0.len() * ENTRY_SIZE
370 }
371}
372
373impl Deserializable for FungibleAssetDelta {
374 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
375 let num_fungible_assets = source.read_usize()?;
376 let map = source
380 .read_many_iter::<(AssetVaultKey, u64)>(num_fungible_assets)?
381 .map(|result| result.map(|(vault_key, delta_as_u64)| (vault_key, delta_as_u64 as i64)))
382 .collect::<Result<_, _>>()?;
383
384 Self::new(map).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
385 }
386}
387
388#[derive(Clone, Debug, Default, PartialEq, Eq)]
396pub struct NonFungibleAssetDelta(
397 BTreeMap<AssetVaultKey, (NonFungibleAsset, NonFungibleDeltaAction)>,
398);
399
400impl NonFungibleAssetDelta {
401 pub const fn new(
403 map: BTreeMap<AssetVaultKey, (NonFungibleAsset, NonFungibleDeltaAction)>,
404 ) -> Self {
405 Self(map)
406 }
407
408 pub fn add(&mut self, asset: NonFungibleAsset) -> Result<(), AccountDeltaError> {
413 self.apply_action(asset, NonFungibleDeltaAction::Add)
414 }
415
416 pub fn remove(&mut self, asset: NonFungibleAsset) -> Result<(), AccountDeltaError> {
421 self.apply_action(asset, NonFungibleDeltaAction::Remove)
422 }
423
424 pub fn num_assets(&self) -> usize {
426 self.0.len()
427 }
428
429 pub fn is_empty(&self) -> bool {
431 self.0.is_empty()
432 }
433
434 pub fn iter(&self) -> impl Iterator<Item = (&NonFungibleAsset, &NonFungibleDeltaAction)> {
436 self.0
437 .iter()
438 .map(|(_key, (non_fungible_asset, delta_action))| (non_fungible_asset, delta_action))
439 }
440
441 pub fn merge(&mut self, other: Self) -> Result<(), AccountDeltaError> {
448 for (&asset, &action) in other.iter() {
450 self.apply_action(asset, action)?;
451 }
452
453 Ok(())
454 }
455
456 fn apply_action(
465 &mut self,
466 asset: NonFungibleAsset,
467 action: NonFungibleDeltaAction,
468 ) -> Result<(), AccountDeltaError> {
469 match self.0.entry(asset.vault_key()) {
470 Entry::Vacant(entry) => {
471 entry.insert((asset, action));
472 },
473 Entry::Occupied(entry) => {
474 let (_prev_asset, previous_action) = *entry.get();
475 if previous_action == action {
476 return Err(AccountDeltaError::DuplicateNonFungibleVaultUpdate(asset));
478 }
479 entry.remove();
481 },
482 }
483
484 Ok(())
485 }
486
487 fn filter_by_action(
489 &self,
490 action: NonFungibleDeltaAction,
491 ) -> impl Iterator<Item = NonFungibleAsset> + '_ {
492 self.0
493 .iter()
494 .filter(move |&(_, (_asset, cur_action))| cur_action == &action)
495 .map(|(_key, (asset, _action))| *asset)
496 }
497
498 pub(super) fn append_delta_elements(&self, elements: &mut Vec<Felt>) {
501 for (asset, action) in self.iter() {
502 let was_added = match action {
503 NonFungibleDeltaAction::Remove => ZERO,
504 NonFungibleDeltaAction::Add => ONE,
505 };
506
507 let key_word = asset.vault_key().to_word();
508 elements.extend_from_slice(&[
509 DOMAIN_ASSET,
510 was_added,
511 key_word[2], key_word[3], ]);
514 elements.extend_from_slice(asset.to_value_word().as_elements());
515 }
516 }
517}
518
519impl Serializable for NonFungibleAssetDelta {
520 fn write_into<W: ByteWriter>(&self, target: &mut W) {
521 let added: Vec<_> = self.filter_by_action(NonFungibleDeltaAction::Add).collect();
522 let removed: Vec<_> = self.filter_by_action(NonFungibleDeltaAction::Remove).collect();
523
524 target.write_usize(added.len());
525 target.write_many(added.iter());
526
527 target.write_usize(removed.len());
528 target.write_many(removed.iter());
529 }
530
531 fn get_size_hint(&self) -> usize {
532 let added = self.filter_by_action(NonFungibleDeltaAction::Add).count();
533 let removed = self.filter_by_action(NonFungibleDeltaAction::Remove).count();
534
535 added.get_size_hint()
536 + removed.get_size_hint()
537 + added * NonFungibleAsset::SERIALIZED_SIZE
538 + removed * NonFungibleAsset::SERIALIZED_SIZE
539 }
540}
541
542impl Deserializable for NonFungibleAssetDelta {
543 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
544 let mut map = BTreeMap::new();
545
546 let num_added = source.read_usize()?;
547 for _ in 0..num_added {
548 let added_asset: NonFungibleAsset = source.read()?;
549 map.insert(added_asset.vault_key(), (added_asset, NonFungibleDeltaAction::Add));
550 }
551
552 let num_removed = source.read_usize()?;
553 for _ in 0..num_removed {
554 let removed_asset: NonFungibleAsset = source.read()?;
555 map.insert(removed_asset.vault_key(), (removed_asset, NonFungibleDeltaAction::Remove));
556 }
557
558 Ok(Self::new(map))
559 }
560}
561
562#[derive(Clone, Copy, Debug, PartialEq, Eq)]
563pub enum NonFungibleDeltaAction {
564 Add,
565 Remove,
566}
567
568#[cfg(test)]
572mod tests {
573 use super::{AccountVaultDelta, Deserializable, Serializable};
574 use crate::account::AccountId;
575 use crate::asset::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails};
576 use crate::testing::account_id::{
577 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
578 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
579 };
580
581 #[test]
582 fn test_serde_account_vault() {
583 let asset_0 = FungibleAsset::mock(100);
584 let asset_1 = NonFungibleAsset::mock(&[10, 21, 32, 43]);
585 let delta = AccountVaultDelta::from_iters([asset_0], [asset_1]);
586
587 let serialized = delta.to_bytes();
588 let deserialized = AccountVaultDelta::read_from_bytes(&serialized).unwrap();
589 assert_eq!(deserialized, delta);
590 }
591
592 #[test]
593 fn test_is_empty_account_vault() {
594 let faucet = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
595 let asset: Asset = FungibleAsset::new(faucet, 123).unwrap().into();
596
597 assert!(AccountVaultDelta::default().is_empty());
598 assert!(!AccountVaultDelta::from_iters([asset], []).is_empty());
599 assert!(!AccountVaultDelta::from_iters([], [asset]).is_empty());
600 }
601
602 #[rstest::rstest]
603 #[case::pos_pos(50, 50, Some(100))]
604 #[case::neg_neg(-50, -50, Some(-100))]
605 #[case::empty_pos(0, 50, Some(50))]
606 #[case::empty_neg(0, -50, Some(-50))]
607 #[case::nullify_pos_neg(100, -100, Some(0))]
608 #[case::nullify_neg_pos(-100, 100, Some(0))]
609 #[case::overflow(FungibleAsset::MAX_AMOUNT as i64, FungibleAsset::MAX_AMOUNT as i64, None)]
610 #[case::underflow(-(FungibleAsset::MAX_AMOUNT as i64), -(FungibleAsset::MAX_AMOUNT as i64), None)]
611 #[test]
612 fn merge_fungible_aggregation(#[case] x: i64, #[case] y: i64, #[case] expected: Option<i64>) {
613 fn create_delta_with_fungible(account_id: AccountId, amount: i64) -> AccountVaultDelta {
616 let asset = FungibleAsset::new(account_id, amount.unsigned_abs()).unwrap().into();
617 match amount {
618 0 => AccountVaultDelta::default(),
619 x if x.is_positive() => AccountVaultDelta::from_iters([asset], []),
620 _ => AccountVaultDelta::from_iters([], [asset]),
621 }
622 }
623
624 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap();
625
626 let mut delta_x = create_delta_with_fungible(account_id, x);
627 let delta_y = create_delta_with_fungible(account_id, y);
628
629 let result = delta_x.merge(delta_y);
630
631 if let Some(expected) = expected {
633 let expected = create_delta_with_fungible(account_id, expected);
634 assert_eq!(result.map(|_| delta_x).unwrap(), expected);
635 } else {
636 assert!(result.is_err());
637 }
638 }
639
640 #[rstest::rstest]
641 #[case::empty_removed(None, Some(false), Ok(Some(false)))]
642 #[case::empty_added(None, Some(true), Ok(Some(true)))]
643 #[case::add_remove(Some(true), Some(false), Ok(None))]
644 #[case::remove_add(Some(false), Some(true), Ok(None))]
645 #[case::double_add(Some(true), Some(true), Err(()))]
646 #[case::double_remove(Some(false), Some(false), Err(()))]
647 #[test]
648 fn merge_non_fungible_aggregation(
649 #[case] x: Option<bool>,
650 #[case] y: Option<bool>,
651 #[case] expected: Result<Option<bool>, ()>,
652 ) {
653 fn create_delta_with_non_fungible(
656 account_id: AccountId,
657 added: Option<bool>,
658 ) -> AccountVaultDelta {
659 let asset: Asset = NonFungibleAsset::new(
660 &NonFungibleAssetDetails::new(account_id, vec![1, 2, 3]).unwrap(),
661 )
662 .unwrap()
663 .into();
664
665 match added {
666 Some(true) => AccountVaultDelta::from_iters([asset], []),
667 Some(false) => AccountVaultDelta::from_iters([], [asset]),
668 None => AccountVaultDelta::default(),
669 }
670 }
671
672 let account_id = NonFungibleAsset::mock_issuer();
673
674 let mut delta_x = create_delta_with_non_fungible(account_id, x);
675 let delta_y = create_delta_with_non_fungible(account_id, y);
676
677 let result = delta_x.merge(delta_y);
678
679 if let Ok(expected) = expected {
680 let expected = create_delta_with_non_fungible(account_id, expected);
681 assert_eq!(result.map(|_| delta_x).unwrap(), expected);
682 } else {
683 assert!(result.is_err());
684 }
685 }
686}