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 partial;
87pub use partial::PartialAccount;
88
89#[derive(Debug, Clone, PartialEq, Eq)]
111pub struct Account {
112 id: AccountId,
113 vault: AssetVault,
114 storage: AccountStorage,
115 code: AccountCode,
116 nonce: Felt,
117 seed: Option<Word>,
118}
119
120impl Account {
121 pub fn new(
136 id: AccountId,
137 vault: AssetVault,
138 storage: AccountStorage,
139 code: AccountCode,
140 nonce: Felt,
141 seed: Option<Word>,
142 ) -> Result<Self, AccountError> {
143 validate_account_seed(id, code.commitment(), storage.to_commitment(), seed, nonce)?;
144 validate_asset_callbacks(id, &storage)?;
145
146 Ok(Self::new_unchecked(id, vault, storage, code, nonce, seed))
147 }
148
149 pub fn new_unchecked(
156 id: AccountId,
157 vault: AssetVault,
158 storage: AccountStorage,
159 code: AccountCode,
160 nonce: Felt,
161 seed: Option<Word>,
162 ) -> Self {
163 Self { id, vault, storage, code, nonce, seed }
164 }
165
166 pub(super) fn initialize_from_components(
188 components: Vec<AccountComponent>,
189 ) -> Result<(AccountCode, AccountStorage), AccountError> {
190 let code = AccountCode::from_components_unchecked(&components)?;
191 let storage = AccountStorage::from_components(components)?;
192
193 Ok((code, storage))
194 }
195
196 pub fn builder(init_seed: [u8; 32]) -> AccountBuilder {
201 AccountBuilder::new(init_seed)
202 }
203
204 pub fn to_header(&self) -> AccountHeader {
209 AccountHeader::from(self)
210 }
211
212 pub fn to_commitment(&self) -> Word {
216 AccountHeader::from(self).to_commitment()
217 }
218
219 pub fn initial_commitment(&self) -> Word {
229 if self.is_new() {
230 Word::empty()
231 } else {
232 self.to_commitment()
233 }
234 }
235
236 pub fn id(&self) -> AccountId {
238 self.id
239 }
240
241 pub fn vault(&self) -> &AssetVault {
243 &self.vault
244 }
245
246 pub fn storage(&self) -> &AccountStorage {
248 &self.storage
249 }
250
251 pub fn code(&self) -> &AccountCode {
253 &self.code
254 }
255
256 pub fn code_interface(&self) -> AccountCodeInterface {
259 self.code.interface(self.id())
260 }
261
262 pub fn nonce(&self) -> Felt {
264 self.nonce
265 }
266
267 pub fn seed(&self) -> Option<Word> {
271 self.seed
272 }
273
274 pub fn is_public(&self) -> bool {
276 self.id().is_public()
277 }
278
279 pub fn is_private(&self) -> bool {
281 self.id().is_private()
282 }
283
284 pub fn is_new(&self) -> bool {
289 self.nonce == ZERO
290 }
291
292 pub fn into_parts(
294 self,
295 ) -> (AccountId, AssetVault, AccountStorage, AccountCode, Felt, Option<Word>) {
296 (self.id, self.vault, self.storage, self.code, self.nonce, self.seed)
297 }
298
299 pub fn apply_patch(&mut self, patch: &AccountPatch) -> Result<(), AccountError> {
316 if patch.id() != self.id {
317 return Err(AccountError::PatchAccountIdMismatch {
318 account_id: self.id,
319 patch_id: patch.id(),
320 });
321 }
322
323 if patch.is_full_state() {
324 return Err(AccountError::ApplyFullStatePatchToAccount);
325 }
326
327 self.vault
328 .apply_patch(patch.vault())
329 .map_err(AccountError::AssetVaultUpdateError)?;
330
331 self.storage.apply_patch(patch.storage())?;
332
333 if let Some(new_nonce) = patch.final_nonce() {
334 self.set_nonce(new_nonce)?;
335 }
336
337 Ok(())
338 }
339
340 pub fn increment_nonce(&mut self, nonce_delta: Felt) -> Result<(), AccountError> {
347 let new_nonce = self.nonce + nonce_delta;
348
349 self.set_nonce(new_nonce)
350 }
351
352 pub fn set_nonce(&mut self, new_nonce: Felt) -> Result<(), AccountError> {
358 if new_nonce.as_canonical_u64() < self.nonce.as_canonical_u64() {
359 return Err(AccountError::NonceMustIncrease { current: self.nonce, new: new_nonce });
360 }
361
362 self.nonce = new_nonce;
363
364 if !self.is_new() {
369 self.seed = None;
370 }
371
372 Ok(())
373 }
374
375 #[cfg(any(feature = "testing", test))]
379 pub fn vault_mut(&mut self) -> &mut AssetVault {
381 &mut self.vault
382 }
383
384 #[cfg(any(feature = "testing", test))]
385 pub fn storage_mut(&mut self) -> &mut AccountStorage {
387 &mut self.storage
388 }
389}
390
391impl TryFrom<Account> for AccountDelta {
392 type Error = AccountError;
393
394 fn try_from(account: Account) -> Result<Self, Self::Error> {
403 let Account { id, vault, storage, code, nonce, seed } = account;
404
405 if seed.is_some() {
406 return Err(AccountError::DeltaFromAccountWithSeed);
407 }
408
409 let slot_deltas = storage
410 .into_slots()
411 .into_iter()
412 .map(StorageSlot::into_parts)
413 .map(|(slot_name, slot_content)| (slot_name, StorageSlotPatch::from(slot_content)))
414 .collect();
415 let storage_patch = AccountStoragePatch::from_raw(slot_deltas)
418 .expect("number of slot patches is bounded by the account's storage slots");
419
420 let vault_delta = AccountVaultDelta::new(
422 vault.assets().map(|asset| AssetDelta::new(AssetDeltaOperation::Add, asset)),
423 )
424 .expect("assets in the account vault should be unique");
425
426 let nonce_delta = nonce;
429
430 let delta = AccountDelta::new(id, storage_patch, vault_delta, Some(code), nonce_delta)
434 .expect("full state delta from account contains only create patches");
435
436 Ok(delta)
437 }
438}
439
440impl TryFrom<Account> for AccountPatch {
441 type Error = AccountError;
442
443 fn try_from(account: Account) -> Result<Self, Self::Error> {
452 let Account { id, vault, storage, code, nonce, seed } = account;
453
454 if seed.is_some() {
455 return Err(AccountError::PatchFromAccountWithSeed);
456 }
457
458 let slot_patches = storage
459 .into_slots()
460 .into_iter()
461 .map(StorageSlot::into_parts)
462 .map(|(slot_name, slot_content)| (slot_name, StorageSlotPatch::from(slot_content)))
463 .collect();
464 let storage_patch = AccountStoragePatch::from_raw(slot_patches)
467 .expect("number of slot patches is bounded by the account's storage slots");
468
469 let mut vault_patch = AccountVaultPatch::default();
470 for asset in vault.assets() {
471 vault_patch.insert_asset(asset);
472 }
473
474 let patch = AccountPatch::new(id, storage_patch, vault_patch, Some(code), Some(nonce))
479 .expect("non-seeded account should yield a valid patch");
480
481 Ok(patch)
482 }
483}
484
485impl SequentialCommit for Account {
486 type Commitment = Word;
487
488 fn to_elements(&self) -> Vec<Felt> {
489 AccountHeader::from(self).to_elements()
490 }
491
492 fn to_commitment(&self) -> Self::Commitment {
493 AccountHeader::from(self).to_commitment()
494 }
495}
496
497impl Serializable for Account {
501 fn write_into<W: ByteWriter>(&self, target: &mut W) {
502 let Account { id, vault, storage, code, nonce, seed } = self;
503
504 AccountHeader::VERSION_1.write_into(target);
505 id.write_into(target);
506 vault.write_into(target);
507 storage.write_into(target);
508 code.write_into(target);
509 nonce.write_into(target);
510 seed.write_into(target);
511 }
512
513 fn get_size_hint(&self) -> usize {
514 AccountHeader::VERSION_1.get_size_hint()
515 + self.id.get_size_hint()
516 + self.vault.get_size_hint()
517 + self.storage.get_size_hint()
518 + self.code.get_size_hint()
519 + self.nonce.get_size_hint()
520 + self.seed.get_size_hint()
521 }
522}
523
524impl Deserializable for Account {
525 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
526 let version = u8::read_from(source)?;
527
528 if version != AccountHeader::VERSION_1 {
529 return Err(DeserializationError::InvalidValue(format!(
530 "account version is {} but only version {} is supported",
531 version,
532 AccountHeader::VERSION_1,
533 )));
534 }
535
536 let id = AccountId::read_from(source)?;
537 let vault = AssetVault::read_from(source)?;
538 let storage = AccountStorage::read_from(source)?;
539 let code = AccountCode::read_from(source)?;
540 let nonce = Felt::read_from(source)?;
541 let seed = <Option<Word>>::read_from(source)?;
542
543 Self::new(id, vault, storage, code, nonce, seed)
544 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
545 }
546}
547
548pub(super) fn validate_asset_callbacks(
557 id: AccountId,
558 storage: &AccountStorage,
559) -> Result<(), AccountError> {
560 if !id.asset_callback_flag().is_enabled() && storage.has_callback_slots() {
561 return Err(AccountError::AssetCallbackSlotWithDisabledFlag(id));
562 }
563
564 Ok(())
565}
566
567pub(super) fn validate_account_seed(
569 id: AccountId,
570 code_commitment: Word,
571 storage_commitment: Word,
572 seed: Option<Word>,
573 nonce: Felt,
574) -> Result<(), AccountError> {
575 let account_is_new = nonce == ZERO;
576
577 match (account_is_new, seed) {
578 (true, Some(seed)) => {
579 let account_id =
580 AccountId::new(seed, id.version(), code_commitment, storage_commitment)
581 .map_err(AccountError::SeedConvertsToInvalidAccountId)?;
582
583 if account_id != id {
584 return Err(AccountError::AccountIdSeedMismatch {
585 expected: id,
586 actual: account_id,
587 });
588 }
589
590 Ok(())
591 },
592 (true, None) => Err(AccountError::NewAccountMissingSeed),
593 (false, Some(_)) => Err(AccountError::ExistingAccountWithSeed),
594 (false, None) => Ok(()),
595 }
596}
597
598#[cfg(test)]
602mod tests {
603 use alloc::vec::Vec;
604
605 use assert_matches::assert_matches;
606 use miden_crypto::utils::{Deserializable, DeserializationError, Serializable};
607 use miden_crypto::{Felt, Word};
608
609 use super::{AccountCode, AccountDelta, AccountId, AccountStorage, AccountStoragePatch};
610 use crate::account::{
611 Account,
612 AccountBuilder,
613 AccountIdVersion,
614 AccountPatch,
615 AccountType,
616 AccountVaultDelta,
617 AccountVaultPatch,
618 AssetCallbackFlag,
619 PartialAccount,
620 StorageMap,
621 StorageMapKey,
622 StorageSlot,
623 StorageSlotContent,
624 StorageSlotName,
625 };
626 use crate::asset::{Asset, AssetCallbacks, AssetVault, FungibleAsset, NonFungibleAsset};
627 use crate::errors::AccountError;
628 use crate::testing::account_id::{
629 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
630 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2,
631 };
632 use crate::testing::add_component::AddComponent;
633 use crate::testing::noop_auth_component::NoopAuthComponent;
634
635 #[test]
636 fn test_serde_account() {
637 let init_nonce = Felt::from(1_u32);
638 let asset_0 = FungibleAsset::mock(99);
639 let word = Word::from([1, 2, 3, 4u32]);
640 let storage_slot = StorageSlotContent::Value(word);
641 let account = build_account(vec![asset_0], init_nonce, vec![storage_slot]);
642
643 let serialized = account.to_bytes();
644 let deserialized = Account::read_from_bytes(&serialized).unwrap();
645 assert_eq!(deserialized, account);
646 }
647
648 #[test]
649 fn test_serde_account_delta() {
650 let nonce_delta = Felt::from(2_u32);
651 let asset_0 = FungibleAsset::mock(15);
652 let asset_1 = NonFungibleAsset::mock(&[5, 5, 5]);
653 let storage_patch = AccountStoragePatch::builder()
654 .update_value(StorageSlotName::mock(0), Word::empty())
655 .update_value(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))
656 .build();
657 let account_delta =
658 build_account_delta(vec![asset_1], vec![asset_0], nonce_delta, storage_patch);
659
660 let serialized = account_delta.to_bytes();
661 let deserialized = AccountDelta::read_from_bytes(&serialized).unwrap();
662 assert_eq!(deserialized, account_delta);
663 }
664
665 #[test]
666 fn account_patch_is_correctly_applied() -> anyhow::Result<()> {
667 let init_nonce = Felt::from(1_u32);
668 let asset_0 = FungibleAsset::mock(100);
669 let asset_1 = NonFungibleAsset::mock(&[1, 2, 3]);
670
671 let storage_slot_value_0 = StorageSlotContent::Value(Word::from([1, 2, 3, 4u32]));
673 let storage_slot_value_1 = StorageSlotContent::Value(Word::from([5, 6, 7, 8u32]));
674 let map_key_0 = StorageMapKey::from_array([101, 102, 103, 104]);
675 let map_key_1 = StorageMapKey::from_array([105, 106, 107, 108]);
676
677 let mut storage_map = StorageMap::with_entries([
678 (map_key_0, Word::from([1, 2, 3, 4_u32])),
679 (map_key_1, Word::from([5, 6, 7, 8_u32])),
680 ])
681 .unwrap();
682 let storage_slot_map = StorageSlotContent::Map(storage_map.clone());
683
684 let initial_account = build_account(
686 vec![asset_0],
687 init_nonce,
688 vec![storage_slot_value_0, storage_slot_value_1, storage_slot_map],
689 );
690
691 let value = Word::from([9, 10, 11, 12u32]);
692 storage_map.insert(map_key_0, value).unwrap();
693
694 let final_nonce = init_nonce + Felt::ONE;
696 let storage_patch = AccountStoragePatch::builder()
697 .update_value(StorageSlotName::mock(0), Word::empty())
698 .update_value(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))
699 .update_map(StorageSlotName::mock(2), [(map_key_0, value)])
700 .build();
701 let account_patch =
702 build_account_patch(final_nonce, vec![asset_1], vec![asset_0], storage_patch);
703
704 let mut account_with_patched = initial_account;
706
707 account_with_patched.apply_patch(&account_patch)?;
708
709 let final_account = build_account(
710 vec![asset_1],
711 final_nonce,
712 vec![
713 StorageSlotContent::Value(Word::empty()),
714 StorageSlotContent::Value(Word::from([1, 2, 3, 4u32])),
715 StorageSlotContent::Map(storage_map),
716 ],
717 );
718
719 assert_eq!(account_with_patched, final_account);
720
721 Ok(())
722 }
723
724 #[test]
725 fn apply_patch_rejects_new_account_patch() -> anyhow::Result<()> {
726 let account_id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE)?;
727 let init_nonce = Felt::from(1_u32);
728 let mut account = build_account(vec![], init_nonce, vec![]);
729
730 let patch = AccountPatch::new(
731 account_id,
732 AccountStoragePatch::new(),
733 AccountVaultPatch::default(),
734 Some(AccountCode::mock()),
735 Some(Felt::from(2_u32)),
736 )?;
737
738 let err = account.apply_patch(&patch).unwrap_err();
739 assert_matches!(err, AccountError::ApplyFullStatePatchToAccount);
740
741 Ok(())
742 }
743
744 #[test]
745 fn apply_patch_rejects_non_increasing_nonce() -> anyhow::Result<()> {
746 let account_id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE)?;
747 let init_nonce = 5_u32;
748 let mut account = build_account(vec![], Felt::from(init_nonce), vec![]);
749
750 let patch_smaller = AccountPatch::new(
752 account_id,
753 AccountStoragePatch::new(),
754 AccountVaultPatch::default(),
755 None,
756 Some(Felt::from(init_nonce - 1)),
757 )?;
758 let err = account.apply_patch(&patch_smaller).unwrap_err();
759 assert_matches!(err, AccountError::NonceMustIncrease { .. });
760
761 Ok(())
762 }
763
764 #[test]
765 fn apply_patch_rejects_id_mismatch() -> anyhow::Result<()> {
766 let other_account_id =
767 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2)?;
768 let init_nonce = Felt::from(1_u32);
769 let mut account = build_account(vec![], init_nonce, vec![]);
770
771 let patch = AccountPatch::new(
772 other_account_id,
773 AccountStoragePatch::default(),
774 AccountVaultPatch::default(),
775 None,
776 Some(Felt::from(2_u32)),
777 )?;
778
779 let err = account.apply_patch(&patch).unwrap_err();
780 assert_matches!(err, AccountError::PatchAccountIdMismatch { .. });
781
782 Ok(())
783 }
784
785 #[test]
786 fn apply_empty_account_patch() -> anyhow::Result<()> {
787 let nonce = Felt::from(2u8);
788 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
789 let empty_patch = AccountPatch::new(
790 id,
791 AccountStoragePatch::default(),
792 AccountVaultPatch::default(),
793 None,
794 None,
795 )?;
796 let init_account = build_account(vec![], nonce, vec![]);
797
798 let mut account_with_patch = init_account.clone();
799 account_with_patch.apply_patch(&empty_patch)?;
800
801 assert_eq!(init_account, account_with_patch, "account should be unchanged");
802
803 Ok(())
804 }
805
806 #[test]
807 fn apply_empty_account_patch_with_incremented_nonce() -> anyhow::Result<()> {
808 let initial_nonce = Felt::from(2u8);
809 let final_nonce = initial_nonce + Felt::ONE;
810
811 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
812 let empty_patch = AccountPatch::new(
813 id,
814 AccountStoragePatch::default(),
815 AccountVaultPatch::default(),
816 None,
817 Some(final_nonce),
818 )?;
819
820 let init_account = build_account(vec![], initial_nonce, vec![]);
821 let final_account = build_account(vec![], final_nonce, vec![]);
822
823 let mut account_with_patch = init_account.clone();
824 account_with_patch.apply_patch(&empty_patch)?;
825
826 assert_eq!(final_account, account_with_patch);
827
828 Ok(())
829 }
830
831 pub fn build_account_delta(
832 added_assets: Vec<Asset>,
833 removed_assets: Vec<Asset>,
834 nonce_delta: Felt,
835 storage_patch: AccountStoragePatch,
836 ) -> AccountDelta {
837 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
838 let vault_delta = AccountVaultDelta::from_iters(added_assets, removed_assets);
839 AccountDelta::new(id, storage_patch, vault_delta, None, nonce_delta).unwrap()
840 }
841
842 pub fn build_account_patch(
843 final_nonce: Felt,
844 added_assets: Vec<Asset>,
845 removed_assets: Vec<Asset>,
846 storage_patch: AccountStoragePatch,
847 ) -> AccountPatch {
848 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
849 let vault_patch = AccountVaultPatch::from_iters(added_assets, removed_assets);
850 AccountPatch::new(id, storage_patch, vault_patch, None, Some(final_nonce)).unwrap()
851 }
852
853 pub fn build_account(
854 assets: Vec<Asset>,
855 nonce: Felt,
856 slots: Vec<StorageSlotContent>,
857 ) -> Account {
858 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
859 let code = AccountCode::mock();
860
861 let vault = AssetVault::new(&assets).unwrap();
862
863 let slots = slots
864 .into_iter()
865 .enumerate()
866 .map(|(idx, slot)| StorageSlot::new(StorageSlotName::mock(idx), slot))
867 .collect();
868
869 let storage = AccountStorage::new(slots).unwrap();
870
871 Account::new_existing(id, vault, storage, code, nonce)
872 }
873
874 #[test]
877 fn account_new_rejects_callback_slot_with_disabled_flag() -> anyhow::Result<()> {
878 let account = AccountBuilder::new([5; 32])
879 .with_component(NoopAuthComponent)
880 .with_component(AddComponent)
881 .build_existing()?;
882 assert_eq!(account.id().asset_callback_flag(), AssetCallbackFlag::Disabled);
883
884 let (id, vault, storage, code, nonce, _seed) = account.into_parts();
885
886 let mut slots = storage.into_slots();
887 slots.push(StorageSlot::with_value(
888 AssetCallbacks::on_before_asset_added_to_account_slot().clone(),
889 Word::from([1u32, 2, 3, 4]),
890 ));
891 let storage = AccountStorage::new(slots)?;
892
893 let err = Account::new(id, vault, storage, code, nonce, None).unwrap_err();
894 assert_matches!(err, AccountError::AssetCallbackSlotWithDisabledFlag(_));
895
896 Ok(())
897 }
898
899 #[test]
901 fn seed_validation() -> anyhow::Result<()> {
902 let account = AccountBuilder::new([5; 32])
903 .with_component(NoopAuthComponent)
904 .with_component(AddComponent)
905 .build()?;
906 let (id, vault, storage, code, _nonce, seed) = account.into_parts();
907 assert!(seed.is_some());
908
909 let other_seed = AccountId::compute_account_seed(
910 [9; 32],
911 AccountType::Public,
912 AssetCallbackFlag::Disabled,
913 AccountIdVersion::Version1,
914 code.commitment(),
915 storage.to_commitment(),
916 )?;
917
918 let err = Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ONE, seed)
920 .unwrap_err();
921 assert_matches!(err, AccountError::ExistingAccountWithSeed);
922
923 let err = Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ZERO, None)
925 .unwrap_err();
926 assert_matches!(err, AccountError::NewAccountMissingSeed);
927
928 let err = Account::new(
931 id,
932 vault.clone(),
933 storage.clone(),
934 code.clone(),
935 Felt::ZERO,
936 Some(other_seed),
937 )
938 .unwrap_err();
939 assert_matches!(err, AccountError::AccountIdSeedMismatch { .. });
940
941 let err = Account::new(
944 id,
945 vault.clone(),
946 storage.clone(),
947 code.clone(),
948 Felt::ZERO,
949 Some(Word::from([1, 2, 3, 4u32])),
950 )
951 .unwrap_err();
952 assert_matches!(err, AccountError::SeedConvertsToInvalidAccountId(_));
953
954 Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ONE, None)?;
957
958 Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ZERO, seed)?;
961
962 Ok(())
963 }
964
965 #[test]
966 fn incrementing_nonce_should_remove_seed() -> anyhow::Result<()> {
967 let mut account = AccountBuilder::new([5; 32])
968 .with_component(NoopAuthComponent)
969 .with_component(AddComponent)
970 .build()?;
971 account.increment_nonce(Felt::ONE)?;
972
973 assert_matches!(account.seed(), None);
974
975 let _partial_account = PartialAccount::from(&account);
978
979 Ok(())
980 }
981
982 #[test]
983 fn account_deserialization_rejects_unsupported_version() {
984 let error = Account::read_from_bytes(&[0]).unwrap_err();
985
986 assert_matches!(error, DeserializationError::InvalidValue(message) => {
987 assert!(message.contains("account version is 0"));
988 });
989 }
990}