miden_objects/account/
mod.rs

1use crate::{
2    AccountError, Digest, Felt, Hasher, Word, ZERO,
3    asset::AssetVault,
4    utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
5};
6
7mod account_id;
8pub use account_id::{
9    AccountId, AccountIdAnchor, AccountIdPrefix, AccountIdPrefixV0, AccountIdV0, AccountIdVersion,
10    AccountStorageMode, AccountType, AddressType, NetworkId,
11};
12
13pub mod auth;
14
15pub use auth::AuthSecretKey;
16
17mod builder;
18pub use builder::AccountBuilder;
19
20pub mod code;
21pub use code::{AccountCode, procedure::AccountProcedureInfo};
22
23pub mod component;
24pub use component::{
25    AccountComponent, AccountComponentMetadata, AccountComponentTemplate, FeltRepresentation,
26    InitStorageData, MapEntry, MapRepresentation, PlaceholderTypeRequirement, StorageEntry,
27    StorageValueName, StorageValueNameError, TemplateType, TemplateTypeError, WordRepresentation,
28};
29
30pub mod delta;
31pub use delta::{
32    AccountDelta, AccountStorageDelta, AccountVaultDelta, FungibleAssetDelta,
33    NonFungibleAssetDelta, NonFungibleDeltaAction, StorageMapDelta,
34};
35
36mod storage;
37pub use storage::{
38    AccountStorage, AccountStorageHeader, PartialStorage, StorageMap, StorageSlot, StorageSlotType,
39};
40
41mod header;
42pub use header::AccountHeader;
43
44mod file;
45pub use file::AccountFile;
46
47mod partial;
48pub use partial::PartialAccount;
49
50// ACCOUNT
51// ================================================================================================
52
53/// An account which can store assets and define rules for manipulating them.
54///
55/// An account consists of the following components:
56/// - Account ID, which uniquely identifies the account and also defines basic properties of the
57///   account.
58/// - Account vault, which stores assets owned by the account.
59/// - Account storage, which is a key-value map (both keys and values are words) used to store
60///   arbitrary user-defined data.
61/// - Account code, which is a set of Miden VM programs defining the public interface of the
62///   account.
63/// - Account nonce, a value which is incremented whenever account state is updated.
64///
65/// Out of the above components account ID is always immutable (once defined it can never be
66/// changed). Other components may be mutated throughout the lifetime of the account. However,
67/// account state can be changed only by invoking one of account interface methods.
68///
69/// The recommended way to build an account is through an [`AccountBuilder`], which can be
70/// instantiated through [`Account::builder`]. See the type's documentation for details.
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct Account {
73    id: AccountId,
74    vault: AssetVault,
75    storage: AccountStorage,
76    code: AccountCode,
77    nonce: Felt,
78}
79
80impl Account {
81    // CONSTRUCTORS
82    // --------------------------------------------------------------------------------------------
83
84    /// Returns an [Account] instantiated with the provided components.
85    pub fn from_parts(
86        id: AccountId,
87        vault: AssetVault,
88        storage: AccountStorage,
89        code: AccountCode,
90        nonce: Felt,
91    ) -> Self {
92        Self { id, vault, storage, code, nonce }
93    }
94
95    /// Creates an account's [`AccountCode`] and [`AccountStorage`] from the provided components.
96    ///
97    /// This merges all libraries of the components into a single
98    /// [`MastForest`](vm_processor::MastForest) to produce the [`AccountCode`]. For each
99    /// procedure in the resulting forest, the storage offset and size are set so that the
100    /// procedure can only access the storage slots of the component in which it was defined and
101    /// each component's storage offset is the total number of slots in the previous components.
102    /// To illustrate, given two components with one and two storage slots respectively:
103    ///
104    /// - RpoFalcon512 Component: Component slot 0 stores the public key.
105    /// - Custom Component: Component slot 0 stores a custom [`StorageSlot::Value`] and component
106    ///   slot 1 stores a custom [`StorageSlot::Map`].
107    ///
108    /// When combined, their assigned slots in the [`AccountStorage`] would be:
109    ///
110    /// - The RpoFalcon512 Component has offset 0 and size 1: Account slot 0 stores the public key.
111    /// - The Custom Component has offset 1 and size 2: Account slot 1 stores the value and account
112    ///   slot 2 stores the map.
113    ///
114    /// The resulting commitments from code and storage can then be used to construct an
115    /// [`AccountId`]. Finally, a new account can then be instantiated from those parts using
116    /// [`Account::new`].
117    ///
118    /// If the account type is faucet the reserved slot (slot 0) will be initialized.
119    /// - For Fungible Faucets the value is [`StorageSlot::empty_value`].
120    /// - For Non-Fungible Faucets the value is [`StorageSlot::empty_map`].
121    ///
122    /// If the storage needs to be initialized with certain values in that slot, those can be added
123    /// after construction with the standard set methods for items and maps.
124    ///
125    /// # Errors
126    ///
127    /// Returns an error if:
128    /// - Any of the components does not support `account_type`.
129    /// - The number of procedures in all merged libraries is 0 or exceeds
130    ///   [`AccountCode::MAX_NUM_PROCEDURES`].
131    /// - Two or more libraries export a procedure with the same MAST root.
132    /// - The number of [`StorageSlot`]s of all components exceeds 255.
133    /// - [`MastForest::merge`](vm_processor::MastForest::merge) fails on all libraries.
134    pub(super) fn initialize_from_components(
135        account_type: AccountType,
136        components: &[AccountComponent],
137    ) -> Result<(AccountCode, AccountStorage), AccountError> {
138        validate_components_support_account_type(components, account_type)?;
139
140        let code = AccountCode::from_components_unchecked(components, account_type)?;
141        let storage = AccountStorage::from_components(components, account_type)?;
142
143        Ok((code, storage))
144    }
145
146    /// Creates a new [`AccountBuilder`] for an account and sets the initial seed from which the
147    /// grinding process for that account's [`AccountId`] will start.
148    ///
149    /// This initial seed should come from a cryptographic random number generator.
150    pub fn builder(init_seed: [u8; 32]) -> AccountBuilder {
151        AccountBuilder::new(init_seed)
152    }
153
154    // PUBLIC ACCESSORS
155    // --------------------------------------------------------------------------------------------
156
157    /// Returns the commitment of this account.
158    ///
159    /// The commitment of an account is computed as hash(id, nonce, vault_root, storage_commitment,
160    /// code_commitment). Computing the account commitment requires 2 permutations of the hash
161    /// function.
162    pub fn commitment(&self) -> Digest {
163        hash_account(
164            self.id,
165            self.nonce,
166            self.vault.root(),
167            self.storage.commitment(),
168            self.code.commitment(),
169        )
170    }
171
172    /// Returns the commitment of this account as used for the initial account state commitment in
173    /// transaction proofs.
174    ///
175    /// For existing accounts, this is exactly the same as [Account::commitment()], however, for new
176    /// accounts this value is set to [crate::EMPTY_WORD]. This is because when a transaction is
177    /// executed against a new account, public input for the initial account state is set to
178    /// [crate::EMPTY_WORD] to distinguish new accounts from existing accounts. The actual
179    /// commitment of the initial account state (and the initial state itself), are provided to
180    /// the VM via the advice provider.
181    pub fn init_commitment(&self) -> Digest {
182        if self.is_new() {
183            Digest::default()
184        } else {
185            self.commitment()
186        }
187    }
188
189    /// Returns unique identifier of this account.
190    pub fn id(&self) -> AccountId {
191        self.id
192    }
193
194    /// Returns the account type
195    pub fn account_type(&self) -> AccountType {
196        self.id.account_type()
197    }
198
199    /// Returns a reference to the vault of this account.
200    pub fn vault(&self) -> &AssetVault {
201        &self.vault
202    }
203
204    /// Returns a reference to the storage of this account.
205    pub fn storage(&self) -> &AccountStorage {
206        &self.storage
207    }
208
209    /// Returns a reference to the code of this account.
210    pub fn code(&self) -> &AccountCode {
211        &self.code
212    }
213
214    /// Returns nonce for this account.
215    pub fn nonce(&self) -> Felt {
216        self.nonce
217    }
218
219    /// Returns true if this account can issue assets.
220    pub fn is_faucet(&self) -> bool {
221        self.id.is_faucet()
222    }
223
224    /// Returns true if this is a regular account.
225    pub fn is_regular_account(&self) -> bool {
226        self.id.is_regular_account()
227    }
228
229    /// Returns `true` if the full state of the account is on chain, i.e. if the storage modes are
230    /// [`AccountStorageMode::Public`] or [`AccountStorageMode::Network`], `false` otherwise.
231    pub fn is_onchain(&self) -> bool {
232        self.id().is_onchain()
233    }
234
235    /// Returns `true` if the storage mode is [`AccountStorageMode::Public`], `false` otherwise.
236    pub fn is_public(&self) -> bool {
237        self.id().is_public()
238    }
239
240    /// Returns `true` if the storage mode is [`AccountStorageMode::Private`], `false` otherwise.
241    pub fn is_private(&self) -> bool {
242        self.id().is_private()
243    }
244
245    /// Returns `true` if the storage mode is [`AccountStorageMode::Network`], `false` otherwise.
246    pub fn is_network(&self) -> bool {
247        self.id().is_network()
248    }
249
250    /// Returns true if the account is new (i.e. it has not been initialized yet).
251    pub fn is_new(&self) -> bool {
252        self.nonce == ZERO
253    }
254
255    /// Decomposes the account into the underlying account components.
256    pub fn into_parts(self) -> (AccountId, AssetVault, AccountStorage, AccountCode, Felt) {
257        (self.id, self.vault, self.storage, self.code, self.nonce)
258    }
259
260    // DATA MUTATORS
261    // --------------------------------------------------------------------------------------------
262
263    /// Applies the provided delta to this account. This updates account vault, storage, and nonce
264    /// to the values specified by the delta.
265    ///
266    /// # Errors
267    /// Returns an error if:
268    /// - Applying vault sub-delta to the vault of this account fails.
269    /// - Applying storage sub-delta to the storage of this account fails.
270    /// - The nonce specified in the provided delta smaller than or equal to the current account
271    ///   nonce.
272    pub fn apply_delta(&mut self, delta: &AccountDelta) -> Result<(), AccountError> {
273        // update vault; we don't check vault delta validity here because `AccountDelta` can contain
274        // only valid vault deltas
275        self.vault
276            .apply_delta(delta.vault())
277            .map_err(AccountError::AssetVaultUpdateError)?;
278
279        // update storage
280        self.storage.apply_delta(delta.storage())?;
281
282        // update nonce
283        if let Some(nonce) = delta.nonce() {
284            self.set_nonce(nonce)?;
285        }
286
287        Ok(())
288    }
289
290    /// Sets the nonce of this account to the specified nonce value.
291    ///
292    /// # Errors
293    /// Returns an error if:
294    /// - The new nonce is smaller than the actual account nonce
295    /// - The new nonce is equal to the actual account nonce
296    pub fn set_nonce(&mut self, nonce: Felt) -> Result<(), AccountError> {
297        if self.nonce.as_int() >= nonce.as_int() {
298            return Err(AccountError::NonceNotMonotonicallyIncreasing {
299                current: self.nonce.as_int(),
300                new: nonce.as_int(),
301            });
302        }
303
304        self.nonce = nonce;
305
306        Ok(())
307    }
308
309    // TEST HELPERS
310    // --------------------------------------------------------------------------------------------
311
312    #[cfg(any(feature = "testing", test))]
313    /// Returns a mutable reference to the vault of this account.
314    pub fn vault_mut(&mut self) -> &mut AssetVault {
315        &mut self.vault
316    }
317
318    #[cfg(any(feature = "testing", test))]
319    /// Returns a mutable reference to the storage of this account.
320    pub fn storage_mut(&mut self) -> &mut AccountStorage {
321        &mut self.storage
322    }
323}
324
325// SERIALIZATION
326// ================================================================================================
327
328impl Serializable for Account {
329    fn write_into<W: ByteWriter>(&self, target: &mut W) {
330        let Account { id, vault, storage, code, nonce } = self;
331
332        id.write_into(target);
333        vault.write_into(target);
334        storage.write_into(target);
335        code.write_into(target);
336        nonce.write_into(target);
337    }
338
339    fn get_size_hint(&self) -> usize {
340        self.id.get_size_hint()
341            + self.vault.get_size_hint()
342            + self.storage.get_size_hint()
343            + self.code.get_size_hint()
344            + self.nonce.get_size_hint()
345    }
346}
347
348impl Deserializable for Account {
349    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
350        let id = AccountId::read_from(source)?;
351        let vault = AssetVault::read_from(source)?;
352        let storage = AccountStorage::read_from(source)?;
353        let code = AccountCode::read_from(source)?;
354        let nonce = Felt::read_from(source)?;
355
356        Ok(Self::from_parts(id, vault, storage, code, nonce))
357    }
358}
359
360// HELPERS
361// ================================================================================================
362
363/// Returns hash of an account with the specified ID, nonce, vault root, storage commitment, and
364/// code commitment.
365///
366/// Hash of an account is computed as hash(id, nonce, vault_root, storage_commitment,
367/// code_commitment). Computing the account commitment requires 2 permutations of the hash function.
368pub fn hash_account(
369    id: AccountId,
370    nonce: Felt,
371    vault_root: Digest,
372    storage_commitment: Digest,
373    code_commitment: Digest,
374) -> Digest {
375    let mut elements = [ZERO; 16];
376    elements[0] = id.suffix();
377    elements[1] = id.prefix().as_felt();
378    elements[3] = nonce;
379    elements[4..8].copy_from_slice(&*vault_root);
380    elements[8..12].copy_from_slice(&*storage_commitment);
381    elements[12..].copy_from_slice(&*code_commitment);
382    Hasher::hash_elements(&elements)
383}
384
385/// Validates that all `components` support the given `account_type`.
386fn validate_components_support_account_type(
387    components: &[AccountComponent],
388    account_type: AccountType,
389) -> Result<(), AccountError> {
390    for (component_index, component) in components.iter().enumerate() {
391        if !component.supports_type(account_type) {
392            return Err(AccountError::UnsupportedComponentForAccountType {
393                account_type,
394                component_index,
395            });
396        }
397    }
398
399    Ok(())
400}
401
402// TESTS
403// ================================================================================================
404
405#[cfg(test)]
406mod tests {
407    use alloc::vec::Vec;
408
409    use assembly::Assembler;
410    use assert_matches::assert_matches;
411    use miden_crypto::{
412        Felt, Word,
413        utils::{Deserializable, Serializable},
414    };
415    use vm_processor::Digest;
416
417    use super::{
418        AccountCode, AccountDelta, AccountId, AccountStorage, AccountStorageDelta,
419        AccountVaultDelta,
420    };
421    use crate::{
422        AccountError,
423        account::{
424            Account, AccountComponent, AccountType, StorageMap, StorageMapDelta, StorageSlot,
425        },
426        asset::{Asset, AssetVault, FungibleAsset, NonFungibleAsset},
427        testing::{
428            account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
429            storage::AccountStorageDeltaBuilder,
430        },
431    };
432
433    #[test]
434    fn test_serde_account() {
435        let init_nonce = Felt::new(1);
436        let asset_0 = FungibleAsset::mock(99);
437        let word = [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)];
438        let storage_slot = StorageSlot::Value(word);
439        let account = build_account(vec![asset_0], init_nonce, vec![storage_slot]);
440
441        let serialized = account.to_bytes();
442        let deserialized = Account::read_from_bytes(&serialized).unwrap();
443        assert_eq!(deserialized, account);
444    }
445
446    #[test]
447    fn test_serde_account_delta() {
448        let final_nonce = Felt::new(2);
449        let asset_0 = FungibleAsset::mock(15);
450        let asset_1 = NonFungibleAsset::mock(&[5, 5, 5]);
451        let storage_delta = AccountStorageDeltaBuilder::default()
452            .add_cleared_items([0])
453            .add_updated_values([(1_u8, [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)])])
454            .build()
455            .unwrap();
456        let account_delta =
457            build_account_delta(vec![asset_1], vec![asset_0], final_nonce, storage_delta);
458
459        let serialized = account_delta.to_bytes();
460        let deserialized = AccountDelta::read_from_bytes(&serialized).unwrap();
461        assert_eq!(deserialized, account_delta);
462    }
463
464    #[test]
465    fn valid_account_delta_is_correctly_applied() {
466        // build account
467        let init_nonce = Felt::new(1);
468        let asset_0 = FungibleAsset::mock(100);
469        let asset_1 = NonFungibleAsset::mock(&[1, 2, 3]);
470
471        // build storage slots
472        let storage_slot_value_0 =
473            StorageSlot::Value([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]);
474        let storage_slot_value_1 =
475            StorageSlot::Value([Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]);
476        let mut storage_map = StorageMap::with_entries([
477            (
478                Digest::new([Felt::new(101), Felt::new(102), Felt::new(103), Felt::new(104)]),
479                [Felt::new(1_u64), Felt::new(2_u64), Felt::new(3_u64), Felt::new(4_u64)],
480            ),
481            (
482                Digest::new([Felt::new(105), Felt::new(106), Felt::new(107), Felt::new(108)]),
483                [Felt::new(5_u64), Felt::new(6_u64), Felt::new(7_u64), Felt::new(8_u64)],
484            ),
485        ])
486        .unwrap();
487        let storage_slot_map = StorageSlot::Map(storage_map.clone());
488
489        let mut account = build_account(
490            vec![asset_0],
491            init_nonce,
492            vec![storage_slot_value_0, storage_slot_value_1, storage_slot_map],
493        );
494
495        // update storage map
496        let new_map_entry = (
497            Digest::new([Felt::new(101), Felt::new(102), Felt::new(103), Felt::new(104)]),
498            [Felt::new(9_u64), Felt::new(10_u64), Felt::new(11_u64), Felt::new(12_u64)],
499        );
500
501        let updated_map =
502            StorageMapDelta::from_iters([], [(new_map_entry.0.into(), new_map_entry.1)]);
503        storage_map.insert(new_map_entry.0, new_map_entry.1);
504
505        // build account delta
506        let final_nonce = Felt::new(2);
507        let storage_delta = AccountStorageDeltaBuilder::default()
508            .add_cleared_items([0])
509            .add_updated_values([(1, [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)])])
510            .add_updated_maps([(2, updated_map)])
511            .build()
512            .unwrap();
513        let account_delta =
514            build_account_delta(vec![asset_1], vec![asset_0], final_nonce, storage_delta);
515
516        // apply delta and create final_account
517        account.apply_delta(&account_delta).unwrap();
518
519        let final_account = build_account(
520            vec![asset_1],
521            final_nonce,
522            vec![
523                StorageSlot::Value(Word::default()),
524                StorageSlot::Value([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]),
525                StorageSlot::Map(storage_map),
526            ],
527        );
528
529        // assert account is what it should be
530        assert_eq!(account, final_account);
531    }
532
533    #[test]
534    #[should_panic]
535    fn valid_account_delta_with_unchanged_nonce() {
536        // build account
537        let init_nonce = Felt::new(1);
538        let asset = FungibleAsset::mock(110);
539        let mut account =
540            build_account(vec![asset], init_nonce, vec![StorageSlot::Value(Word::default())]);
541
542        // build account delta
543        let storage_delta = AccountStorageDeltaBuilder::default()
544            .add_cleared_items([0])
545            .add_updated_values([(1_u8, [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)])])
546            .build()
547            .unwrap();
548        let account_delta = build_account_delta(vec![], vec![asset], init_nonce, storage_delta);
549
550        // apply delta
551        account.apply_delta(&account_delta).unwrap()
552    }
553
554    #[test]
555    #[should_panic]
556    fn valid_account_delta_with_decremented_nonce() {
557        // build account
558        let init_nonce = Felt::new(2);
559        let asset = FungibleAsset::mock(100);
560        let mut account =
561            build_account(vec![asset], init_nonce, vec![StorageSlot::Value(Word::default())]);
562
563        // build account delta
564        let final_nonce = Felt::new(1);
565        let storage_delta = AccountStorageDeltaBuilder::default()
566            .add_cleared_items([0])
567            .add_updated_values([(1_u8, [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)])])
568            .build()
569            .unwrap();
570        let account_delta = build_account_delta(vec![], vec![asset], final_nonce, storage_delta);
571
572        // apply delta
573        account.apply_delta(&account_delta).unwrap()
574    }
575
576    #[test]
577    fn empty_account_delta_with_incremented_nonce() {
578        // build account
579        let init_nonce = Felt::new(1);
580        let word = [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)];
581        let storage_slot = StorageSlot::Value(word);
582        let mut account = build_account(vec![], init_nonce, vec![storage_slot]);
583
584        // build account delta
585        let final_nonce = Felt::new(2);
586        let account_delta = AccountDelta::new(
587            AccountStorageDelta::default(),
588            AccountVaultDelta::default(),
589            Some(final_nonce),
590        )
591        .unwrap();
592
593        // apply delta
594        account.apply_delta(&account_delta).unwrap()
595    }
596
597    pub fn build_account_delta(
598        added_assets: Vec<Asset>,
599        removed_assets: Vec<Asset>,
600        nonce: Felt,
601        storage_delta: AccountStorageDelta,
602    ) -> AccountDelta {
603        let vault_delta = AccountVaultDelta::from_iters(added_assets, removed_assets);
604        AccountDelta::new(storage_delta, vault_delta, Some(nonce)).unwrap()
605    }
606
607    pub fn build_account(assets: Vec<Asset>, nonce: Felt, slots: Vec<StorageSlot>) -> Account {
608        let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
609        let code = AccountCode::mock();
610
611        let vault = AssetVault::new(&assets).unwrap();
612
613        let storage = AccountStorage::new(slots).unwrap();
614
615        Account::from_parts(id, vault, storage, code, nonce)
616    }
617
618    /// Tests that initializing code and storage from a component which does not support the given
619    /// account type returns an error.
620    #[test]
621    fn test_account_unsupported_component_type() {
622        let code1 = "export.foo add end";
623        let library1 = Assembler::default().assemble_library([code1]).unwrap();
624
625        // This component support all account types except the regular account with updatable code.
626        let component1 = AccountComponent::new(library1, vec![])
627            .unwrap()
628            .with_supported_type(AccountType::FungibleFaucet)
629            .with_supported_type(AccountType::NonFungibleFaucet)
630            .with_supported_type(AccountType::RegularAccountImmutableCode);
631
632        let err = Account::initialize_from_components(
633            AccountType::RegularAccountUpdatableCode,
634            &[component1],
635        )
636        .unwrap_err();
637
638        assert!(matches!(
639            err,
640            AccountError::UnsupportedComponentForAccountType {
641                account_type: AccountType::RegularAccountUpdatableCode,
642                component_index: 0
643            }
644        ))
645    }
646
647    /// Two components who export a procedure with the same MAST root should fail to convert into
648    /// code and storage.
649    #[test]
650    fn test_account_duplicate_exported_mast_root() {
651        let code1 = "export.foo add eq.1 end";
652        let code2 = "export.bar add eq.1 end";
653
654        let library1 = Assembler::default().assemble_library([code1]).unwrap();
655        let library2 = Assembler::default().assemble_library([code2]).unwrap();
656
657        let component1 = AccountComponent::new(library1, vec![]).unwrap().with_supports_all_types();
658        let component2 = AccountComponent::new(library2, vec![]).unwrap().with_supports_all_types();
659
660        let err = Account::initialize_from_components(
661            AccountType::RegularAccountUpdatableCode,
662            &[component1, component2],
663        )
664        .unwrap_err();
665
666        assert_matches!(err, AccountError::AccountComponentDuplicateProcedureRoot(_))
667    }
668}