miden_objects/account/delta/
mod.rs

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