miden_objects/account/delta/mod.rs
1use alloc::string::ToString;
2use alloc::vec::Vec;
3
4use crate::account::{
5 Account,
6 AccountCode,
7 AccountId,
8 AccountStorage,
9 StorageSlot,
10 StorageSlotType,
11};
12use crate::asset::AssetVault;
13use crate::crypto::SequentialCommit;
14use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
15use crate::{AccountDeltaError, AccountError, Felt, Word, ZERO};
16
17mod storage;
18pub use storage::{AccountStorageDelta, StorageMapDelta};
19
20mod vault;
21pub use vault::{
22 AccountVaultDelta,
23 FungibleAssetDelta,
24 NonFungibleAssetDelta,
25 NonFungibleDeltaAction,
26};
27
28// ACCOUNT DELTA
29// ================================================================================================
30
31/// The [`AccountDelta`] stores the differences between two account states, which can result from
32/// one or more transaction.
33///
34/// The differences are represented as follows:
35/// - storage: an [`AccountStorageDelta`] that contains the changes to the account storage.
36/// - vault: an [`AccountVaultDelta`] object that contains the changes to the account vault.
37/// - nonce: if the nonce of the account has changed, the _delta_ of the nonce is stored, i.e. the
38/// value by which the nonce increased.
39/// - code: an [`AccountCode`] for new accounts and `None` for others.
40///
41/// The presence of the code in a delta signals if the delta is a _full state_ or _partial state_
42/// delta. A full state delta must be converted into an [`Account`] object, while a partial state
43/// delta must be applied to an existing [`Account`].
44///
45/// TODO(code_upgrades): The ability to track account code updates is an outstanding feature. For
46/// that reason, the account code is not considered as part of the "nonce must be incremented if
47/// state changed" check.
48#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct AccountDelta {
50 /// The ID of the account to which this delta applies. If the delta is created during
51 /// transaction execution, that is the native account of the transaction.
52 account_id: AccountId,
53 /// The delta of the account's storage.
54 storage: AccountStorageDelta,
55 /// The delta of the account's asset vault.
56 vault: AccountVaultDelta,
57 /// The code of a new account (`Some`) or `None` for existing accounts.
58 code: Option<AccountCode>,
59 /// The value by which the nonce was incremented. Must be greater than zero if storage or vault
60 /// are non-empty.
61 nonce_delta: Felt,
62}
63
64impl AccountDelta {
65 // CONSTRUCTOR
66 // --------------------------------------------------------------------------------------------
67
68 /// Returns new [AccountDelta] instantiated from the provided components.
69 ///
70 /// # Errors
71 ///
72 /// - Returns an error if storage or vault were updated, but the nonce_delta is 0.
73 pub fn new(
74 account_id: AccountId,
75 storage: AccountStorageDelta,
76 vault: AccountVaultDelta,
77 nonce_delta: Felt,
78 ) -> Result<Self, AccountDeltaError> {
79 // nonce must be updated if either account storage or vault were updated
80 validate_nonce(nonce_delta, &storage, &vault)?;
81
82 Ok(Self {
83 account_id,
84 storage,
85 vault,
86 code: None,
87 nonce_delta,
88 })
89 }
90
91 // PUBLIC MUTATORS
92 // --------------------------------------------------------------------------------------------
93
94 /// Merge another [AccountDelta] into this one.
95 pub fn merge(&mut self, other: Self) -> Result<(), AccountDeltaError> {
96 let new_nonce_delta = self.nonce_delta + other.nonce_delta;
97
98 if new_nonce_delta.as_int() < self.nonce_delta.as_int() {
99 return Err(AccountDeltaError::NonceIncrementOverflow {
100 current: self.nonce_delta,
101 increment: other.nonce_delta,
102 new: new_nonce_delta,
103 });
104 }
105
106 // TODO(code_upgrades): This should go away once we have proper account code updates in
107 // deltas. Then, the two code updates can be merged. For now, code cannot be merged
108 // and this should never happen.
109 if self.is_full_state() && other.is_full_state() {
110 return Err(AccountDeltaError::MergingFullStateDeltas);
111 }
112
113 if let Some(code) = other.code {
114 self.code = Some(code);
115 }
116
117 self.nonce_delta = new_nonce_delta;
118
119 self.storage.merge(other.storage)?;
120 self.vault.merge(other.vault)
121 }
122
123 /// Returns a mutable reference to the account vault delta.
124 pub fn vault_mut(&mut self) -> &mut AccountVaultDelta {
125 &mut self.vault
126 }
127
128 /// Sets the [`AccountCode`] of the delta.
129 pub fn with_code(mut self, code: Option<AccountCode>) -> Self {
130 self.code = code;
131 self
132 }
133
134 // PUBLIC ACCESSORS
135 // --------------------------------------------------------------------------------------------
136
137 /// Returns true if this account delta does not contain any vault, storage or nonce updates.
138 pub fn is_empty(&self) -> bool {
139 self.storage.is_empty() && self.vault.is_empty() && self.nonce_delta == ZERO
140 }
141
142 /// Returns `true` if this delta is a "full state" delta, `false` otherwise, i.e. if it is a
143 /// "partial state" delta.
144 ///
145 /// See the type-level docs for more on this distinction.
146 pub fn is_full_state(&self) -> bool {
147 // TODO(code_upgrades): Change this to another detection mechanism once we have code upgrade
148 // support, at which point the presence of code may not be enough of an indication
149 // that a delta can be converted to a full account.
150 self.code.is_some()
151 }
152
153 /// Returns storage updates for this account delta.
154 pub fn storage(&self) -> &AccountStorageDelta {
155 &self.storage
156 }
157
158 /// Returns vault updates for this account delta.
159 pub fn vault(&self) -> &AccountVaultDelta {
160 &self.vault
161 }
162
163 /// Returns the amount by which the nonce was incremented.
164 pub fn nonce_delta(&self) -> Felt {
165 self.nonce_delta
166 }
167
168 /// Returns the account ID to which this delta applies.
169 pub fn id(&self) -> AccountId {
170 self.account_id
171 }
172
173 /// Returns a reference to the account code of this delta, if present.
174 pub fn code(&self) -> Option<&AccountCode> {
175 self.code.as_ref()
176 }
177
178 /// Converts this storage delta into individual delta components.
179 pub fn into_parts(self) -> (AccountStorageDelta, AccountVaultDelta, Option<AccountCode>, Felt) {
180 (self.storage, self.vault, self.code, self.nonce_delta)
181 }
182
183 /// Computes the commitment to the account delta.
184 ///
185 /// # Computation
186 ///
187 /// The delta is a sequential hash over a vector of field elements which starts out empty and
188 /// is appended to in the following way. Whenever sorting is expected, it is that of a
189 /// [`LexicographicWord`](crate::LexicographicWord). The WORD layout is in memory-order.
190 ///
191 /// - Append `[[nonce_delta, 0, account_id_suffix, account_id_prefix], EMPTY_WORD]`, where
192 /// account_id_{prefix,suffix} are the prefix and suffix felts of the native account id and
193 /// nonce_delta is the value by which the nonce was incremented.
194 /// - Fungible Asset Delta
195 /// - For each **updated** fungible asset, sorted by its vault key, whose amount delta is
196 /// **non-zero**:
197 /// - Append `[domain = 1, was_added, 0, 0]`.
198 /// - Append `[amount, 0, faucet_id_suffix, faucet_id_prefix]` where amount is the delta by
199 /// which the fungible asset's amount has changed and was_added is a boolean flag
200 /// indicating whether the amount was added (1) or subtracted (0).
201 /// - Non-Fungible Asset Delta
202 /// - For each **updated** non-fungible asset, sorted by its vault key:
203 /// - Append `[domain = 1, was_added, 0, 0]` where was_added is a boolean flag indicating
204 /// whether the asset was added (1) or removed (0). Note that the domain is the same for
205 /// assets since `faucet_id_prefix` is at the same position in the layout for both assets,
206 /// and, by design, it is never the same for fungible and non-fungible assets.
207 /// - Append `[hash0, hash1, hash2, faucet_id_prefix]`, i.e. the non-fungible asset.
208 /// - Storage Slots - for each slot **whose value has changed**, depending on the slot type:
209 /// - Value Slot
210 /// - Append `[[domain = 2, slot_idx, 0, 0], NEW_VALUE]` where NEW_VALUE is the new value of
211 /// the slot and slot_idx is the index of the slot.
212 /// - Map Slot
213 /// - For each key-value pair, sorted by key, whose new value is different from the previous
214 /// value in the map:
215 /// - Append `[KEY, NEW_VALUE]`.
216 /// - Append `[[domain = 3, slot_idx, num_changed_entries, 0], 0, 0, 0, 0]`, except if
217 /// `num_changed_entries` is 0, where slot_idx is the index of the slot and
218 /// `num_changed_entries` is the number of changed key-value pairs in the map.
219 ///
220 /// # Rationale
221 ///
222 /// The rationale for this layout is that hashing in the VM should be as efficient as possible
223 /// and minimize the number of branches to be as efficient as possible. Every high-level section
224 /// in this bullet point list should add an even number of words since the hasher operates
225 /// on double words. In the VM, each permutation is done immediately, so adding an uneven
226 /// number of words in a given step will result in more difficulty in the MASM implementation.
227 ///
228 /// # Security
229 ///
230 /// The general concern with the commitment is that two distinct deltas must never hash to the
231 /// same commitment. E.g. a commitment of a delta that changes a key-value pair in a storage
232 /// map slot should be different from a delta that adds a non-fungible asset to the vault.
233 /// If not, a delta can be crafted in the VM that sets a map key but a malicious actor
234 /// crafts a delta outside the VM that adds a non-fungible asset. To prevent that, a couple
235 /// of measures are taken.
236 ///
237 /// - Because multiple unrelated contexts (e.g. vaults and storage slots) are hashed in the same
238 /// hasher, domain separators are used to disambiguate. For each changed asset and each
239 /// changed slot in the delta, a domain separator is hashed into the delta. The domain
240 /// separator is always at the same index in each layout so it cannot be maliciously crafted
241 /// (see below for an example).
242 /// - Storage value slots:
243 /// - since only changed value slots are included in the delta, there is no ambiguity between
244 /// a value slot being set to EMPTY_WORD and its value being unchanged.
245 /// - Storage map slots:
246 /// - Map slots append a header which summarizes the changes in the slot, in particular the
247 /// slot index and number of changed entries. Since only changed slots are included, the
248 /// number of changed entries is never zero.
249 /// - Two distinct storage map slots use the same domain but are disambiguated due to
250 /// inclusion of the slot index.
251 ///
252 /// **Domain Separators**
253 ///
254 /// As an example for ambiguity, consider these two deltas:
255 ///
256 /// ```text
257 /// [
258 /// ID_AND_NONCE, EMPTY_WORD,
259 /// [/* no fungible asset delta */],
260 /// [[domain = 1, was_added = 1, 0, 0], NON_FUNGIBLE_ASSET],
261 /// [/* no storage delta */]
262 /// ]
263 /// ```
264 ///
265 /// ```text
266 /// [
267 /// ID_AND_NONCE, EMPTY_WORD,
268 /// [/* no fungible asset delta */],
269 /// [/* no non-fungible asset delta */],
270 /// [[domain = 2, slot_idx = 1, 0, 0], NEW_VALUE]
271 /// ]
272 /// ```
273 ///
274 /// `NEW_VALUE` is user-controllable so it can be crafted to match `NON_FUNGIBLE_ASSET`. The
275 /// domain separator is then the only value that differentiates these two deltas. This shows the
276 /// importance of placing the domain separators in the same index within each word's layout
277 /// which makes it easy to see that this value cannot be crafted to be the same.
278 ///
279 /// **Number of Changed Entries**
280 ///
281 /// As an example for ambiguity, consider these two deltas:
282 ///
283 /// ```text
284 /// [
285 /// EMPTY_WORD, ID_AND_NONCE,
286 /// [/* no fungible asset delta */],
287 /// [[domain = 1, was_added = 1, 0, 0], NON_FUNGIBLE_ASSET],
288 /// [/* no storage delta */],
289 /// ]
290 /// ```
291 ///
292 /// ```text
293 /// [
294 /// ID_AND_NONCE, EMPTY_WORD,
295 /// [/* no fungible asset delta */],
296 /// [/* no non-fungible asset delta */],
297 /// [KEY0, VALUE0],
298 /// [KEY1, VALUE1],
299 /// [domain = 3, slot_idx = 0, num_changed_entries = 2, 0, 0, 0, 0, 0]
300 /// ]
301 /// ```
302 ///
303 /// The keys and values of map slots are user-controllable so `KEY0` and `VALUE0` can be crafted
304 /// to match `NON_FUNGIBLE_ASSET` and its metadata. Including the header of the map slot
305 /// additionally hashes the map domain into the delta, but if the header was included whenever
306 /// _any_ value in the map has changed, it would cause ambiguity about whether `KEY0`/`VALUE0`
307 /// are in fact map keys or a non-fungible asset (or any asset or a value storage slot more
308 /// generally). Including `num_changed_entries` disambiguates this situation, by ensuring
309 /// that the delta commitment is different when, e.g. 1) a non-fungible asset and one key-value
310 /// pair have changed and 2) when two key-value pairs have changed.
311 pub fn to_commitment(&self) -> Word {
312 <Self as SequentialCommit>::to_commitment(self)
313 }
314}
315
316impl TryFrom<&AccountDelta> for Account {
317 type Error = AccountError;
318
319 /// Converts an [`AccountDelta`] into an [`Account`].
320 ///
321 /// Conceptually, this applies the delta onto an empty account.
322 ///
323 /// # Errors
324 ///
325 /// Returns an error if:
326 /// - If the delta is not a full state delta. See [`AccountDelta`] for details.
327 /// - If any vault delta operation removes an asset.
328 /// - If any vault delta operation adds an asset that would overflow the maximum representable
329 /// amount.
330 /// - If any storage delta update violates account storage constraints.
331 fn try_from(delta: &AccountDelta) -> Result<Self, Self::Error> {
332 if !delta.is_full_state() {
333 return Err(AccountError::PartialStateDeltaToAccount);
334 }
335
336 let Some(code) = delta.code().cloned() else {
337 return Err(AccountError::PartialStateDeltaToAccount);
338 };
339
340 let mut vault = AssetVault::default();
341 vault.apply_delta(delta.vault()).map_err(AccountError::AssetVaultUpdateError)?;
342
343 // Once we support addition and removal of storage slots, we may be able to change
344 // this to create an empty account and use `Account::apply_delta` instead.
345 // For now, we need to create the initial storage of the account with the same slot types.
346 let mut empty_storage_slots = Vec::new();
347 for slot_idx in 0..u8::MAX {
348 let slot = match delta.storage().slot_type(slot_idx) {
349 Some(StorageSlotType::Value) => StorageSlot::empty_value(),
350 Some(StorageSlotType::Map) => StorageSlot::empty_map(),
351 None => break,
352 };
353 empty_storage_slots.push(slot);
354 }
355 let mut storage = AccountStorage::new(empty_storage_slots)
356 .expect("storage delta should contain a valid number of slots");
357 storage.apply_delta(delta.storage())?;
358
359 // The nonce of the account is the initial nonce of 0 plus the nonce_delta, so the
360 // nonce_delta itself.
361 let nonce = delta.nonce_delta();
362
363 Account::new(delta.id(), vault, storage, code, nonce, None)
364 }
365}
366
367impl SequentialCommit for AccountDelta {
368 type Commitment = Word;
369
370 /// Reduces the delta to a sequence of field elements.
371 ///
372 /// See [AccountDelta::to_commitment()] for more details.
373 fn to_elements(&self) -> Vec<Felt> {
374 // The commitment to an empty delta is defined as the empty word.
375 if self.is_empty() {
376 return Vec::new();
377 }
378
379 // Minor optimization: At least 24 elements are always added.
380 let mut elements = Vec::with_capacity(24);
381
382 // ID and Nonce
383 elements.extend_from_slice(&[
384 self.nonce_delta,
385 ZERO,
386 self.account_id.suffix(),
387 self.account_id.prefix().as_felt(),
388 ]);
389 elements.extend_from_slice(Word::empty().as_elements());
390
391 // Vault Delta
392 self.vault.append_delta_elements(&mut elements);
393
394 // Storage Delta
395 self.storage.append_delta_elements(&mut elements);
396
397 debug_assert!(
398 elements.len() % (2 * crate::WORD_SIZE) == 0,
399 "expected elements to contain an even number of words, but it contained {} elements",
400 elements.len()
401 );
402
403 elements
404 }
405}
406
407// ACCOUNT UPDATE DETAILS
408// ================================================================================================
409
410/// [`AccountUpdateDetails`] describes the details of one or more transactions executed against an
411/// account.
412///
413/// In particular, private account changes aren't tracked at all; they are represented as
414/// [`AccountUpdateDetails::Private`].
415///
416/// Non-private accounts are tracked as an [`AccountDelta`]. If the account is new, the delta can be
417/// converted into an [`Account`]. If not, the delta can be applied to the existing account using
418/// [`Account::apply_delta`].
419///
420/// Note that these details can represent the changes from one or more transactions in which case
421/// the deltas of each transaction are merged together using [`AccountDelta::merge`].
422#[derive(Clone, Debug, PartialEq, Eq)]
423pub enum AccountUpdateDetails {
424 /// The state update details of a private account is not publicly accessible.
425 Private,
426
427 /// The state update details of non-private accounts.
428 Delta(AccountDelta),
429}
430
431impl AccountUpdateDetails {
432 /// Returns `true` if the account update details are for private account.
433 pub fn is_private(&self) -> bool {
434 matches!(self, Self::Private)
435 }
436
437 /// Merges the `other` update into this one.
438 ///
439 /// This account update is assumed to come before the other.
440 pub fn merge(self, other: AccountUpdateDetails) -> Result<Self, AccountDeltaError> {
441 let merged_update = match (self, other) {
442 (AccountUpdateDetails::Private, AccountUpdateDetails::Private) => {
443 AccountUpdateDetails::Private
444 },
445 (AccountUpdateDetails::Delta(mut delta), AccountUpdateDetails::Delta(new_delta)) => {
446 delta.merge(new_delta)?;
447 AccountUpdateDetails::Delta(delta)
448 },
449 (left, right) => {
450 return Err(AccountDeltaError::IncompatibleAccountUpdates {
451 left_update_type: left.as_tag_str(),
452 right_update_type: right.as_tag_str(),
453 });
454 },
455 };
456
457 Ok(merged_update)
458 }
459
460 /// Returns the tag of the [`AccountUpdateDetails`] as a string for inclusion in error messages.
461 pub(crate) const fn as_tag_str(&self) -> &'static str {
462 match self {
463 AccountUpdateDetails::Private => "private",
464 AccountUpdateDetails::Delta(_) => "delta",
465 }
466 }
467}
468
469// SERIALIZATION
470// ================================================================================================
471
472impl Serializable for AccountDelta {
473 fn write_into<W: ByteWriter>(&self, target: &mut W) {
474 self.account_id.write_into(target);
475 self.storage.write_into(target);
476 self.vault.write_into(target);
477 self.code.write_into(target);
478 self.nonce_delta.write_into(target);
479 }
480
481 fn get_size_hint(&self) -> usize {
482 self.account_id.get_size_hint()
483 + self.storage.get_size_hint()
484 + self.vault.get_size_hint()
485 + self.code.get_size_hint()
486 + self.nonce_delta.get_size_hint()
487 }
488}
489
490impl Deserializable for AccountDelta {
491 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
492 let account_id = AccountId::read_from(source)?;
493 let storage = AccountStorageDelta::read_from(source)?;
494 let vault = AccountVaultDelta::read_from(source)?;
495 let code = <Option<AccountCode>>::read_from(source)?;
496 let nonce_delta = Felt::read_from(source)?;
497
498 validate_nonce(nonce_delta, &storage, &vault)
499 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
500
501 Ok(Self {
502 account_id,
503 storage,
504 vault,
505 code,
506 nonce_delta,
507 })
508 }
509}
510
511impl Serializable for AccountUpdateDetails {
512 fn write_into<W: ByteWriter>(&self, target: &mut W) {
513 match self {
514 AccountUpdateDetails::Private => {
515 0_u8.write_into(target);
516 },
517 AccountUpdateDetails::Delta(delta) => {
518 1_u8.write_into(target);
519 delta.write_into(target);
520 },
521 }
522 }
523
524 fn get_size_hint(&self) -> usize {
525 // Size of the serialized enum tag.
526 let u8_size = 0u8.get_size_hint();
527
528 match self {
529 AccountUpdateDetails::Private => u8_size,
530 AccountUpdateDetails::Delta(account_delta) => u8_size + account_delta.get_size_hint(),
531 }
532 }
533}
534
535impl Deserializable for AccountUpdateDetails {
536 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
537 match u8::read_from(source)? {
538 0 => Ok(Self::Private),
539 1 => Ok(Self::Delta(AccountDelta::read_from(source)?)),
540 variant => Err(DeserializationError::InvalidValue(format!(
541 "Unknown variant {variant} for AccountDetails"
542 ))),
543 }
544 }
545}
546
547// HELPER FUNCTIONS
548// ================================================================================================
549
550/// Checks if the nonce was updated correctly given the provided storage and vault deltas.
551///
552/// # Errors
553///
554/// Returns an error if:
555/// - storage or vault were updated, but the nonce_delta was set to 0.
556fn validate_nonce(
557 nonce_delta: Felt,
558 storage: &AccountStorageDelta,
559 vault: &AccountVaultDelta,
560) -> Result<(), AccountDeltaError> {
561 if (!storage.is_empty() || !vault.is_empty()) && nonce_delta == ZERO {
562 return Err(AccountDeltaError::NonEmptyStorageOrVaultDeltaWithZeroNonceDelta);
563 }
564
565 Ok(())
566}
567
568// TESTS
569// ================================================================================================
570
571#[cfg(test)]
572mod tests {
573
574 use assert_matches::assert_matches;
575 use miden_core::utils::Serializable;
576 use miden_core::{Felt, FieldElement};
577
578 use super::{AccountDelta, AccountStorageDelta, AccountVaultDelta};
579 use crate::account::delta::AccountUpdateDetails;
580 use crate::account::{
581 Account,
582 AccountCode,
583 AccountId,
584 AccountStorage,
585 AccountStorageMode,
586 AccountType,
587 StorageMapDelta,
588 };
589 use crate::asset::{
590 Asset,
591 AssetVault,
592 FungibleAsset,
593 NonFungibleAsset,
594 NonFungibleAssetDetails,
595 };
596 use crate::testing::account_id::{
597 ACCOUNT_ID_PRIVATE_SENDER,
598 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
599 AccountIdBuilder,
600 };
601 use crate::{AccountDeltaError, ONE, Word, ZERO};
602
603 #[test]
604 fn account_delta_nonce_validation() {
605 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
606 // empty delta
607 let storage_delta = AccountStorageDelta::new();
608 let vault_delta = AccountVaultDelta::default();
609
610 AccountDelta::new(account_id, storage_delta.clone(), vault_delta.clone(), ZERO).unwrap();
611 AccountDelta::new(account_id, storage_delta.clone(), vault_delta.clone(), ONE).unwrap();
612
613 // non-empty delta
614 let storage_delta = AccountStorageDelta::from_iters([1], [], []);
615
616 assert_matches!(
617 AccountDelta::new(account_id, storage_delta.clone(), vault_delta.clone(), ZERO)
618 .unwrap_err(),
619 AccountDeltaError::NonEmptyStorageOrVaultDeltaWithZeroNonceDelta
620 );
621 AccountDelta::new(account_id, storage_delta.clone(), vault_delta.clone(), ONE).unwrap();
622 }
623
624 #[test]
625 fn account_delta_nonce_overflow() {
626 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
627 let storage_delta = AccountStorageDelta::new();
628 let vault_delta = AccountVaultDelta::default();
629
630 let nonce_delta0 = ONE;
631 let nonce_delta1 = Felt::try_from(0xffff_ffff_0000_0000u64).unwrap();
632
633 let mut delta0 =
634 AccountDelta::new(account_id, storage_delta.clone(), vault_delta.clone(), nonce_delta0)
635 .unwrap();
636 let delta1 =
637 AccountDelta::new(account_id, storage_delta, vault_delta, nonce_delta1).unwrap();
638
639 assert_matches!(delta0.merge(delta1).unwrap_err(), AccountDeltaError::NonceIncrementOverflow {
640 current, increment, new
641 } => {
642 assert_eq!(current, nonce_delta0);
643 assert_eq!(increment, nonce_delta1);
644 assert_eq!(new, nonce_delta0 + nonce_delta1);
645 });
646 }
647
648 #[test]
649 fn account_update_details_size_hint() {
650 // AccountDelta
651 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
652 let storage_delta = AccountStorageDelta::new();
653 let vault_delta = AccountVaultDelta::default();
654 assert_eq!(storage_delta.to_bytes().len(), storage_delta.get_size_hint());
655 assert_eq!(vault_delta.to_bytes().len(), vault_delta.get_size_hint());
656
657 let account_delta =
658 AccountDelta::new(account_id, storage_delta, vault_delta, ZERO).unwrap();
659 assert_eq!(account_delta.to_bytes().len(), account_delta.get_size_hint());
660
661 let storage_delta = AccountStorageDelta::from_iters(
662 [1],
663 [(2, Word::from([1, 1, 1, 1u32])), (3, Word::from([1, 1, 0, 1u32]))],
664 [(
665 4,
666 StorageMapDelta::from_iters(
667 [Word::from([1, 1, 1, 0u32]), Word::from([0, 1, 1, 1u32])],
668 [(Word::from([1, 1, 1, 1u32]), Word::from([1, 1, 1, 1u32]))],
669 ),
670 )],
671 );
672
673 let non_fungible: Asset = NonFungibleAsset::new(
674 &NonFungibleAssetDetails::new(
675 AccountIdBuilder::new()
676 .account_type(AccountType::NonFungibleFaucet)
677 .storage_mode(AccountStorageMode::Public)
678 .build_with_rng(&mut rand::rng())
679 .prefix(),
680 vec![6],
681 )
682 .unwrap(),
683 )
684 .unwrap()
685 .into();
686 let fungible_2: Asset = FungibleAsset::new(
687 AccountIdBuilder::new()
688 .account_type(AccountType::FungibleFaucet)
689 .storage_mode(AccountStorageMode::Public)
690 .build_with_rng(&mut rand::rng()),
691 10,
692 )
693 .unwrap()
694 .into();
695 let vault_delta = AccountVaultDelta::from_iters([non_fungible], [fungible_2]);
696
697 assert_eq!(storage_delta.to_bytes().len(), storage_delta.get_size_hint());
698 assert_eq!(vault_delta.to_bytes().len(), vault_delta.get_size_hint());
699
700 let account_delta = AccountDelta::new(account_id, storage_delta, vault_delta, ONE).unwrap();
701 assert_eq!(account_delta.to_bytes().len(), account_delta.get_size_hint());
702
703 // Account
704
705 let account_id =
706 AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap();
707
708 let asset_vault = AssetVault::mock();
709 assert_eq!(asset_vault.to_bytes().len(), asset_vault.get_size_hint());
710
711 let account_storage = AccountStorage::mock();
712 assert_eq!(account_storage.to_bytes().len(), account_storage.get_size_hint());
713
714 let account_code = AccountCode::mock();
715 assert_eq!(account_code.to_bytes().len(), account_code.get_size_hint());
716
717 let account = Account::new_existing(
718 account_id,
719 asset_vault,
720 account_storage,
721 account_code,
722 Felt::ONE,
723 );
724 assert_eq!(account.to_bytes().len(), account.get_size_hint());
725
726 // AccountUpdateDetails
727
728 let update_details_private = AccountUpdateDetails::Private;
729 assert_eq!(update_details_private.to_bytes().len(), update_details_private.get_size_hint());
730
731 let update_details_delta = AccountUpdateDetails::Delta(account_delta);
732 assert_eq!(update_details_delta.to_bytes().len(), update_details_delta.get_size_hint());
733 }
734}