miden_objects/account/
mod.rs

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