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]`.
217 /// - For partial state deltas, the map header must only be included if
218 /// `num_changed_entries` is not zero.
219 /// - For full state deltas, the map header must always be included.
220 ///
221 /// ## Rationale
222 ///
223 /// The rationale for this layout is that hashing in the VM should be as efficient as possible
224 /// and minimize the number of branches to be as efficient as possible. Every high-level section
225 /// in this bullet point list should add an even number of words since the hasher operates
226 /// on double words. In the VM, each permutation is done immediately, so adding an uneven
227 /// number of words in a given step will result in more difficulty in the MASM implementation.
228 ///
229 /// ### New Accounts
230 ///
231 /// The delta for new accounts (a full state delta) must commit to all the storage slots of the
232 /// account, even if the storage slots have a default value (e.g. the empty word for value slots
233 /// or an empty storage map). This ensures the full state delta commits to the exact storage
234 /// slots that are contained in the account.
235 ///
236 /// ## Security
237 ///
238 /// The general concern with the commitment is that two distinct deltas must never hash to the
239 /// same commitment. E.g. a commitment of a delta that changes a key-value pair in a storage
240 /// map slot should be different from a delta that adds a non-fungible asset to the vault.
241 /// If not, a delta can be crafted in the VM that sets a map key but a malicious actor
242 /// crafts a delta outside the VM that adds a non-fungible asset. To prevent that, a couple
243 /// of measures are taken.
244 ///
245 /// - Because multiple unrelated contexts (e.g. vaults and storage slots) are hashed in the same
246 /// hasher, domain separators are used to disambiguate. For each changed asset and each
247 /// changed slot in the delta, a domain separator is hashed into the delta. The domain
248 /// separator is always at the same index in each layout so it cannot be maliciously crafted
249 /// (see below for an example).
250 /// - Storage value slots:
251 /// - since only changed value slots are included in the delta, there is no ambiguity between
252 /// a value slot being set to EMPTY_WORD and its value being unchanged.
253 /// - Storage map slots:
254 /// - Map slots append a header which summarizes the changes in the slot, in particular the
255 /// slot index and number of changed entries. Since only changed slots are included, the
256 /// number of changed entries is never zero.
257 /// - Two distinct storage map slots use the same domain but are disambiguated due to
258 /// inclusion of the slot index.
259 ///
260 /// ### Domain Separators
261 ///
262 /// As an example for ambiguity, consider these two deltas:
263 ///
264 /// ```text
265 /// [
266 /// ID_AND_NONCE, EMPTY_WORD,
267 /// [/* no fungible asset delta */],
268 /// [[domain = 1, was_added = 1, 0, 0], NON_FUNGIBLE_ASSET],
269 /// [/* no storage delta */]
270 /// ]
271 /// ```
272 ///
273 /// ```text
274 /// [
275 /// ID_AND_NONCE, EMPTY_WORD,
276 /// [/* no fungible asset delta */],
277 /// [/* no non-fungible asset delta */],
278 /// [[domain = 2, slot_idx = 1, 0, 0], NEW_VALUE]
279 /// ]
280 /// ```
281 ///
282 /// `NEW_VALUE` is user-controllable so it can be crafted to match `NON_FUNGIBLE_ASSET`. The
283 /// domain separator is then the only value that differentiates these two deltas. This shows the
284 /// importance of placing the domain separators in the same index within each word's layout
285 /// which makes it easy to see that this value cannot be crafted to be the same.
286 ///
287 /// ### Number of Changed Entries
288 ///
289 /// As an example for ambiguity, consider these two deltas:
290 ///
291 /// ```text
292 /// [
293 /// ID_AND_NONCE, EMPTY_WORD,
294 /// [/* no fungible asset delta */],
295 /// [/* no non-fungible asset delta */],
296 /// [domain = 3, slot_idx = 0, num_changed_entries = 0, 0, 0, 0, 0, 0]
297 /// [domain = 3, slot_idx = 1, num_changed_entries = 0, 0, 0, 0, 0, 0]
298 /// ]
299 /// ```
300 ///
301 /// ```text
302 /// [
303 /// ID_AND_NONCE, EMPTY_WORD,
304 /// [/* no fungible asset delta */],
305 /// [/* no non-fungible asset delta */],
306 /// [KEY0, VALUE0],
307 /// [domain = 3, slot_idx = 1, num_changed_entries = 1, 0, 0, 0, 0, 0]
308 /// ]
309 /// ```
310 ///
311 /// The keys and values of map slots are user-controllable so `KEY0` and `VALUE0` could be
312 /// crafted to match the first map header in the first delta. So, _without_ having
313 /// `num_changed_entries` included in the commitment, these deltas would be ambiguous. A delta
314 /// with two empty maps could have the same commitment as a delta with one map entry where one
315 /// key-value pair has changed.
316 ///
317 /// #### New Accounts
318 ///
319 /// The number of changed entries of a storage map can be validly zero when an empty storage map
320 /// is added to a new account. In such cases, the number of changed key-value pairs is 0, but
321 /// the map must still be committed to, in order to differentiate between a slot being an empty
322 /// map or not being present at all.
323 pub fn to_commitment(&self) -> Word {
324 <Self as SequentialCommit>::to_commitment(self)
325 }
326}
327
328impl TryFrom<&AccountDelta> for Account {
329 type Error = AccountError;
330
331 /// Converts an [`AccountDelta`] into an [`Account`].
332 ///
333 /// Conceptually, this applies the delta onto an empty account.
334 ///
335 /// # Errors
336 ///
337 /// Returns an error if:
338 /// - If the delta is not a full state delta. See [`AccountDelta`] for details.
339 /// - If any vault delta operation removes an asset.
340 /// - If any vault delta operation adds an asset that would overflow the maximum representable
341 /// amount.
342 /// - If any storage delta update violates account storage constraints.
343 fn try_from(delta: &AccountDelta) -> Result<Self, Self::Error> {
344 if !delta.is_full_state() {
345 return Err(AccountError::PartialStateDeltaToAccount);
346 }
347
348 let Some(code) = delta.code().cloned() else {
349 return Err(AccountError::PartialStateDeltaToAccount);
350 };
351
352 let mut vault = AssetVault::default();
353 vault.apply_delta(delta.vault()).map_err(AccountError::AssetVaultUpdateError)?;
354
355 // Once we support addition and removal of storage slots, we may be able to change
356 // this to create an empty account and use `Account::apply_delta` instead.
357 // For now, we need to create the initial storage of the account with the same slot types.
358 let mut empty_storage_slots = Vec::new();
359 for slot_idx in 0..u8::MAX {
360 let slot = match delta.storage().slot_type(slot_idx) {
361 Some(StorageSlotType::Value) => StorageSlot::empty_value(),
362 Some(StorageSlotType::Map) => StorageSlot::empty_map(),
363 None => break,
364 };
365 empty_storage_slots.push(slot);
366 }
367 let mut storage = AccountStorage::new(empty_storage_slots)
368 .expect("storage delta should contain a valid number of slots");
369 storage.apply_delta(delta.storage())?;
370
371 // The nonce of the account is the initial nonce of 0 plus the nonce_delta, so the
372 // nonce_delta itself.
373 let nonce = delta.nonce_delta();
374
375 Account::new(delta.id(), vault, storage, code, nonce, None)
376 }
377}
378
379impl SequentialCommit for AccountDelta {
380 type Commitment = Word;
381
382 /// Reduces the delta to a sequence of field elements.
383 ///
384 /// See [AccountDelta::to_commitment()] for more details.
385 fn to_elements(&self) -> Vec<Felt> {
386 // The commitment to an empty delta is defined as the empty word.
387 if self.is_empty() {
388 return Vec::new();
389 }
390
391 // Minor optimization: At least 24 elements are always added.
392 let mut elements = Vec::with_capacity(24);
393
394 // ID and Nonce
395 elements.extend_from_slice(&[
396 self.nonce_delta,
397 ZERO,
398 self.account_id.suffix(),
399 self.account_id.prefix().as_felt(),
400 ]);
401 elements.extend_from_slice(Word::empty().as_elements());
402
403 // Vault Delta
404 self.vault.append_delta_elements(&mut elements);
405
406 // Storage Delta
407 self.storage.append_delta_elements(&mut elements);
408
409 debug_assert!(
410 elements.len() % (2 * crate::WORD_SIZE) == 0,
411 "expected elements to contain an even number of words, but it contained {} elements",
412 elements.len()
413 );
414
415 elements
416 }
417}
418
419// ACCOUNT UPDATE DETAILS
420// ================================================================================================
421
422/// [`AccountUpdateDetails`] describes the details of one or more transactions executed against an
423/// account.
424///
425/// In particular, private account changes aren't tracked at all; they are represented as
426/// [`AccountUpdateDetails::Private`].
427///
428/// Non-private accounts are tracked as an [`AccountDelta`]. If the account is new, the delta can be
429/// converted into an [`Account`]. If not, the delta can be applied to the existing account using
430/// [`Account::apply_delta`].
431///
432/// Note that these details can represent the changes from one or more transactions in which case
433/// the deltas of each transaction are merged together using [`AccountDelta::merge`].
434#[derive(Clone, Debug, PartialEq, Eq)]
435pub enum AccountUpdateDetails {
436 /// The state update details of a private account is not publicly accessible.
437 Private,
438
439 /// The state update details of non-private accounts.
440 Delta(AccountDelta),
441}
442
443impl AccountUpdateDetails {
444 /// Returns `true` if the account update details are for private account.
445 pub fn is_private(&self) -> bool {
446 matches!(self, Self::Private)
447 }
448
449 /// Merges the `other` update into this one.
450 ///
451 /// This account update is assumed to come before the other.
452 pub fn merge(self, other: AccountUpdateDetails) -> Result<Self, AccountDeltaError> {
453 let merged_update = match (self, other) {
454 (AccountUpdateDetails::Private, AccountUpdateDetails::Private) => {
455 AccountUpdateDetails::Private
456 },
457 (AccountUpdateDetails::Delta(mut delta), AccountUpdateDetails::Delta(new_delta)) => {
458 delta.merge(new_delta)?;
459 AccountUpdateDetails::Delta(delta)
460 },
461 (left, right) => {
462 return Err(AccountDeltaError::IncompatibleAccountUpdates {
463 left_update_type: left.as_tag_str(),
464 right_update_type: right.as_tag_str(),
465 });
466 },
467 };
468
469 Ok(merged_update)
470 }
471
472 /// Returns the tag of the [`AccountUpdateDetails`] as a string for inclusion in error messages.
473 pub(crate) const fn as_tag_str(&self) -> &'static str {
474 match self {
475 AccountUpdateDetails::Private => "private",
476 AccountUpdateDetails::Delta(_) => "delta",
477 }
478 }
479}
480
481// SERIALIZATION
482// ================================================================================================
483
484impl Serializable for AccountDelta {
485 fn write_into<W: ByteWriter>(&self, target: &mut W) {
486 self.account_id.write_into(target);
487 self.storage.write_into(target);
488 self.vault.write_into(target);
489 self.code.write_into(target);
490 self.nonce_delta.write_into(target);
491 }
492
493 fn get_size_hint(&self) -> usize {
494 self.account_id.get_size_hint()
495 + self.storage.get_size_hint()
496 + self.vault.get_size_hint()
497 + self.code.get_size_hint()
498 + self.nonce_delta.get_size_hint()
499 }
500}
501
502impl Deserializable for AccountDelta {
503 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
504 let account_id = AccountId::read_from(source)?;
505 let storage = AccountStorageDelta::read_from(source)?;
506 let vault = AccountVaultDelta::read_from(source)?;
507 let code = <Option<AccountCode>>::read_from(source)?;
508 let nonce_delta = Felt::read_from(source)?;
509
510 validate_nonce(nonce_delta, &storage, &vault)
511 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
512
513 Ok(Self {
514 account_id,
515 storage,
516 vault,
517 code,
518 nonce_delta,
519 })
520 }
521}
522
523impl Serializable for AccountUpdateDetails {
524 fn write_into<W: ByteWriter>(&self, target: &mut W) {
525 match self {
526 AccountUpdateDetails::Private => {
527 0_u8.write_into(target);
528 },
529 AccountUpdateDetails::Delta(delta) => {
530 1_u8.write_into(target);
531 delta.write_into(target);
532 },
533 }
534 }
535
536 fn get_size_hint(&self) -> usize {
537 // Size of the serialized enum tag.
538 let u8_size = 0u8.get_size_hint();
539
540 match self {
541 AccountUpdateDetails::Private => u8_size,
542 AccountUpdateDetails::Delta(account_delta) => u8_size + account_delta.get_size_hint(),
543 }
544 }
545}
546
547impl Deserializable for AccountUpdateDetails {
548 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
549 match u8::read_from(source)? {
550 0 => Ok(Self::Private),
551 1 => Ok(Self::Delta(AccountDelta::read_from(source)?)),
552 variant => Err(DeserializationError::InvalidValue(format!(
553 "Unknown variant {variant} for AccountDetails"
554 ))),
555 }
556 }
557}
558
559// HELPER FUNCTIONS
560// ================================================================================================
561
562/// Checks if the nonce was updated correctly given the provided storage and vault deltas.
563///
564/// # Errors
565///
566/// Returns an error if:
567/// - storage or vault were updated, but the nonce_delta was set to 0.
568fn validate_nonce(
569 nonce_delta: Felt,
570 storage: &AccountStorageDelta,
571 vault: &AccountVaultDelta,
572) -> Result<(), AccountDeltaError> {
573 if (!storage.is_empty() || !vault.is_empty()) && nonce_delta == ZERO {
574 return Err(AccountDeltaError::NonEmptyStorageOrVaultDeltaWithZeroNonceDelta);
575 }
576
577 Ok(())
578}
579
580// TESTS
581// ================================================================================================
582
583#[cfg(test)]
584mod tests {
585
586 use assert_matches::assert_matches;
587 use miden_core::utils::Serializable;
588 use miden_core::{Felt, FieldElement};
589
590 use super::{AccountDelta, AccountStorageDelta, AccountVaultDelta};
591 use crate::account::delta::AccountUpdateDetails;
592 use crate::account::{
593 Account,
594 AccountCode,
595 AccountId,
596 AccountStorage,
597 AccountStorageMode,
598 AccountType,
599 StorageMapDelta,
600 };
601 use crate::asset::{
602 Asset,
603 AssetVault,
604 FungibleAsset,
605 NonFungibleAsset,
606 NonFungibleAssetDetails,
607 };
608 use crate::testing::account_id::{
609 ACCOUNT_ID_PRIVATE_SENDER,
610 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
611 AccountIdBuilder,
612 };
613 use crate::{AccountDeltaError, ONE, Word, ZERO};
614
615 #[test]
616 fn account_delta_nonce_validation() {
617 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
618 // empty delta
619 let storage_delta = AccountStorageDelta::new();
620 let vault_delta = AccountVaultDelta::default();
621
622 AccountDelta::new(account_id, storage_delta.clone(), vault_delta.clone(), ZERO).unwrap();
623 AccountDelta::new(account_id, storage_delta.clone(), vault_delta.clone(), ONE).unwrap();
624
625 // non-empty delta
626 let storage_delta = AccountStorageDelta::from_iters([1], [], []);
627
628 assert_matches!(
629 AccountDelta::new(account_id, storage_delta.clone(), vault_delta.clone(), ZERO)
630 .unwrap_err(),
631 AccountDeltaError::NonEmptyStorageOrVaultDeltaWithZeroNonceDelta
632 );
633 AccountDelta::new(account_id, storage_delta.clone(), vault_delta.clone(), ONE).unwrap();
634 }
635
636 #[test]
637 fn account_delta_nonce_overflow() {
638 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
639 let storage_delta = AccountStorageDelta::new();
640 let vault_delta = AccountVaultDelta::default();
641
642 let nonce_delta0 = ONE;
643 let nonce_delta1 = Felt::try_from(0xffff_ffff_0000_0000u64).unwrap();
644
645 let mut delta0 =
646 AccountDelta::new(account_id, storage_delta.clone(), vault_delta.clone(), nonce_delta0)
647 .unwrap();
648 let delta1 =
649 AccountDelta::new(account_id, storage_delta, vault_delta, nonce_delta1).unwrap();
650
651 assert_matches!(delta0.merge(delta1).unwrap_err(), AccountDeltaError::NonceIncrementOverflow {
652 current, increment, new
653 } => {
654 assert_eq!(current, nonce_delta0);
655 assert_eq!(increment, nonce_delta1);
656 assert_eq!(new, nonce_delta0 + nonce_delta1);
657 });
658 }
659
660 #[test]
661 fn account_update_details_size_hint() {
662 // AccountDelta
663 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
664 let storage_delta = AccountStorageDelta::new();
665 let vault_delta = AccountVaultDelta::default();
666 assert_eq!(storage_delta.to_bytes().len(), storage_delta.get_size_hint());
667 assert_eq!(vault_delta.to_bytes().len(), vault_delta.get_size_hint());
668
669 let account_delta =
670 AccountDelta::new(account_id, storage_delta, vault_delta, ZERO).unwrap();
671 assert_eq!(account_delta.to_bytes().len(), account_delta.get_size_hint());
672
673 let storage_delta = AccountStorageDelta::from_iters(
674 [1],
675 [(2, Word::from([1, 1, 1, 1u32])), (3, Word::from([1, 1, 0, 1u32]))],
676 [(
677 4,
678 StorageMapDelta::from_iters(
679 [Word::from([1, 1, 1, 0u32]), Word::from([0, 1, 1, 1u32])],
680 [(Word::from([1, 1, 1, 1u32]), Word::from([1, 1, 1, 1u32]))],
681 ),
682 )],
683 );
684
685 let non_fungible: Asset = NonFungibleAsset::new(
686 &NonFungibleAssetDetails::new(
687 AccountIdBuilder::new()
688 .account_type(AccountType::NonFungibleFaucet)
689 .storage_mode(AccountStorageMode::Public)
690 .build_with_rng(&mut rand::rng())
691 .prefix(),
692 vec![6],
693 )
694 .unwrap(),
695 )
696 .unwrap()
697 .into();
698 let fungible_2: Asset = FungibleAsset::new(
699 AccountIdBuilder::new()
700 .account_type(AccountType::FungibleFaucet)
701 .storage_mode(AccountStorageMode::Public)
702 .build_with_rng(&mut rand::rng()),
703 10,
704 )
705 .unwrap()
706 .into();
707 let vault_delta = AccountVaultDelta::from_iters([non_fungible], [fungible_2]);
708
709 assert_eq!(storage_delta.to_bytes().len(), storage_delta.get_size_hint());
710 assert_eq!(vault_delta.to_bytes().len(), vault_delta.get_size_hint());
711
712 let account_delta = AccountDelta::new(account_id, storage_delta, vault_delta, ONE).unwrap();
713 assert_eq!(account_delta.to_bytes().len(), account_delta.get_size_hint());
714
715 // Account
716
717 let account_id =
718 AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap();
719
720 let asset_vault = AssetVault::mock();
721 assert_eq!(asset_vault.to_bytes().len(), asset_vault.get_size_hint());
722
723 let account_storage = AccountStorage::mock();
724 assert_eq!(account_storage.to_bytes().len(), account_storage.get_size_hint());
725
726 let account_code = AccountCode::mock();
727 assert_eq!(account_code.to_bytes().len(), account_code.get_size_hint());
728
729 let account = Account::new_existing(
730 account_id,
731 asset_vault,
732 account_storage,
733 account_code,
734 Felt::ONE,
735 );
736 assert_eq!(account.to_bytes().len(), account.get_size_hint());
737
738 // AccountUpdateDetails
739
740 let update_details_private = AccountUpdateDetails::Private;
741 assert_eq!(update_details_private.to_bytes().len(), update_details_private.get_size_hint());
742
743 let update_details_delta = AccountUpdateDetails::Delta(account_delta);
744 assert_eq!(update_details_delta.to_bytes().len(), update_details_delta.get_size_hint());
745 }
746}