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)));
333 }
334
335 fn get_size_hint(&self) -> usize {
336 let entries_size: usize = self
337 .0
338 .keys()
339 .map(|id| {
340 id.get_size_hint() + core::mem::size_of::<u64>()
342 })
343 .sum();
344
345 self.0.len().get_size_hint() + entries_size
346 }
347}
348
349impl Deserializable for FungibleAssetDelta {
350 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
351 let num_fungible_assets = source.read_usize()?;
352 let map = source
356 .read_many_iter::<(AssetId, u64)>(num_fungible_assets)?
357 .map(|result| result.map(|(asset_id, delta_as_u64)| (asset_id, delta_as_u64 as i64)))
358 .collect::<Result<_, _>>()?;
359
360 Self::new(map).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
361 }
362}
363
364#[derive(Clone, Debug, Default, PartialEq, Eq)]
372pub struct NonFungibleAssetDelta(BTreeMap<AssetId, (NonFungibleAsset, NonFungibleDeltaAction)>);
373
374impl NonFungibleAssetDelta {
375 pub const fn new(map: BTreeMap<AssetId, (NonFungibleAsset, NonFungibleDeltaAction)>) -> Self {
377 Self(map)
378 }
379
380 pub fn add(&mut self, asset: NonFungibleAsset) -> Result<(), AccountDeltaError> {
385 self.apply_action(asset, NonFungibleDeltaAction::Add)
386 }
387
388 pub fn remove(&mut self, asset: NonFungibleAsset) -> Result<(), AccountDeltaError> {
393 self.apply_action(asset, NonFungibleDeltaAction::Remove)
394 }
395
396 pub fn num_assets(&self) -> usize {
398 self.0.len()
399 }
400
401 pub fn is_empty(&self) -> bool {
403 self.0.is_empty()
404 }
405
406 pub fn iter(&self) -> impl Iterator<Item = (&NonFungibleAsset, &NonFungibleDeltaAction)> {
408 self.0
409 .iter()
410 .map(|(_key, (non_fungible_asset, delta_action))| (non_fungible_asset, delta_action))
411 }
412
413 fn apply_action(
422 &mut self,
423 asset: NonFungibleAsset,
424 action: NonFungibleDeltaAction,
425 ) -> Result<(), AccountDeltaError> {
426 match self.0.entry(asset.id()) {
427 Entry::Vacant(entry) => {
428 entry.insert((asset, action));
429 },
430 Entry::Occupied(entry) => {
431 let (_prev_asset, previous_action) = *entry.get();
432 if previous_action == action {
433 return Err(AccountDeltaError::DuplicateNonFungibleVaultUpdate(asset));
435 }
436 entry.remove();
438 },
439 }
440
441 Ok(())
442 }
443
444 fn filter_by_action(
446 &self,
447 action: NonFungibleDeltaAction,
448 ) -> impl Iterator<Item = NonFungibleAsset> + '_ {
449 self.0
450 .iter()
451 .filter(move |&(_, (_asset, cur_action))| cur_action == &action)
452 .map(|(_key, (asset, _action))| *asset)
453 }
454}
455
456impl Serializable for NonFungibleAssetDelta {
457 fn write_into<W: ByteWriter>(&self, target: &mut W) {
458 let added: Vec<_> = self.filter_by_action(NonFungibleDeltaAction::Add).collect();
459 let removed: Vec<_> = self.filter_by_action(NonFungibleDeltaAction::Remove).collect();
460
461 target.write_usize(added.len());
462 target.write_many(added.iter());
463
464 target.write_usize(removed.len());
465 target.write_many(removed.iter());
466 }
467
468 fn get_size_hint(&self) -> usize {
469 let added = self.filter_by_action(NonFungibleDeltaAction::Add).count();
470 let removed = self.filter_by_action(NonFungibleDeltaAction::Remove).count();
471
472 added.get_size_hint()
473 + removed.get_size_hint()
474 + added * NonFungibleAsset::SERIALIZED_SIZE
475 + removed * NonFungibleAsset::SERIALIZED_SIZE
476 }
477}
478
479impl Deserializable for NonFungibleAssetDelta {
480 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
481 let mut map = BTreeMap::new();
482
483 let num_added = source.read_usize()?;
484 for _ in 0..num_added {
485 let added_asset: NonFungibleAsset = source.read()?;
486 map.insert(added_asset.id(), (added_asset, NonFungibleDeltaAction::Add));
487 }
488
489 let num_removed = source.read_usize()?;
490 for _ in 0..num_removed {
491 let removed_asset: NonFungibleAsset = source.read()?;
492 map.insert(removed_asset.id(), (removed_asset, NonFungibleDeltaAction::Remove));
493 }
494
495 Ok(Self::new(map))
496 }
497}
498
499#[derive(Clone, Copy, Debug, PartialEq, Eq)]
500pub enum NonFungibleDeltaAction {
501 Add,
502 Remove,
503}
504
505#[cfg(test)]
509mod tests {
510 use super::{AccountVaultDelta, Deserializable, Serializable};
511 use crate::account::AccountId;
512 use crate::asset::{Asset, FungibleAsset, NonFungibleAsset};
513 use crate::testing::account_id::ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET;
514
515 #[test]
516 fn test_serde_account_vault() {
517 let asset_0 = FungibleAsset::mock(100);
518 let asset_1 = NonFungibleAsset::mock(&[10, 21, 32, 43]);
519 let delta = AccountVaultDelta::from_iters([asset_0], [asset_1]);
520
521 let serialized = delta.to_bytes();
522 let deserialized = AccountVaultDelta::read_from_bytes(&serialized).unwrap();
523 assert_eq!(deserialized, delta);
524 }
525
526 #[test]
527 fn test_is_empty_account_vault() {
528 let faucet = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
529 let asset: Asset = FungibleAsset::new(faucet, 123).unwrap().into();
530
531 assert!(AccountVaultDelta::default().is_empty());
532 assert!(!AccountVaultDelta::from_iters([asset], []).is_empty());
533 assert!(!AccountVaultDelta::from_iters([], [asset]).is_empty());
534 }
535}