1use alloc::string::ToString;
2use alloc::vec::Vec;
3
4use crate::account::delta::AssetDeltaOperation;
5use crate::asset::AssetVault;
6use crate::crypto::SequentialCommit;
7use crate::errors::AccountError;
8use crate::utils::serde::{
9 ByteReader,
10 ByteWriter,
11 Deserializable,
12 DeserializationError,
13 Serializable,
14};
15use crate::{Felt, Hasher, Word, ZERO};
16
17mod account_id;
18pub use account_id::{
19 AccountId,
20 AccountIdPrefix,
21 AccountIdPrefixV1,
22 AccountIdV1,
23 AccountIdVersion,
24 AccountType,
25 AssetCallbackFlag,
26};
27
28pub(crate) mod name_validation;
29
30pub mod auth;
31
32mod access;
33pub use access::RoleSymbol;
34
35mod builder;
36pub use builder::AccountBuilder;
37
38pub mod code;
39pub use code::AccountCode;
40pub use code::procedure::AccountProcedureRoot;
41
42pub mod component;
43pub use component::{AccountComponent, AccountComponentCode, AccountComponentMetadata};
44
45pub mod interface;
46pub use interface::{AccountCodeInterface, AccountComponentName};
47
48mod patch;
49pub(crate) use patch::validate_new_public_account;
50pub use patch::{
51 AccountPatch,
52 AccountStoragePatch,
53 AccountUpdateDetails,
54 AccountVaultPatch,
55 StorageMapPatch,
56 StorageMapPatchEntries,
57 StoragePatchOperation,
58 StorageSlotPatch,
59 StorageValuePatch,
60};
61
62pub mod delta;
63pub use delta::{AccountDelta, AccountVaultDelta, AssetDelta};
64
65pub mod storage;
66pub use storage::{
67 AccountStorage,
68 AccountStorageHeader,
69 PartialStorage,
70 PartialStorageMap,
71 StorageMap,
72 StorageMapKey,
73 StorageMapKeyHash,
74 StorageMapWitness,
75 StorageSlot,
76 StorageSlotContent,
77 StorageSlotHeader,
78 StorageSlotId,
79 StorageSlotName,
80 StorageSlotType,
81};
82
83mod header;
84pub use header::AccountHeader;
85
86mod file;
87pub use file::AccountFile;
88
89mod partial;
90pub use partial::PartialAccount;
91
92#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct Account {
115 id: AccountId,
116 vault: AssetVault,
117 storage: AccountStorage,
118 code: AccountCode,
119 nonce: Felt,
120 seed: Option<Word>,
121}
122
123impl Account {
124 pub fn new(
139 id: AccountId,
140 vault: AssetVault,
141 storage: AccountStorage,
142 code: AccountCode,
143 nonce: Felt,
144 seed: Option<Word>,
145 ) -> Result<Self, AccountError> {
146 validate_account_seed(id, code.commitment(), storage.to_commitment(), seed, nonce)?;
147 validate_asset_callbacks(id, &storage)?;
148
149 Ok(Self::new_unchecked(id, vault, storage, code, nonce, seed))
150 }
151
152 pub fn new_unchecked(
159 id: AccountId,
160 vault: AssetVault,
161 storage: AccountStorage,
162 code: AccountCode,
163 nonce: Felt,
164 seed: Option<Word>,
165 ) -> Self {
166 Self { id, vault, storage, code, nonce, seed }
167 }
168
169 pub(super) fn initialize_from_components(
191 components: Vec<AccountComponent>,
192 ) -> Result<(AccountCode, AccountStorage), AccountError> {
193 let code = AccountCode::from_components_unchecked(&components)?;
194 let storage = AccountStorage::from_components(components)?;
195
196 Ok((code, storage))
197 }
198
199 pub fn builder(init_seed: [u8; 32]) -> AccountBuilder {
204 AccountBuilder::new(init_seed)
205 }
206
207 pub fn to_header(&self) -> AccountHeader {
212 AccountHeader::from(self)
213 }
214
215 pub fn to_commitment(&self) -> Word {
219 AccountHeader::from(self).to_commitment()
220 }
221
222 pub fn initial_commitment(&self) -> Word {
232 if self.is_new() {
233 Word::empty()
234 } else {
235 self.to_commitment()
236 }
237 }
238
239 pub fn id(&self) -> AccountId {
241 self.id
242 }
243
244 pub fn vault(&self) -> &AssetVault {
246 &self.vault
247 }
248
249 pub fn storage(&self) -> &AccountStorage {
251 &self.storage
252 }
253
254 pub fn code(&self) -> &AccountCode {
256 &self.code
257 }
258
259 pub fn code_interface(&self) -> AccountCodeInterface {
262 self.code.interface(self.id())
263 }
264
265 pub fn nonce(&self) -> Felt {
267 self.nonce
268 }
269
270 pub fn seed(&self) -> Option<Word> {
274 self.seed
275 }
276
277 pub fn is_public(&self) -> bool {
279 self.id().is_public()
280 }
281
282 pub fn is_private(&self) -> bool {
284 self.id().is_private()
285 }
286
287 pub fn is_new(&self) -> bool {
292 self.nonce == ZERO
293 }
294
295 pub fn into_parts(
297 self,
298 ) -> (AccountId, AssetVault, AccountStorage, AccountCode, Felt, Option<Word>) {
299 (self.id, self.vault, self.storage, self.code, self.nonce, self.seed)
300 }
301
302 pub fn apply_patch(&mut self, patch: &AccountPatch) -> Result<(), AccountError> {
319 if patch.id() != self.id {
320 return Err(AccountError::PatchAccountIdMismatch {
321 account_id: self.id,
322 patch_id: patch.id(),
323 });
324 }
325
326 if patch.is_full_state() {
327 return Err(AccountError::ApplyFullStatePatchToAccount);
328 }
329
330 self.vault
331 .apply_patch(patch.vault())
332 .map_err(AccountError::AssetVaultUpdateError)?;
333
334 self.storage.apply_patch(patch.storage())?;
335
336 if let Some(new_nonce) = patch.final_nonce() {
337 self.set_nonce(new_nonce)?;
338 }
339
340 Ok(())
341 }
342
343 pub fn increment_nonce(&mut self, nonce_delta: Felt) -> Result<(), AccountError> {
350 let new_nonce = self.nonce + nonce_delta;
351
352 self.set_nonce(new_nonce)
353 }
354
355 pub fn set_nonce(&mut self, new_nonce: Felt) -> Result<(), AccountError> {
361 if new_nonce.as_canonical_u64() < self.nonce.as_canonical_u64() {
362 return Err(AccountError::NonceMustIncrease { current: self.nonce, new: new_nonce });
363 }
364
365 self.nonce = new_nonce;
366
367 if !self.is_new() {
372 self.seed = None;
373 }
374
375 Ok(())
376 }
377
378 #[cfg(any(feature = "testing", test))]
382 pub fn vault_mut(&mut self) -> &mut AssetVault {
384 &mut self.vault
385 }
386
387 #[cfg(any(feature = "testing", test))]
388 pub fn storage_mut(&mut self) -> &mut AccountStorage {
390 &mut self.storage
391 }
392}
393
394impl TryFrom<Account> for AccountDelta {
395 type Error = AccountError;
396
397 fn try_from(account: Account) -> Result<Self, Self::Error> {
406 let Account { id, vault, storage, code, nonce, seed } = account;
407
408 if seed.is_some() {
409 return Err(AccountError::DeltaFromAccountWithSeed);
410 }
411
412 let slot_deltas = storage
413 .into_slots()
414 .into_iter()
415 .map(StorageSlot::into_parts)
416 .map(|(slot_name, slot_content)| (slot_name, StorageSlotPatch::from(slot_content)))
417 .collect();
418 let storage_patch = AccountStoragePatch::from_raw(slot_deltas)
421 .expect("number of slot patches is bounded by the account's storage slots");
422
423 let vault_delta = AccountVaultDelta::new(
425 vault.assets().map(|asset| AssetDelta::new(AssetDeltaOperation::Add, asset)),
426 )
427 .expect("assets in the account vault should be unique");
428
429 let nonce_delta = nonce;
432
433 let delta = AccountDelta::new(id, storage_patch, vault_delta, Some(code), nonce_delta)
437 .expect("full state delta from account contains only create patches");
438
439 Ok(delta)
440 }
441}
442
443impl TryFrom<Account> for AccountPatch {
444 type Error = AccountError;
445
446 fn try_from(account: Account) -> Result<Self, Self::Error> {
455 let Account { id, vault, storage, code, nonce, seed } = account;
456
457 if seed.is_some() {
458 return Err(AccountError::PatchFromAccountWithSeed);
459 }
460
461 let slot_patches = storage
462 .into_slots()
463 .into_iter()
464 .map(StorageSlot::into_parts)
465 .map(|(slot_name, slot_content)| (slot_name, StorageSlotPatch::from(slot_content)))
466 .collect();
467 let storage_patch = AccountStoragePatch::from_raw(slot_patches)
470 .expect("number of slot patches is bounded by the account's storage slots");
471
472 let mut vault_patch = AccountVaultPatch::default();
473 for asset in vault.assets() {
474 vault_patch.insert_asset(asset);
475 }
476
477 let patch = AccountPatch::new(id, storage_patch, vault_patch, Some(code), Some(nonce))
482 .expect("non-seeded account should yield a valid patch");
483
484 Ok(patch)
485 }
486}
487
488impl SequentialCommit for Account {
489 type Commitment = Word;
490
491 fn to_elements(&self) -> Vec<Felt> {
492 AccountHeader::from(self).to_elements()
493 }
494
495 fn to_commitment(&self) -> Self::Commitment {
496 AccountHeader::from(self).to_commitment()
497 }
498}
499
500impl Serializable for Account {
504 fn write_into<W: ByteWriter>(&self, target: &mut W) {
505 let Account { id, vault, storage, code, nonce, seed } = self;
506
507 AccountHeader::VERSION_1.write_into(target);
508 id.write_into(target);
509 vault.write_into(target);
510 storage.write_into(target);
511 code.write_into(target);
512 nonce.write_into(target);
513 seed.write_into(target);
514 }
515
516 fn get_size_hint(&self) -> usize {
517 AccountHeader::VERSION_1.get_size_hint()
518 + self.id.get_size_hint()
519 + self.vault.get_size_hint()
520 + self.storage.get_size_hint()
521 + self.code.get_size_hint()
522 + self.nonce.get_size_hint()
523 + self.seed.get_size_hint()
524 }
525}
526
527impl Deserializable for Account {
528 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
529 let version = u8::read_from(source)?;
530
531 if version != AccountHeader::VERSION_1 {
532 return Err(DeserializationError::InvalidValue(format!(
533 "account version is {} but only version {} is supported",
534 version,
535 AccountHeader::VERSION_1,
536 )));
537 }
538
539 let id = AccountId::read_from(source)?;
540 let vault = AssetVault::read_from(source)?;
541 let storage = AccountStorage::read_from(source)?;
542 let code = AccountCode::read_from(source)?;
543 let nonce = Felt::read_from(source)?;
544 let seed = <Option<Word>>::read_from(source)?;
545
546 Self::new(id, vault, storage, code, nonce, seed)
547 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
548 }
549}
550
551pub(super) fn validate_asset_callbacks(
560 id: AccountId,
561 storage: &AccountStorage,
562) -> Result<(), AccountError> {
563 if !id.asset_callback_flag().is_enabled() && storage.has_callback_slots() {
564 return Err(AccountError::AssetCallbackSlotWithDisabledFlag(id));
565 }
566
567 Ok(())
568}
569
570pub(super) fn validate_account_seed(
572 id: AccountId,
573 code_commitment: Word,
574 storage_commitment: Word,
575 seed: Option<Word>,
576 nonce: Felt,
577) -> Result<(), AccountError> {
578 let account_is_new = nonce == ZERO;
579
580 match (account_is_new, seed) {
581 (true, Some(seed)) => {
582 let account_id =
583 AccountId::new(seed, id.version(), code_commitment, storage_commitment)
584 .map_err(AccountError::SeedConvertsToInvalidAccountId)?;
585
586 if account_id != id {
587 return Err(AccountError::AccountIdSeedMismatch {
588 expected: id,
589 actual: account_id,
590 });
591 }
592
593 Ok(())
594 },
595 (true, None) => Err(AccountError::NewAccountMissingSeed),
596 (false, Some(_)) => Err(AccountError::ExistingAccountWithSeed),
597 (false, None) => Ok(()),
598 }
599}
600
601#[cfg(test)]
605mod tests {
606 use alloc::vec::Vec;
607
608 use assert_matches::assert_matches;
609 use miden_crypto::utils::{Deserializable, DeserializationError, Serializable};
610 use miden_crypto::{Felt, Word};
611
612 use super::{AccountCode, AccountDelta, AccountId, AccountStorage, AccountStoragePatch};
613 use crate::account::{
614 Account,
615 AccountBuilder,
616 AccountIdVersion,
617 AccountPatch,
618 AccountType,
619 AccountVaultDelta,
620 AccountVaultPatch,
621 AssetCallbackFlag,
622 PartialAccount,
623 StorageMap,
624 StorageMapKey,
625 StorageSlot,
626 StorageSlotContent,
627 StorageSlotName,
628 };
629 use crate::asset::{Asset, AssetCallbacks, AssetVault, FungibleAsset, NonFungibleAsset};
630 use crate::errors::AccountError;
631 use crate::testing::account_id::{
632 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
633 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2,
634 };
635 use crate::testing::add_component::AddComponent;
636 use crate::testing::noop_auth_component::NoopAuthComponent;
637
638 #[test]
639 fn test_serde_account() {
640 let init_nonce = Felt::from(1_u32);
641 let asset_0 = FungibleAsset::mock(99);
642 let word = Word::from([1, 2, 3, 4u32]);
643 let storage_slot = StorageSlotContent::Value(word);
644 let account = build_account(vec![asset_0], init_nonce, vec![storage_slot]);
645
646 let serialized = account.to_bytes();
647 let deserialized = Account::read_from_bytes(&serialized).unwrap();
648 assert_eq!(deserialized, account);
649 }
650
651 #[test]
652 fn test_serde_account_delta() {
653 let nonce_delta = Felt::from(2_u32);
654 let asset_0 = FungibleAsset::mock(15);
655 let asset_1 = NonFungibleAsset::mock(&[5, 5, 5]);
656 let storage_patch = AccountStoragePatch::builder()
657 .update_value(StorageSlotName::mock(0), Word::empty())
658 .update_value(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))
659 .build();
660 let account_delta =
661 build_account_delta(vec![asset_1], vec![asset_0], nonce_delta, storage_patch);
662
663 let serialized = account_delta.to_bytes();
664 let deserialized = AccountDelta::read_from_bytes(&serialized).unwrap();
665 assert_eq!(deserialized, account_delta);
666 }
667
668 #[test]
669 fn account_patch_is_correctly_applied() -> anyhow::Result<()> {
670 let init_nonce = Felt::from(1_u32);
671 let asset_0 = FungibleAsset::mock(100);
672 let asset_1 = NonFungibleAsset::mock(&[1, 2, 3]);
673
674 let storage_slot_value_0 = StorageSlotContent::Value(Word::from([1, 2, 3, 4u32]));
676 let storage_slot_value_1 = StorageSlotContent::Value(Word::from([5, 6, 7, 8u32]));
677 let map_key_0 = StorageMapKey::from_array([101, 102, 103, 104]);
678 let map_key_1 = StorageMapKey::from_array([105, 106, 107, 108]);
679
680 let mut storage_map = StorageMap::with_entries([
681 (map_key_0, Word::from([1, 2, 3, 4_u32])),
682 (map_key_1, Word::from([5, 6, 7, 8_u32])),
683 ])
684 .unwrap();
685 let storage_slot_map = StorageSlotContent::Map(storage_map.clone());
686
687 let initial_account = build_account(
689 vec![asset_0],
690 init_nonce,
691 vec![storage_slot_value_0, storage_slot_value_1, storage_slot_map],
692 );
693
694 let value = Word::from([9, 10, 11, 12u32]);
695 storage_map.insert(map_key_0, value).unwrap();
696
697 let final_nonce = init_nonce + Felt::ONE;
699 let storage_patch = AccountStoragePatch::builder()
700 .update_value(StorageSlotName::mock(0), Word::empty())
701 .update_value(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))
702 .update_map(StorageSlotName::mock(2), [(map_key_0, value)])
703 .build();
704 let account_patch =
705 build_account_patch(final_nonce, vec![asset_1], vec![asset_0], storage_patch);
706
707 let mut account_with_patched = initial_account;
709
710 account_with_patched.apply_patch(&account_patch)?;
711
712 let final_account = build_account(
713 vec![asset_1],
714 final_nonce,
715 vec![
716 StorageSlotContent::Value(Word::empty()),
717 StorageSlotContent::Value(Word::from([1, 2, 3, 4u32])),
718 StorageSlotContent::Map(storage_map),
719 ],
720 );
721
722 assert_eq!(account_with_patched, final_account);
723
724 Ok(())
725 }
726
727 #[test]
728 fn apply_patch_rejects_new_account_patch() -> anyhow::Result<()> {
729 let account_id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE)?;
730 let init_nonce = Felt::from(1_u32);
731 let mut account = build_account(vec![], init_nonce, vec![]);
732
733 let patch = AccountPatch::new(
734 account_id,
735 AccountStoragePatch::new(),
736 AccountVaultPatch::default(),
737 Some(AccountCode::mock()),
738 Some(Felt::from(2_u32)),
739 )?;
740
741 let err = account.apply_patch(&patch).unwrap_err();
742 assert_matches!(err, AccountError::ApplyFullStatePatchToAccount);
743
744 Ok(())
745 }
746
747 #[test]
748 fn apply_patch_rejects_non_increasing_nonce() -> anyhow::Result<()> {
749 let account_id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE)?;
750 let init_nonce = 5_u32;
751 let mut account = build_account(vec![], Felt::from(init_nonce), vec![]);
752
753 let patch_smaller = AccountPatch::new(
755 account_id,
756 AccountStoragePatch::new(),
757 AccountVaultPatch::default(),
758 None,
759 Some(Felt::from(init_nonce - 1)),
760 )?;
761 let err = account.apply_patch(&patch_smaller).unwrap_err();
762 assert_matches!(err, AccountError::NonceMustIncrease { .. });
763
764 Ok(())
765 }
766
767 #[test]
768 fn apply_patch_rejects_id_mismatch() -> anyhow::Result<()> {
769 let other_account_id =
770 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2)?;
771 let init_nonce = Felt::from(1_u32);
772 let mut account = build_account(vec![], init_nonce, vec![]);
773
774 let patch = AccountPatch::new(
775 other_account_id,
776 AccountStoragePatch::default(),
777 AccountVaultPatch::default(),
778 None,
779 Some(Felt::from(2_u32)),
780 )?;
781
782 let err = account.apply_patch(&patch).unwrap_err();
783 assert_matches!(err, AccountError::PatchAccountIdMismatch { .. });
784
785 Ok(())
786 }
787
788 #[test]
789 fn apply_empty_account_patch() -> anyhow::Result<()> {
790 let nonce = Felt::from(2u8);
791 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
792 let empty_patch = AccountPatch::new(
793 id,
794 AccountStoragePatch::default(),
795 AccountVaultPatch::default(),
796 None,
797 None,
798 )?;
799 let init_account = build_account(vec![], nonce, vec![]);
800
801 let mut account_with_patch = init_account.clone();
802 account_with_patch.apply_patch(&empty_patch)?;
803
804 assert_eq!(init_account, account_with_patch, "account should be unchanged");
805
806 Ok(())
807 }
808
809 #[test]
810 fn apply_empty_account_patch_with_incremented_nonce() -> anyhow::Result<()> {
811 let initial_nonce = Felt::from(2u8);
812 let final_nonce = initial_nonce + Felt::ONE;
813
814 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
815 let empty_patch = AccountPatch::new(
816 id,
817 AccountStoragePatch::default(),
818 AccountVaultPatch::default(),
819 None,
820 Some(final_nonce),
821 )?;
822
823 let init_account = build_account(vec![], initial_nonce, vec![]);
824 let final_account = build_account(vec![], final_nonce, vec![]);
825
826 let mut account_with_patch = init_account.clone();
827 account_with_patch.apply_patch(&empty_patch)?;
828
829 assert_eq!(final_account, account_with_patch);
830
831 Ok(())
832 }
833
834 pub fn build_account_delta(
835 added_assets: Vec<Asset>,
836 removed_assets: Vec<Asset>,
837 nonce_delta: Felt,
838 storage_patch: AccountStoragePatch,
839 ) -> AccountDelta {
840 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
841 let vault_delta = AccountVaultDelta::from_iters(added_assets, removed_assets);
842 AccountDelta::new(id, storage_patch, vault_delta, None, nonce_delta).unwrap()
843 }
844
845 pub fn build_account_patch(
846 final_nonce: Felt,
847 added_assets: Vec<Asset>,
848 removed_assets: Vec<Asset>,
849 storage_patch: AccountStoragePatch,
850 ) -> AccountPatch {
851 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
852 let vault_patch = AccountVaultPatch::from_iters(added_assets, removed_assets);
853 AccountPatch::new(id, storage_patch, vault_patch, None, Some(final_nonce)).unwrap()
854 }
855
856 pub fn build_account(
857 assets: Vec<Asset>,
858 nonce: Felt,
859 slots: Vec<StorageSlotContent>,
860 ) -> Account {
861 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
862 let code = AccountCode::mock();
863
864 let vault = AssetVault::new(&assets).unwrap();
865
866 let slots = slots
867 .into_iter()
868 .enumerate()
869 .map(|(idx, slot)| StorageSlot::new(StorageSlotName::mock(idx), slot))
870 .collect();
871
872 let storage = AccountStorage::new(slots).unwrap();
873
874 Account::new_existing(id, vault, storage, code, nonce)
875 }
876
877 #[test]
880 fn account_new_rejects_callback_slot_with_disabled_flag() -> anyhow::Result<()> {
881 let account = AccountBuilder::new([5; 32])
882 .with_component(NoopAuthComponent)
883 .with_component(AddComponent)
884 .build_existing()?;
885 assert_eq!(account.id().asset_callback_flag(), AssetCallbackFlag::Disabled);
886
887 let (id, vault, storage, code, nonce, _seed) = account.into_parts();
888
889 let mut slots = storage.into_slots();
890 slots.push(StorageSlot::with_value(
891 AssetCallbacks::on_before_asset_added_to_account_slot().clone(),
892 Word::from([1u32, 2, 3, 4]),
893 ));
894 let storage = AccountStorage::new(slots)?;
895
896 let err = Account::new(id, vault, storage, code, nonce, None).unwrap_err();
897 assert_matches!(err, AccountError::AssetCallbackSlotWithDisabledFlag(_));
898
899 Ok(())
900 }
901
902 #[test]
904 fn seed_validation() -> anyhow::Result<()> {
905 let account = AccountBuilder::new([5; 32])
906 .with_component(NoopAuthComponent)
907 .with_component(AddComponent)
908 .build()?;
909 let (id, vault, storage, code, _nonce, seed) = account.into_parts();
910 assert!(seed.is_some());
911
912 let other_seed = AccountId::compute_account_seed(
913 [9; 32],
914 AccountType::Public,
915 AssetCallbackFlag::Disabled,
916 AccountIdVersion::Version1,
917 code.commitment(),
918 storage.to_commitment(),
919 )?;
920
921 let err = Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ONE, seed)
923 .unwrap_err();
924 assert_matches!(err, AccountError::ExistingAccountWithSeed);
925
926 let err = Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ZERO, None)
928 .unwrap_err();
929 assert_matches!(err, AccountError::NewAccountMissingSeed);
930
931 let err = Account::new(
934 id,
935 vault.clone(),
936 storage.clone(),
937 code.clone(),
938 Felt::ZERO,
939 Some(other_seed),
940 )
941 .unwrap_err();
942 assert_matches!(err, AccountError::AccountIdSeedMismatch { .. });
943
944 let err = Account::new(
947 id,
948 vault.clone(),
949 storage.clone(),
950 code.clone(),
951 Felt::ZERO,
952 Some(Word::from([1, 2, 3, 4u32])),
953 )
954 .unwrap_err();
955 assert_matches!(err, AccountError::SeedConvertsToInvalidAccountId(_));
956
957 Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ONE, None)?;
960
961 Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ZERO, seed)?;
964
965 Ok(())
966 }
967
968 #[test]
969 fn incrementing_nonce_should_remove_seed() -> anyhow::Result<()> {
970 let mut account = AccountBuilder::new([5; 32])
971 .with_component(NoopAuthComponent)
972 .with_component(AddComponent)
973 .build()?;
974 account.increment_nonce(Felt::ONE)?;
975
976 assert_matches!(account.seed(), None);
977
978 let _partial_account = PartialAccount::from(&account);
981
982 Ok(())
983 }
984
985 #[test]
986 fn account_deserialization_rejects_unsupported_version() {
987 let error = Account::read_from_bytes(&[0]).unwrap_err();
988
989 assert_matches!(error, DeserializationError::InvalidValue(message) => {
990 assert!(message.contains("account version is 0"));
991 });
992 }
993}