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 miden_core::Word;
7
8use super::{
9 AccountDeltaError,
10 ByteReader,
11 ByteWriter,
12 Deserializable,
13 DeserializationError,
14 Serializable,
15};
16use crate::Felt;
17use crate::account::delta::AssetDeltaOperation;
18use crate::asset::{Asset, AssetId, FungibleAsset, NonFungibleAsset};
19
20#[derive(Clone, Debug, Default, PartialEq, Eq)]
30pub struct AccountVaultDelta {
31 fungible: FungibleAssetDelta,
32 non_fungible: NonFungibleAssetDelta,
33}
34
35impl AccountVaultDelta {
36 pub(in crate::account) const DOMAIN: Felt = Felt::new_unchecked(3);
38
39 pub const fn new(fungible: FungibleAssetDelta, non_fungible: NonFungibleAssetDelta) -> Self {
45 Self { fungible, non_fungible }
46 }
47
48 pub fn fungible(&self) -> &FungibleAssetDelta {
50 &self.fungible
51 }
52
53 pub fn non_fungible(&self) -> &NonFungibleAssetDelta {
55 &self.non_fungible
56 }
57
58 pub fn is_empty(&self) -> bool {
60 self.fungible.is_empty() && self.non_fungible.is_empty()
61 }
62
63 pub fn add_asset(&mut self, asset: Asset) -> Result<(), AccountDeltaError> {
65 match asset {
66 Asset::Fungible(asset) => self.fungible.add(asset),
67 Asset::NonFungible(asset) => self.non_fungible.add(asset),
68 }
69 }
70
71 pub fn remove_asset(&mut self, asset: Asset) -> Result<(), AccountDeltaError> {
73 match asset {
74 Asset::Fungible(asset) => self.fungible.remove(asset),
75 Asset::NonFungible(asset) => self.non_fungible.remove(asset),
76 }
77 }
78
79 pub fn added_assets(&self) -> impl Iterator<Item = crate::asset::Asset> + '_ {
81 self.fungible
82 .0
83 .iter()
84 .filter(|&(_, &value)| value >= 0)
85 .map(|(asset_id, &diff)| {
86 Asset::Fungible(
87 FungibleAsset::new(asset_id.faucet_id(), diff.unsigned_abs()).unwrap(),
88 )
89 })
90 .chain(
91 self.non_fungible
92 .filter_by_action(NonFungibleDeltaAction::Add)
93 .map(Asset::NonFungible),
94 )
95 }
96
97 pub fn removed_assets(&self) -> impl Iterator<Item = crate::asset::Asset> + '_ {
99 self.fungible
100 .0
101 .iter()
102 .filter(|&(_, &value)| value < 0)
103 .map(|(asset_id, &diff)| {
104 Asset::Fungible(
105 FungibleAsset::new(asset_id.faucet_id(), diff.unsigned_abs()).unwrap(),
106 )
107 })
108 .chain(
109 self.non_fungible
110 .filter_by_action(NonFungibleDeltaAction::Remove)
111 .map(Asset::NonFungible),
112 )
113 }
114
115 pub(super) fn append_delta_elements(&self, elements: &mut Vec<Felt>) {
118 let added_assets = BTreeMap::from_iter(
123 self.added_assets().map(|asset| (asset.id(), asset.to_value_word())),
124 );
125 let removed_assets = BTreeMap::from_iter(
126 self.removed_assets().map(|asset| (asset.id(), asset.to_value_word())),
127 );
128
129 Self::add_asset_section(AssetDeltaOperation::Add, added_assets, elements);
130 Self::add_asset_section(AssetDeltaOperation::Remove, removed_assets, elements);
131 }
132
133 fn add_asset_section(
134 delta_op: AssetDeltaOperation,
135 assets: BTreeMap<AssetId, Word>,
136 elements: &mut Vec<Felt>,
137 ) {
138 let num_changed_assets = assets.len();
139 for (asset_id, asset_value) in assets {
140 elements.extend_from_slice(asset_id.to_word().as_elements());
141 elements.extend_from_slice(asset_value.as_elements());
142 }
143
144 if num_changed_assets != 0 {
145 let num_changed_assets = Felt::try_from(num_changed_assets as u64)
146 .expect("number of changed assets should not exceed max representable felt");
147
148 elements.extend_from_slice(&[
149 Self::DOMAIN,
150 Felt::from(delta_op.as_u8()),
151 num_changed_assets,
152 Felt::ZERO,
153 ]);
154 elements.extend_from_slice(Word::empty().as_elements());
155 }
156 }
157}
158
159#[cfg(any(feature = "testing", test))]
160impl AccountVaultDelta {
161 pub fn from_iters(
163 added_assets: impl IntoIterator<Item = crate::asset::Asset>,
164 removed_assets: impl IntoIterator<Item = crate::asset::Asset>,
165 ) -> Self {
166 let mut fungible = FungibleAssetDelta::default();
167 let mut non_fungible = NonFungibleAssetDelta::default();
168
169 for asset in added_assets {
170 match asset {
171 Asset::Fungible(asset) => {
172 fungible.add(asset).unwrap();
173 },
174 Asset::NonFungible(asset) => {
175 non_fungible.add(asset).unwrap();
176 },
177 }
178 }
179
180 for asset in removed_assets {
181 match asset {
182 Asset::Fungible(asset) => {
183 fungible.remove(asset).unwrap();
184 },
185 Asset::NonFungible(asset) => {
186 non_fungible.remove(asset).unwrap();
187 },
188 }
189 }
190
191 Self { fungible, non_fungible }
192 }
193}
194
195impl Serializable for AccountVaultDelta {
196 fn write_into<W: ByteWriter>(&self, target: &mut W) {
197 target.write(&self.fungible);
198 target.write(&self.non_fungible);
199 }
200
201 fn get_size_hint(&self) -> usize {
202 self.fungible.get_size_hint() + self.non_fungible.get_size_hint()
203 }
204}
205
206impl Deserializable for AccountVaultDelta {
207 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
208 let fungible = source.read()?;
209 let non_fungible = source.read()?;
210
211 Ok(Self::new(fungible, non_fungible))
212 }
213}
214
215#[derive(Clone, Debug, Default, PartialEq, Eq)]
223pub struct FungibleAssetDelta(BTreeMap<AssetId, i64>);
224
225impl FungibleAssetDelta {
226 pub fn new(map: BTreeMap<AssetId, i64>) -> Result<Self, AccountDeltaError> {
231 Self::validate(&map)?;
232
233 Ok(Self(map))
234 }
235
236 pub fn add(&mut self, asset: FungibleAsset) -> Result<(), AccountDeltaError> {
241 let amount: i64 = asset.amount().as_i64();
242 self.add_delta(asset.id(), amount)
243 }
244
245 pub fn remove(&mut self, asset: FungibleAsset) -> Result<(), AccountDeltaError> {
250 let amount: i64 = asset.amount().as_i64();
251 self.add_delta(asset.id(), -amount)
252 }
253
254 pub fn amount(&self, asset_id: &AssetId) -> Option<i64> {
256 self.0.get(asset_id).copied()
257 }
258
259 pub fn num_assets(&self) -> usize {
261 self.0.len()
262 }
263
264 pub fn is_empty(&self) -> bool {
266 self.0.is_empty()
267 }
268
269 pub fn iter(&self) -> impl Iterator<Item = (&AssetId, &i64)> {
271 self.0.iter()
272 }
273
274 fn add_delta(&mut self, asset_id: AssetId, delta: i64) -> Result<(), AccountDeltaError> {
283 match self.0.entry(asset_id) {
284 Entry::Vacant(entry) => {
285 if delta != 0 {
287 entry.insert(delta);
288 }
289 },
290 Entry::Occupied(mut entry) => {
291 let old = *entry.get();
292 let new = old.checked_add(delta).ok_or(
293 AccountDeltaError::FungibleAssetDeltaOverflow {
294 faucet_id: asset_id.faucet_id(),
295 current: old,
296 delta,
297 },
298 )?;
299
300 if new == 0 {
301 entry.remove();
302 } else {
303 *entry.get_mut() = new;
304 }
305 },
306 }
307
308 Ok(())
309 }
310
311 fn validate(map: &BTreeMap<AssetId, i64>) -> Result<(), AccountDeltaError> {
316 for asset_id in map.keys() {
317 if !asset_id.composition().is_fungible() {
318 return Err(AccountDeltaError::NotAFungibleFaucetId(asset_id.faucet_id()));
319 }
320 }
321
322 Ok(())
323 }
324}
325
326impl Serializable for FungibleAssetDelta {
327 fn write_into<W: ByteWriter>(&self, target: &mut W) {
328 target.write_usize(self.0.len());
329 target.write_many(self.0.iter().map(|(asset_id, &delta)| (*asset_id, delta as u64)));
334 }
335
336 fn get_size_hint(&self) -> usize {
337 const ENTRY_SIZE: usize = AssetId::SERIALIZED_SIZE + core::mem::size_of::<u64>();
338 self.0.len().get_size_hint() + self.0.len() * ENTRY_SIZE
339 }
340}
341
342impl Deserializable for FungibleAssetDelta {
343 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
344 let num_fungible_assets = source.read_usize()?;
345 let map = source
349 .read_many_iter::<(AssetId, u64)>(num_fungible_assets)?
350 .map(|result| result.map(|(asset_id, delta_as_u64)| (asset_id, delta_as_u64 as i64)))
351 .collect::<Result<_, _>>()?;
352
353 Self::new(map).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
354 }
355}
356
357#[derive(Clone, Debug, Default, PartialEq, Eq)]
365pub struct NonFungibleAssetDelta(BTreeMap<AssetId, (NonFungibleAsset, NonFungibleDeltaAction)>);
366
367impl NonFungibleAssetDelta {
368 pub const fn new(map: BTreeMap<AssetId, (NonFungibleAsset, NonFungibleDeltaAction)>) -> Self {
370 Self(map)
371 }
372
373 pub fn add(&mut self, asset: NonFungibleAsset) -> Result<(), AccountDeltaError> {
378 self.apply_action(asset, NonFungibleDeltaAction::Add)
379 }
380
381 pub fn remove(&mut self, asset: NonFungibleAsset) -> Result<(), AccountDeltaError> {
386 self.apply_action(asset, NonFungibleDeltaAction::Remove)
387 }
388
389 pub fn num_assets(&self) -> usize {
391 self.0.len()
392 }
393
394 pub fn is_empty(&self) -> bool {
396 self.0.is_empty()
397 }
398
399 pub fn iter(&self) -> impl Iterator<Item = (&NonFungibleAsset, &NonFungibleDeltaAction)> {
401 self.0
402 .iter()
403 .map(|(_key, (non_fungible_asset, delta_action))| (non_fungible_asset, delta_action))
404 }
405
406 fn apply_action(
415 &mut self,
416 asset: NonFungibleAsset,
417 action: NonFungibleDeltaAction,
418 ) -> Result<(), AccountDeltaError> {
419 match self.0.entry(asset.id()) {
420 Entry::Vacant(entry) => {
421 entry.insert((asset, action));
422 },
423 Entry::Occupied(entry) => {
424 let (_prev_asset, previous_action) = *entry.get();
425 if previous_action == action {
426 return Err(AccountDeltaError::DuplicateNonFungibleVaultUpdate(asset));
428 }
429 entry.remove();
431 },
432 }
433
434 Ok(())
435 }
436
437 fn filter_by_action(
439 &self,
440 action: NonFungibleDeltaAction,
441 ) -> impl Iterator<Item = NonFungibleAsset> + '_ {
442 self.0
443 .iter()
444 .filter(move |&(_, (_asset, cur_action))| cur_action == &action)
445 .map(|(_key, (asset, _action))| *asset)
446 }
447}
448
449impl Serializable for NonFungibleAssetDelta {
450 fn write_into<W: ByteWriter>(&self, target: &mut W) {
451 let added: Vec<_> = self.filter_by_action(NonFungibleDeltaAction::Add).collect();
452 let removed: Vec<_> = self.filter_by_action(NonFungibleDeltaAction::Remove).collect();
453
454 target.write_usize(added.len());
455 target.write_many(added.iter());
456
457 target.write_usize(removed.len());
458 target.write_many(removed.iter());
459 }
460
461 fn get_size_hint(&self) -> usize {
462 let added = self.filter_by_action(NonFungibleDeltaAction::Add).count();
463 let removed = self.filter_by_action(NonFungibleDeltaAction::Remove).count();
464
465 added.get_size_hint()
466 + removed.get_size_hint()
467 + added * NonFungibleAsset::SERIALIZED_SIZE
468 + removed * NonFungibleAsset::SERIALIZED_SIZE
469 }
470}
471
472impl Deserializable for NonFungibleAssetDelta {
473 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
474 let mut map = BTreeMap::new();
475
476 let num_added = source.read_usize()?;
477 for _ in 0..num_added {
478 let added_asset: NonFungibleAsset = source.read()?;
479 map.insert(added_asset.id(), (added_asset, NonFungibleDeltaAction::Add));
480 }
481
482 let num_removed = source.read_usize()?;
483 for _ in 0..num_removed {
484 let removed_asset: NonFungibleAsset = source.read()?;
485 map.insert(removed_asset.id(), (removed_asset, NonFungibleDeltaAction::Remove));
486 }
487
488 Ok(Self::new(map))
489 }
490}
491
492#[derive(Clone, Copy, Debug, PartialEq, Eq)]
493pub enum NonFungibleDeltaAction {
494 Add,
495 Remove,
496}
497
498#[cfg(test)]
502mod tests {
503 use super::{AccountVaultDelta, Deserializable, Serializable};
504 use crate::account::AccountId;
505 use crate::asset::{Asset, FungibleAsset, NonFungibleAsset};
506 use crate::testing::account_id::ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET;
507
508 #[test]
509 fn test_serde_account_vault() {
510 let asset_0 = FungibleAsset::mock(100);
511 let asset_1 = NonFungibleAsset::mock(&[10, 21, 32, 43]);
512 let delta = AccountVaultDelta::from_iters([asset_0], [asset_1]);
513
514 let serialized = delta.to_bytes();
515 let deserialized = AccountVaultDelta::read_from_bytes(&serialized).unwrap();
516 assert_eq!(deserialized, delta);
517 }
518
519 #[test]
520 fn test_is_empty_account_vault() {
521 let faucet = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
522 let asset: Asset = FungibleAsset::new(faucet, 123).unwrap().into();
523
524 assert!(AccountVaultDelta::default().is_empty());
525 assert!(!AccountVaultDelta::from_iters([asset], []).is_empty());
526 assert!(!AccountVaultDelta::from_iters([], [asset]).is_empty());
527 }
528}