miden_objects/account/
mod.rs

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