Skip to main content

miden_protocol/account/
mod.rs

1use alloc::string::ToString;
2use alloc::vec::Vec;
3
4use crate::asset::{Asset, AssetVault};
5use crate::crypto::SequentialCommit;
6use crate::errors::AccountError;
7use crate::utils::serde::{
8    ByteReader,
9    ByteWriter,
10    Deserializable,
11    DeserializationError,
12    Serializable,
13};
14use crate::{Felt, Hasher, Word, ZERO};
15
16mod account_id;
17pub use account_id::{
18    AccountId,
19    AccountIdPrefix,
20    AccountIdPrefixV0,
21    AccountIdV0,
22    AccountIdVersion,
23    AccountStorageMode,
24    AccountType,
25};
26
27pub mod auth;
28
29mod builder;
30pub use builder::AccountBuilder;
31
32pub mod code;
33pub use code::AccountCode;
34pub use code::procedure::AccountProcedureRoot;
35
36pub mod component;
37pub use component::{AccountComponent, AccountComponentCode, AccountComponentMetadata};
38
39pub mod delta;
40pub use delta::{
41    AccountDelta,
42    AccountStorageDelta,
43    AccountVaultDelta,
44    FungibleAssetDelta,
45    NonFungibleAssetDelta,
46    NonFungibleDeltaAction,
47    StorageMapDelta,
48    StorageSlotDelta,
49};
50
51pub mod storage;
52pub use storage::{
53    AccountStorage,
54    AccountStorageHeader,
55    PartialStorage,
56    PartialStorageMap,
57    StorageMap,
58    StorageMapKey,
59    StorageMapKeyHash,
60    StorageMapWitness,
61    StorageSlot,
62    StorageSlotContent,
63    StorageSlotHeader,
64    StorageSlotId,
65    StorageSlotName,
66    StorageSlotType,
67};
68
69mod header;
70pub use header::AccountHeader;
71
72mod file;
73pub use file::AccountFile;
74
75mod partial;
76pub use partial::PartialAccount;
77
78// ACCOUNT
79// ================================================================================================
80
81/// An account which can store assets and define rules for manipulating them.
82///
83/// An account consists of the following components:
84/// - Account ID, which uniquely identifies the account and also defines basic properties of the
85///   account.
86/// - Account vault, which stores assets owned by the account.
87/// - Account storage, which is a key-value map (both keys and values are words) used to store
88///   arbitrary user-defined data.
89/// - Account code, which is a set of Miden VM programs defining the public interface of the
90///   account.
91/// - Account nonce, a value which is incremented whenever account state is updated.
92///
93/// Out of the above components account ID is always immutable (once defined it can never be
94/// changed). Other components may be mutated throughout the lifetime of the account. However,
95/// account state can be changed only by invoking one of account interface methods.
96///
97/// The recommended way to build an account is through an [`AccountBuilder`], which can be
98/// instantiated through [`Account::builder`]. See the type's documentation for details.
99#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct Account {
101    id: AccountId,
102    vault: AssetVault,
103    storage: AccountStorage,
104    code: AccountCode,
105    nonce: Felt,
106    seed: Option<Word>,
107}
108
109impl Account {
110    // CONSTRUCTORS
111    // --------------------------------------------------------------------------------------------
112
113    /// Returns an [`Account`] instantiated with the provided components.
114    ///
115    /// # Errors
116    ///
117    /// Returns an error if:
118    /// - an account seed is provided but the account's nonce indicates the account already exists.
119    /// - an account seed is not provided but the account's nonce indicates the account is new.
120    /// - an account seed is provided but the account ID derived from it is invalid or does not
121    ///   match the provided account's ID.
122    pub fn new(
123        id: AccountId,
124        vault: AssetVault,
125        storage: AccountStorage,
126        code: AccountCode,
127        nonce: Felt,
128        seed: Option<Word>,
129    ) -> Result<Self, AccountError> {
130        validate_account_seed(id, code.commitment(), storage.to_commitment(), seed, nonce)?;
131
132        Ok(Self::new_unchecked(id, vault, storage, code, nonce, seed))
133    }
134
135    /// Returns an [`Account`] instantiated with the provided components.
136    ///
137    /// # Warning
138    ///
139    /// This does not check that the provided seed is valid with respect to the provided components.
140    /// Prefer using [`Account::new`] whenever possible.
141    pub fn new_unchecked(
142        id: AccountId,
143        vault: AssetVault,
144        storage: AccountStorage,
145        code: AccountCode,
146        nonce: Felt,
147        seed: Option<Word>,
148    ) -> Self {
149        Self { id, vault, storage, code, nonce, seed }
150    }
151
152    /// Creates an account's [`AccountCode`] and [`AccountStorage`] from the provided components.
153    ///
154    /// This merges all libraries of the components into a single
155    /// [`MastForest`](miden_processor::MastForest) to produce the [`AccountCode`].
156    ///
157    /// The storage slots of all components are merged into a single [`AccountStorage`], where the
158    /// slots are sorted by their [`StorageSlotName`].
159    ///
160    /// The resulting commitments from code and storage can then be used to construct an
161    /// [`AccountId`]. Finally, a new account can then be instantiated from those parts using
162    /// [`Account::new`].
163    ///
164    /// # Errors
165    ///
166    /// Returns an error if:
167    /// - Any of the components does not support `account_type`.
168    /// - The number of procedures in all merged libraries is 0 or exceeds
169    ///   [`AccountCode::MAX_NUM_PROCEDURES`].
170    /// - Two or more libraries export a procedure with the same MAST root.
171    /// - The first component doesn't contain exactly one authentication procedure.
172    /// - Other components contain authentication procedures.
173    /// - The number of [`StorageSlot`]s of all components exceeds 255.
174    /// - [`MastForest::merge`](miden_processor::MastForest::merge) fails on all libraries.
175    pub(super) fn initialize_from_components(
176        account_type: AccountType,
177        components: Vec<AccountComponent>,
178    ) -> Result<(AccountCode, AccountStorage), AccountError> {
179        validate_components_support_account_type(&components, account_type)?;
180
181        let code = AccountCode::from_components_unchecked(&components)?;
182        let storage = AccountStorage::from_components(components)?;
183
184        Ok((code, storage))
185    }
186
187    /// Creates a new [`AccountBuilder`] for an account and sets the initial seed from which the
188    /// grinding process for that account's [`AccountId`] will start.
189    ///
190    /// This initial seed should come from a cryptographic random number generator.
191    pub fn builder(init_seed: [u8; 32]) -> AccountBuilder {
192        AccountBuilder::new(init_seed)
193    }
194
195    // PUBLIC ACCESSORS
196    // --------------------------------------------------------------------------------------------
197
198    /// Returns the commitment of this account.
199    ///
200    /// See [`AccountHeader::to_commitment`] for details on how it is computed.
201    pub fn to_commitment(&self) -> Word {
202        AccountHeader::from(self).to_commitment()
203    }
204
205    /// Returns the commitment of this account as used for the initial account state commitment in
206    /// transaction proofs.
207    ///
208    /// For existing accounts, this is exactly the same as [Account::to_commitment], however, for
209    /// new accounts this value is set to [crate::EMPTY_WORD]. This is because when a
210    /// transaction is executed against a new account, public input for the initial account
211    /// state is set to [crate::EMPTY_WORD] to distinguish new accounts from existing accounts.
212    /// The actual commitment of the initial account state (and the initial state itself), are
213    /// provided to the VM via the advice provider.
214    pub fn initial_commitment(&self) -> Word {
215        if self.is_new() {
216            Word::empty()
217        } else {
218            self.to_commitment()
219        }
220    }
221
222    /// Returns unique identifier of this account.
223    pub fn id(&self) -> AccountId {
224        self.id
225    }
226
227    /// Returns the account type
228    pub fn account_type(&self) -> AccountType {
229        self.id.account_type()
230    }
231
232    /// Returns a reference to the vault of this account.
233    pub fn vault(&self) -> &AssetVault {
234        &self.vault
235    }
236
237    /// Returns a reference to the storage of this account.
238    pub fn storage(&self) -> &AccountStorage {
239        &self.storage
240    }
241
242    /// Returns a reference to the code of this account.
243    pub fn code(&self) -> &AccountCode {
244        &self.code
245    }
246
247    /// Returns nonce for this account.
248    pub fn nonce(&self) -> Felt {
249        self.nonce
250    }
251
252    /// Returns the seed of the account's ID if the account is new.
253    ///
254    /// That is, if [`Account::is_new`] returns `true`, the seed will be `Some`.
255    pub fn seed(&self) -> Option<Word> {
256        self.seed
257    }
258
259    /// Returns true if this account can issue assets.
260    pub fn is_faucet(&self) -> bool {
261        self.id.is_faucet()
262    }
263
264    /// Returns true if this is a regular account.
265    pub fn is_regular_account(&self) -> bool {
266        self.id.is_regular_account()
267    }
268
269    /// Returns `true` if the full state of the account is public on chain, i.e. if the modes are
270    /// [`AccountStorageMode::Public`] or [`AccountStorageMode::Network`], `false` otherwise.
271    pub fn has_public_state(&self) -> bool {
272        self.id().has_public_state()
273    }
274
275    /// Returns `true` if the storage mode is [`AccountStorageMode::Public`], `false` otherwise.
276    pub fn is_public(&self) -> bool {
277        self.id().is_public()
278    }
279
280    /// Returns `true` if the storage mode is [`AccountStorageMode::Private`], `false` otherwise.
281    pub fn is_private(&self) -> bool {
282        self.id().is_private()
283    }
284
285    /// Returns `true` if the storage mode is [`AccountStorageMode::Network`], `false` otherwise.
286    pub fn is_network(&self) -> bool {
287        self.id().is_network()
288    }
289
290    /// Returns `true` if the account is new, `false` otherwise.
291    ///
292    /// An account is considered new if the account's nonce is zero and it hasn't been registered on
293    /// chain yet.
294    pub fn is_new(&self) -> bool {
295        self.nonce == ZERO
296    }
297
298    /// Decomposes the account into the underlying account components.
299    pub fn into_parts(
300        self,
301    ) -> (AccountId, AssetVault, AccountStorage, AccountCode, Felt, Option<Word>) {
302        (self.id, self.vault, self.storage, self.code, self.nonce, self.seed)
303    }
304
305    // DATA MUTATORS
306    // --------------------------------------------------------------------------------------------
307
308    /// Applies the provided delta to this account. This updates account vault, storage, and nonce
309    /// to the values specified by the delta.
310    ///
311    /// # Errors
312    ///
313    /// Returns an error if:
314    /// - [`AccountDelta::is_full_state`] returns `true`, i.e. represents the state of an entire
315    ///   account. Only partial state deltas can be applied to an account.
316    /// - Applying vault sub-delta to the vault of this account fails.
317    /// - Applying storage sub-delta to the storage of this account fails.
318    /// - The nonce specified in the provided delta smaller than or equal to the current account
319    ///   nonce.
320    pub fn apply_delta(&mut self, delta: &AccountDelta) -> Result<(), AccountError> {
321        if delta.is_full_state() {
322            return Err(AccountError::ApplyFullStateDeltaToAccount);
323        }
324
325        // update vault; we don't check vault delta validity here because `AccountDelta` can contain
326        // only valid vault deltas
327        self.vault
328            .apply_delta(delta.vault())
329            .map_err(AccountError::AssetVaultUpdateError)?;
330
331        // update storage
332        self.storage.apply_delta(delta.storage())?;
333
334        // update nonce
335        self.increment_nonce(delta.nonce_delta())?;
336
337        Ok(())
338    }
339
340    /// Increments the nonce of this account by the provided increment.
341    ///
342    /// # Errors
343    ///
344    /// Returns an error if:
345    /// - Incrementing the nonce overflows a [`Felt`].
346    pub fn increment_nonce(&mut self, nonce_delta: Felt) -> Result<(), AccountError> {
347        let new_nonce = self.nonce + nonce_delta;
348
349        if new_nonce.as_int() < self.nonce.as_int() {
350            return Err(AccountError::NonceOverflow {
351                current: self.nonce,
352                increment: nonce_delta,
353                new: new_nonce,
354            });
355        }
356
357        self.nonce = new_nonce;
358
359        // Maintain internal consistency of the account, i.e. the seed should not be present for
360        // existing accounts, where existing accounts are defined as having a nonce > 0.
361        // If we've incremented the nonce, then we should remove the seed (if it was present at
362        // all).
363        if !self.is_new() {
364            self.seed = None;
365        }
366
367        Ok(())
368    }
369
370    // TEST HELPERS
371    // --------------------------------------------------------------------------------------------
372
373    #[cfg(any(feature = "testing", test))]
374    /// Returns a mutable reference to the vault of this account.
375    pub fn vault_mut(&mut self) -> &mut AssetVault {
376        &mut self.vault
377    }
378
379    #[cfg(any(feature = "testing", test))]
380    /// Returns a mutable reference to the storage of this account.
381    pub fn storage_mut(&mut self) -> &mut AccountStorage {
382        &mut self.storage
383    }
384}
385
386impl TryFrom<Account> for AccountDelta {
387    type Error = AccountError;
388
389    /// Converts an [`Account`] into an [`AccountDelta`].
390    ///
391    /// # Errors
392    ///
393    /// Returns an error if:
394    /// - the account has a seed. Accounts with seeds have a nonce of 0. Representing such accounts
395    ///   as deltas is not possible because deltas with a non-empty state change need a nonce_delta
396    ///   greater than 0.
397    fn try_from(account: Account) -> Result<Self, Self::Error> {
398        let Account { id, vault, storage, code, nonce, seed } = account;
399
400        if seed.is_some() {
401            return Err(AccountError::DeltaFromAccountWithSeed);
402        }
403
404        let slot_deltas = storage
405            .into_slots()
406            .into_iter()
407            .map(StorageSlot::into_parts)
408            .map(|(slot_name, slot_content)| (slot_name, StorageSlotDelta::from(slot_content)))
409            .collect();
410        let storage_delta = AccountStorageDelta::from_raw(slot_deltas);
411
412        let mut fungible_delta = FungibleAssetDelta::default();
413        let mut non_fungible_delta = NonFungibleAssetDelta::default();
414        for asset in vault.assets() {
415            // SAFETY: All assets in the account vault should be representable in the delta.
416            match asset {
417                Asset::Fungible(fungible_asset) => {
418                    fungible_delta
419                        .add(fungible_asset)
420                        .expect("delta should allow representing valid fungible assets");
421                },
422                Asset::NonFungible(non_fungible_asset) => {
423                    non_fungible_delta
424                        .add(non_fungible_asset)
425                        .expect("delta should allow representing valid non-fungible assets");
426                },
427            }
428        }
429        let vault_delta = AccountVaultDelta::new(fungible_delta, non_fungible_delta);
430
431        // The nonce of the account is the nonce delta since adding the nonce_delta to 0 would
432        // result in the nonce.
433        let nonce_delta = nonce;
434
435        // SAFETY: As checked earlier, the nonce delta should be greater than 0 allowing for
436        // non-empty state changes.
437        let delta = AccountDelta::new(id, storage_delta, vault_delta, nonce_delta)
438            .expect("nonce_delta should be greater than 0")
439            .with_code(Some(code));
440
441        Ok(delta)
442    }
443}
444
445impl SequentialCommit for Account {
446    type Commitment = Word;
447
448    fn to_elements(&self) -> Vec<Felt> {
449        AccountHeader::from(self).to_elements()
450    }
451
452    fn to_commitment(&self) -> Self::Commitment {
453        AccountHeader::from(self).to_commitment()
454    }
455}
456
457// SERIALIZATION
458// ================================================================================================
459
460impl Serializable for Account {
461    fn write_into<W: ByteWriter>(&self, target: &mut W) {
462        let Account { id, vault, storage, code, nonce, seed } = self;
463
464        id.write_into(target);
465        vault.write_into(target);
466        storage.write_into(target);
467        code.write_into(target);
468        nonce.write_into(target);
469        seed.write_into(target);
470    }
471
472    fn get_size_hint(&self) -> usize {
473        self.id.get_size_hint()
474            + self.vault.get_size_hint()
475            + self.storage.get_size_hint()
476            + self.code.get_size_hint()
477            + self.nonce.get_size_hint()
478            + self.seed.get_size_hint()
479    }
480}
481
482impl Deserializable for Account {
483    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
484        let id = AccountId::read_from(source)?;
485        let vault = AssetVault::read_from(source)?;
486        let storage = AccountStorage::read_from(source)?;
487        let code = AccountCode::read_from(source)?;
488        let nonce = Felt::read_from(source)?;
489        let seed = <Option<Word>>::read_from(source)?;
490
491        Self::new(id, vault, storage, code, nonce, seed)
492            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
493    }
494}
495
496// HELPER FUNCTIONS
497// ================================================================================================
498
499/// Validates that the provided seed is valid for the provided account components.
500pub(super) fn validate_account_seed(
501    id: AccountId,
502    code_commitment: Word,
503    storage_commitment: Word,
504    seed: Option<Word>,
505    nonce: Felt,
506) -> Result<(), AccountError> {
507    let account_is_new = nonce == ZERO;
508
509    match (account_is_new, seed) {
510        (true, Some(seed)) => {
511            let account_id =
512                AccountId::new(seed, id.version(), code_commitment, storage_commitment)
513                    .map_err(AccountError::SeedConvertsToInvalidAccountId)?;
514
515            if account_id != id {
516                return Err(AccountError::AccountIdSeedMismatch {
517                    expected: id,
518                    actual: account_id,
519                });
520            }
521
522            Ok(())
523        },
524        (true, None) => Err(AccountError::NewAccountMissingSeed),
525        (false, Some(_)) => Err(AccountError::ExistingAccountWithSeed),
526        (false, None) => Ok(()),
527    }
528}
529
530/// Validates that all `components` support the given `account_type`.
531fn validate_components_support_account_type(
532    components: &[AccountComponent],
533    account_type: AccountType,
534) -> Result<(), AccountError> {
535    for (component_index, component) in components.iter().enumerate() {
536        if !component.supports_type(account_type) {
537            return Err(AccountError::UnsupportedComponentForAccountType {
538                account_type,
539                component_index,
540            });
541        }
542    }
543
544    Ok(())
545}
546
547// TESTS
548// ================================================================================================
549
550#[cfg(test)]
551mod tests {
552    use alloc::vec::Vec;
553
554    use assert_matches::assert_matches;
555    use miden_assembly::Assembler;
556    use miden_core::FieldElement;
557    use miden_crypto::utils::{Deserializable, Serializable};
558    use miden_crypto::{Felt, Word};
559
560    use super::{
561        AccountCode,
562        AccountDelta,
563        AccountId,
564        AccountStorage,
565        AccountStorageDelta,
566        AccountVaultDelta,
567    };
568    use crate::account::AccountStorageMode::Network;
569    use crate::account::component::AccountComponentMetadata;
570    use crate::account::{
571        Account,
572        AccountBuilder,
573        AccountComponent,
574        AccountIdVersion,
575        AccountType,
576        PartialAccount,
577        StorageMap,
578        StorageMapDelta,
579        StorageMapKey,
580        StorageSlot,
581        StorageSlotContent,
582        StorageSlotName,
583    };
584    use crate::asset::{Asset, AssetVault, FungibleAsset, NonFungibleAsset};
585    use crate::errors::AccountError;
586    use crate::testing::account_id::{
587        ACCOUNT_ID_PRIVATE_SENDER,
588        ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
589    };
590    use crate::testing::add_component::AddComponent;
591    use crate::testing::noop_auth_component::NoopAuthComponent;
592
593    #[test]
594    fn test_serde_account() {
595        let init_nonce = Felt::new(1);
596        let asset_0 = FungibleAsset::mock(99);
597        let word = Word::from([1, 2, 3, 4u32]);
598        let storage_slot = StorageSlotContent::Value(word);
599        let account = build_account(vec![asset_0], init_nonce, vec![storage_slot]);
600
601        let serialized = account.to_bytes();
602        let deserialized = Account::read_from_bytes(&serialized).unwrap();
603        assert_eq!(deserialized, account);
604    }
605
606    #[test]
607    fn test_serde_account_delta() {
608        let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
609        let nonce_delta = Felt::new(2);
610        let asset_0 = FungibleAsset::mock(15);
611        let asset_1 = NonFungibleAsset::mock(&[5, 5, 5]);
612        let storage_delta = AccountStorageDelta::new()
613            .add_cleared_items([StorageSlotName::mock(0)])
614            .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))]);
615        let account_delta = build_account_delta(
616            account_id,
617            vec![asset_1],
618            vec![asset_0],
619            nonce_delta,
620            storage_delta,
621        );
622
623        let serialized = account_delta.to_bytes();
624        let deserialized = AccountDelta::read_from_bytes(&serialized).unwrap();
625        assert_eq!(deserialized, account_delta);
626    }
627
628    #[test]
629    fn valid_account_delta_is_correctly_applied() {
630        // build account
631        let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
632        let init_nonce = Felt::new(1);
633        let asset_0 = FungibleAsset::mock(100);
634        let asset_1 = NonFungibleAsset::mock(&[1, 2, 3]);
635
636        // build storage slots
637        let storage_slot_value_0 = StorageSlotContent::Value(Word::from([1, 2, 3, 4u32]));
638        let storage_slot_value_1 = StorageSlotContent::Value(Word::from([5, 6, 7, 8u32]));
639        let mut storage_map = StorageMap::with_entries([
640            (
641                StorageMapKey::from_array([101, 102, 103, 104]),
642                Word::from([
643                    Felt::new(1_u64),
644                    Felt::new(2_u64),
645                    Felt::new(3_u64),
646                    Felt::new(4_u64),
647                ]),
648            ),
649            (
650                StorageMapKey::from_array([105, 106, 107, 108]),
651                Word::new([Felt::new(5_u64), Felt::new(6_u64), Felt::new(7_u64), Felt::new(8_u64)]),
652            ),
653        ])
654        .unwrap();
655        let storage_slot_map = StorageSlotContent::Map(storage_map.clone());
656
657        let mut account = build_account(
658            vec![asset_0],
659            init_nonce,
660            vec![storage_slot_value_0, storage_slot_value_1, storage_slot_map],
661        );
662
663        // update storage map
664        let key = StorageMapKey::from_array([101, 102, 103, 104]);
665        let value = Word::from([9, 10, 11, 12u32]);
666
667        let updated_map = StorageMapDelta::from_iters([], [(key, value)]);
668        storage_map.insert(key, value).unwrap();
669
670        // build account delta
671        let final_nonce = Felt::new(2);
672        let storage_delta = AccountStorageDelta::new()
673            .add_cleared_items([StorageSlotName::mock(0)])
674            .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))])
675            .add_updated_maps([(StorageSlotName::mock(2), updated_map)]);
676        let account_delta = build_account_delta(
677            account_id,
678            vec![asset_1],
679            vec![asset_0],
680            final_nonce - init_nonce,
681            storage_delta,
682        );
683
684        // apply delta and create final_account
685        account.apply_delta(&account_delta).unwrap();
686
687        let final_account = build_account(
688            vec![asset_1],
689            final_nonce,
690            vec![
691                StorageSlotContent::Value(Word::empty()),
692                StorageSlotContent::Value(Word::from([1, 2, 3, 4u32])),
693                StorageSlotContent::Map(storage_map),
694            ],
695        );
696
697        // assert account is what it should be
698        assert_eq!(account, final_account);
699    }
700
701    #[test]
702    #[should_panic]
703    fn valid_account_delta_with_unchanged_nonce() {
704        // build account
705        let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
706        let init_nonce = Felt::new(1);
707        let asset = FungibleAsset::mock(110);
708        let mut account =
709            build_account(vec![asset], init_nonce, vec![StorageSlotContent::Value(Word::empty())]);
710
711        // build account delta
712        let storage_delta = AccountStorageDelta::new()
713            .add_cleared_items([StorageSlotName::mock(0)])
714            .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))]);
715        let account_delta =
716            build_account_delta(account_id, vec![], vec![asset], init_nonce, storage_delta);
717
718        // apply delta
719        account.apply_delta(&account_delta).unwrap()
720    }
721
722    #[test]
723    #[should_panic]
724    fn valid_account_delta_with_decremented_nonce() {
725        // build account
726        let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
727        let init_nonce = Felt::new(2);
728        let asset = FungibleAsset::mock(100);
729        let mut account =
730            build_account(vec![asset], init_nonce, vec![StorageSlotContent::Value(Word::empty())]);
731
732        // build account delta
733        let final_nonce = Felt::new(1);
734        let storage_delta = AccountStorageDelta::new()
735            .add_cleared_items([StorageSlotName::mock(0)])
736            .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))]);
737        let account_delta =
738            build_account_delta(account_id, vec![], vec![asset], final_nonce, storage_delta);
739
740        // apply delta
741        account.apply_delta(&account_delta).unwrap()
742    }
743
744    #[test]
745    fn empty_account_delta_with_incremented_nonce() {
746        // build account
747        let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
748        let init_nonce = Felt::new(1);
749        let word = Word::from([1, 2, 3, 4u32]);
750        let storage_slot = StorageSlotContent::Value(word);
751        let mut account = build_account(vec![], init_nonce, vec![storage_slot]);
752
753        // build account delta
754        let nonce_delta = Felt::new(1);
755        let account_delta = AccountDelta::new(
756            account_id,
757            AccountStorageDelta::new(),
758            AccountVaultDelta::default(),
759            nonce_delta,
760        )
761        .unwrap();
762
763        // apply delta
764        account.apply_delta(&account_delta).unwrap()
765    }
766
767    pub fn build_account_delta(
768        account_id: AccountId,
769        added_assets: Vec<Asset>,
770        removed_assets: Vec<Asset>,
771        nonce_delta: Felt,
772        storage_delta: AccountStorageDelta,
773    ) -> AccountDelta {
774        let vault_delta = AccountVaultDelta::from_iters(added_assets, removed_assets);
775        AccountDelta::new(account_id, storage_delta, vault_delta, nonce_delta).unwrap()
776    }
777
778    pub fn build_account(
779        assets: Vec<Asset>,
780        nonce: Felt,
781        slots: Vec<StorageSlotContent>,
782    ) -> Account {
783        let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
784        let code = AccountCode::mock();
785
786        let vault = AssetVault::new(&assets).unwrap();
787
788        let slots = slots
789            .into_iter()
790            .enumerate()
791            .map(|(idx, slot)| StorageSlot::new(StorageSlotName::mock(idx), slot))
792            .collect();
793
794        let storage = AccountStorage::new(slots).unwrap();
795
796        Account::new_existing(id, vault, storage, code, nonce)
797    }
798
799    /// Tests that initializing code and storage from a component which does not support the given
800    /// account type returns an error.
801    #[test]
802    fn test_account_unsupported_component_type() {
803        let code1 = "pub proc foo add end";
804        let library1 = Assembler::default().assemble_library([code1]).unwrap();
805
806        // This component support all account types except the regular account with updatable code.
807        let metadata = AccountComponentMetadata::new("test::component1")
808            .with_supported_type(AccountType::FungibleFaucet)
809            .with_supported_type(AccountType::NonFungibleFaucet)
810            .with_supported_type(AccountType::RegularAccountImmutableCode);
811        let component1 = AccountComponent::new(library1, vec![], metadata).unwrap();
812
813        let err = Account::initialize_from_components(
814            AccountType::RegularAccountUpdatableCode,
815            vec![component1],
816        )
817        .unwrap_err();
818
819        assert!(matches!(
820            err,
821            AccountError::UnsupportedComponentForAccountType {
822                account_type: AccountType::RegularAccountUpdatableCode,
823                component_index: 0
824            }
825        ))
826    }
827
828    /// Tests all cases of account ID seed validation.
829    #[test]
830    fn seed_validation() -> anyhow::Result<()> {
831        let account = AccountBuilder::new([5; 32])
832            .with_auth_component(NoopAuthComponent)
833            .with_component(AddComponent)
834            .build()?;
835        let (id, vault, storage, code, _nonce, seed) = account.into_parts();
836        assert!(seed.is_some());
837
838        let other_seed = AccountId::compute_account_seed(
839            [9; 32],
840            AccountType::FungibleFaucet,
841            Network,
842            AccountIdVersion::Version0,
843            code.commitment(),
844            storage.to_commitment(),
845        )?;
846
847        // Set nonce to 1 so the account is considered existing and provide the seed.
848        let err = Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ONE, seed)
849            .unwrap_err();
850        assert_matches!(err, AccountError::ExistingAccountWithSeed);
851
852        // Set nonce to 0 so the account is considered new but don't provide the seed.
853        let err = Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ZERO, None)
854            .unwrap_err();
855        assert_matches!(err, AccountError::NewAccountMissingSeed);
856
857        // Set nonce to 0 so the account is considered new and provide a valid seed that results in
858        // a different ID than the provided one.
859        let err = Account::new(
860            id,
861            vault.clone(),
862            storage.clone(),
863            code.clone(),
864            Felt::ZERO,
865            Some(other_seed),
866        )
867        .unwrap_err();
868        assert_matches!(err, AccountError::AccountIdSeedMismatch { .. });
869
870        // Set nonce to 0 so the account is considered new and provide a seed that results in an
871        // invalid ID.
872        let err = Account::new(
873            id,
874            vault.clone(),
875            storage.clone(),
876            code.clone(),
877            Felt::ZERO,
878            Some(Word::from([1, 2, 3, 4u32])),
879        )
880        .unwrap_err();
881        assert_matches!(err, AccountError::SeedConvertsToInvalidAccountId(_));
882
883        // Set nonce to 1 so the account is considered existing and don't provide the seed, which
884        // should be valid.
885        Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ONE, None)?;
886
887        // Set nonce to 0 so the account is considered new and provide the original seed, which
888        // should be valid.
889        Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ZERO, seed)?;
890
891        Ok(())
892    }
893
894    #[test]
895    fn incrementing_nonce_should_remove_seed() -> anyhow::Result<()> {
896        let mut account = AccountBuilder::new([5; 32])
897            .with_auth_component(NoopAuthComponent)
898            .with_component(AddComponent)
899            .build()?;
900        account.increment_nonce(Felt::ONE)?;
901
902        assert_matches!(account.seed(), None);
903
904        // Sanity check: We should be able to convert the account into a partial account which will
905        // re-check the internal seed - nonce consistency.
906        let _partial_account = PartialAccount::from(&account);
907
908        Ok(())
909    }
910}