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::procedure::AccountProcedureRoot;
40pub use code::{AccountCode, AccountCodeUpgrade};
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 AccountCodePatch,
52 AccountPatch,
53 AccountStoragePatch,
54 AccountUpdateDetails,
55 AccountVaultPatch,
56 StorageMapPatch,
57 StorageMapPatchEntries,
58 StoragePatchOperation,
59 StorageSlotPatch,
60 StorageValuePatch,
61};
62
63pub mod delta;
64pub use delta::{AccountDelta, AccountVaultDelta, AssetDelta};
65
66pub mod storage;
67pub use storage::{
68 AccountStorage,
69 AccountStorageHeader,
70 PartialStorage,
71 PartialStorageMap,
72 StorageMap,
73 StorageMapKey,
74 StorageMapKeyHash,
75 StorageMapWitness,
76 StorageSlot,
77 StorageSlotContent,
78 StorageSlotHeader,
79 StorageSlotId,
80 StorageSlotName,
81 StorageSlotType,
82};
83
84mod header;
85pub use header::AccountHeader;
86
87mod partial;
88pub use partial::PartialAccount;
89
90#[derive(Debug, Clone, PartialEq, Eq)]
112pub struct Account {
113 id: AccountId,
114 vault: AssetVault,
115 storage: AccountStorage,
116 code: AccountCode,
117 nonce: Felt,
118 seed: Option<Word>,
119}
120
121impl Account {
122 pub fn new(
137 id: AccountId,
138 vault: AssetVault,
139 storage: AccountStorage,
140 code: AccountCode,
141 nonce: Felt,
142 seed: Option<Word>,
143 ) -> Result<Self, AccountError> {
144 validate_account_seed(id, code.commitment(), storage.to_commitment(), seed, nonce)?;
145 validate_asset_callbacks(id, &storage)?;
146
147 Ok(Self::new_unchecked(id, vault, storage, code, nonce, seed))
148 }
149
150 pub fn new_unchecked(
157 id: AccountId,
158 vault: AssetVault,
159 storage: AccountStorage,
160 code: AccountCode,
161 nonce: Felt,
162 seed: Option<Word>,
163 ) -> Self {
164 Self { id, vault, storage, code, nonce, seed }
165 }
166
167 pub(super) fn initialize_from_components(
189 components: Vec<AccountComponent>,
190 ) -> Result<(AccountCode, AccountStorage), AccountError> {
191 let code = AccountCode::from_components_unchecked(&components)?;
192 let storage = AccountStorage::from_components(components)?;
193
194 Ok((code, storage))
195 }
196
197 pub fn builder(init_seed: [u8; 32]) -> AccountBuilder {
202 AccountBuilder::new(init_seed)
203 }
204
205 pub fn to_header(&self) -> AccountHeader {
210 AccountHeader::from(self)
211 }
212
213 pub fn to_commitment(&self) -> Word {
217 AccountHeader::from(self).to_commitment()
218 }
219
220 pub fn initial_commitment(&self) -> Word {
230 if self.is_new() {
231 Word::empty()
232 } else {
233 self.to_commitment()
234 }
235 }
236
237 pub fn id(&self) -> AccountId {
239 self.id
240 }
241
242 pub fn vault(&self) -> &AssetVault {
244 &self.vault
245 }
246
247 pub fn storage(&self) -> &AccountStorage {
249 &self.storage
250 }
251
252 pub fn code(&self) -> &AccountCode {
254 &self.code
255 }
256
257 pub fn code_interface(&self) -> AccountCodeInterface {
260 self.code.interface(self.id())
261 }
262
263 pub fn nonce(&self) -> Felt {
265 self.nonce
266 }
267
268 pub fn seed(&self) -> Option<Word> {
272 self.seed
273 }
274
275 pub fn is_public(&self) -> bool {
277 self.id().is_public()
278 }
279
280 pub fn is_private(&self) -> bool {
282 self.id().is_private()
283 }
284
285 pub fn is_new(&self) -> bool {
290 self.nonce == ZERO
291 }
292
293 pub fn into_parts(
295 self,
296 ) -> (AccountId, AssetVault, AccountStorage, AccountCode, Felt, Option<Word>) {
297 (self.id, self.vault, self.storage, self.code, self.nonce, self.seed)
298 }
299
300 pub fn apply_patch(&mut self, patch: &AccountPatch) -> Result<(), AccountError> {
315 if patch.id() != self.id {
316 return Err(AccountError::PatchAccountIdMismatch {
317 account_id: self.id,
318 patch_id: patch.id(),
319 });
320 }
321
322 self.vault
323 .apply_patch(patch.vault())
324 .map_err(AccountError::AssetVaultUpdateError)?;
325
326 self.storage.apply_patch(patch.storage())?;
327
328 if let Some(new_nonce) = patch.final_nonce() {
329 self.set_nonce(new_nonce)?;
330 }
331
332 if let Some(code) = patch.code().as_code() {
334 self.code = code.clone();
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(
433 id,
434 storage_patch,
435 vault_delta,
436 AccountCodePatch::new(Some(code)),
437 nonce_delta,
438 )
439 .expect("delta from an account with a non-zero nonce should be valid");
440
441 Ok(delta)
442 }
443}
444
445impl TryFrom<Account> for AccountPatch {
446 type Error = AccountError;
447
448 fn try_from(account: Account) -> Result<Self, Self::Error> {
457 let Account { id, vault, storage, code, nonce, seed } = account;
458
459 if seed.is_some() {
460 return Err(AccountError::PatchFromAccountWithSeed);
461 }
462
463 let slot_patches = storage
464 .into_slots()
465 .into_iter()
466 .map(StorageSlot::into_parts)
467 .map(|(slot_name, slot_content)| (slot_name, StorageSlotPatch::from(slot_content)))
468 .collect();
469 let storage_patch = AccountStoragePatch::from_raw(slot_patches)
472 .expect("number of slot patches is bounded by the account's storage slots");
473
474 let mut vault_patch = AccountVaultPatch::default();
475 for asset in vault.assets() {
476 vault_patch.insert_asset(asset);
477 }
478
479 let patch = AccountPatch::new(
484 id,
485 storage_patch,
486 vault_patch,
487 AccountCodePatch::new(Some(code)),
488 Some(nonce),
489 )
490 .expect("non-seeded account should yield a valid patch");
491
492 Ok(patch)
493 }
494}
495
496impl SequentialCommit for Account {
497 type Commitment = Word;
498
499 fn to_elements(&self) -> Vec<Felt> {
500 AccountHeader::from(self).to_elements()
501 }
502
503 fn to_commitment(&self) -> Self::Commitment {
504 AccountHeader::from(self).to_commitment()
505 }
506}
507
508impl Serializable for Account {
512 fn write_into<W: ByteWriter>(&self, target: &mut W) {
513 let Account { id, vault, storage, code, nonce, seed } = self;
514
515 AccountHeader::VERSION_1.write_into(target);
516 id.write_into(target);
517 vault.write_into(target);
518 storage.write_into(target);
519 code.write_into(target);
520 nonce.write_into(target);
521 seed.write_into(target);
522 }
523
524 fn get_size_hint(&self) -> usize {
525 AccountHeader::VERSION_1.get_size_hint()
526 + self.id.get_size_hint()
527 + self.vault.get_size_hint()
528 + self.storage.get_size_hint()
529 + self.code.get_size_hint()
530 + self.nonce.get_size_hint()
531 + self.seed.get_size_hint()
532 }
533}
534
535impl Deserializable for Account {
536 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
537 let version = u8::read_from(source)?;
538
539 if version != AccountHeader::VERSION_1 {
540 return Err(DeserializationError::InvalidValue(format!(
541 "account version is {} but only version {} is supported",
542 version,
543 AccountHeader::VERSION_1,
544 )));
545 }
546
547 let id = AccountId::read_from(source)?;
548 let vault = AssetVault::read_from(source)?;
549 let storage = AccountStorage::read_from(source)?;
550 let code = AccountCode::read_from(source)?;
551 let nonce = Felt::read_from(source)?;
552 let seed = <Option<Word>>::read_from(source)?;
553
554 Self::new(id, vault, storage, code, nonce, seed)
555 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
556 }
557}
558
559pub(super) fn validate_asset_callbacks(
568 id: AccountId,
569 storage: &AccountStorage,
570) -> Result<(), AccountError> {
571 if !id.asset_callback_flag().is_enabled() && storage.has_callback_slots() {
572 return Err(AccountError::AssetCallbackSlotWithDisabledFlag(id));
573 }
574
575 Ok(())
576}
577
578pub(super) fn validate_account_seed(
580 id: AccountId,
581 code_commitment: Word,
582 storage_commitment: Word,
583 seed: Option<Word>,
584 nonce: Felt,
585) -> Result<(), AccountError> {
586 let account_is_new = nonce == ZERO;
587
588 match (account_is_new, seed) {
589 (true, Some(seed)) => {
590 let account_id =
591 AccountId::new(seed, id.version(), code_commitment, storage_commitment)
592 .map_err(AccountError::SeedConvertsToInvalidAccountId)?;
593
594 if account_id != id {
595 return Err(AccountError::AccountIdSeedMismatch {
596 expected: id,
597 actual: account_id,
598 });
599 }
600
601 Ok(())
602 },
603 (true, None) => Err(AccountError::NewAccountMissingSeed),
604 (false, Some(_)) => Err(AccountError::ExistingAccountWithSeed),
605 (false, None) => Ok(()),
606 }
607}
608
609#[cfg(test)]
613mod tests {
614 use alloc::vec::Vec;
615
616 use assert_matches::assert_matches;
617 use miden_crypto::utils::{Deserializable, DeserializationError, Serializable};
618 use miden_crypto::{Felt, Word};
619
620 use super::{AccountCode, AccountDelta, AccountId, AccountStorage, AccountStoragePatch};
621 use crate::account::{
622 Account,
623 AccountBuilder,
624 AccountCodePatch,
625 AccountIdVersion,
626 AccountPatch,
627 AccountType,
628 AccountVaultDelta,
629 AccountVaultPatch,
630 AssetCallbackFlag,
631 PartialAccount,
632 StorageMap,
633 StorageMapKey,
634 StorageSlot,
635 StorageSlotContent,
636 StorageSlotName,
637 };
638 use crate::asset::{Asset, AssetCallbacks, AssetVault, FungibleAsset, NonFungibleAsset};
639 use crate::errors::AccountError;
640 use crate::testing::account_id::{
641 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
642 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2,
643 };
644 use crate::testing::add_component::AddComponent;
645 use crate::testing::noop_auth_component::NoopAuthComponent;
646
647 #[test]
648 fn test_serde_account() {
649 let init_nonce = Felt::from(1_u32);
650 let asset_0 = FungibleAsset::mock(99);
651 let word = Word::from([1, 2, 3, 4u32]);
652 let storage_slot = StorageSlotContent::Value(word);
653 let account = build_account(vec![asset_0], init_nonce, vec![storage_slot]);
654
655 let serialized = account.to_bytes();
656 let deserialized = Account::read_from_bytes(&serialized).unwrap();
657 assert_eq!(deserialized, account);
658 }
659
660 #[test]
661 fn test_serde_account_delta() {
662 let nonce_delta = Felt::from(2_u32);
663 let asset_0 = FungibleAsset::mock(15);
664 let asset_1 = NonFungibleAsset::mock(&[5, 5, 5]);
665 let storage_patch = AccountStoragePatch::builder()
666 .update_value(StorageSlotName::mock(0), Word::empty())
667 .update_value(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))
668 .build();
669 let account_delta =
670 build_account_delta(vec![asset_1], vec![asset_0], nonce_delta, storage_patch);
671
672 let serialized = account_delta.to_bytes();
673 let deserialized = AccountDelta::read_from_bytes(&serialized).unwrap();
674 assert_eq!(deserialized, account_delta);
675 }
676
677 #[test]
678 fn account_patch_is_correctly_applied() -> anyhow::Result<()> {
679 let init_nonce = Felt::from(1_u32);
680 let asset_0 = FungibleAsset::mock(100);
681 let asset_1 = NonFungibleAsset::mock(&[1, 2, 3]);
682
683 let storage_slot_value_0 = StorageSlotContent::Value(Word::from([1, 2, 3, 4u32]));
685 let storage_slot_value_1 = StorageSlotContent::Value(Word::from([5, 6, 7, 8u32]));
686 let map_key_0 = StorageMapKey::from_array([101, 102, 103, 104]);
687 let map_key_1 = StorageMapKey::from_array([105, 106, 107, 108]);
688
689 let mut storage_map = StorageMap::with_entries([
690 (map_key_0, Word::from([1, 2, 3, 4_u32])),
691 (map_key_1, Word::from([5, 6, 7, 8_u32])),
692 ])
693 .unwrap();
694 let storage_slot_map = StorageSlotContent::Map(storage_map.clone());
695
696 let initial_account = build_account(
698 vec![asset_0],
699 init_nonce,
700 vec![storage_slot_value_0, storage_slot_value_1, storage_slot_map],
701 );
702
703 let value = Word::from([9, 10, 11, 12u32]);
704 storage_map.insert(map_key_0, value).unwrap();
705
706 let final_nonce = init_nonce + Felt::ONE;
708 let storage_patch = AccountStoragePatch::builder()
709 .update_value(StorageSlotName::mock(0), Word::empty())
710 .update_value(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))
711 .update_map(StorageSlotName::mock(2), [(map_key_0, value)])
712 .build();
713 let account_patch =
714 build_account_patch(final_nonce, vec![asset_1], vec![asset_0], storage_patch);
715
716 let mut account_with_patched = initial_account;
718
719 account_with_patched.apply_patch(&account_patch)?;
720
721 let final_account = build_account(
722 vec![asset_1],
723 final_nonce,
724 vec![
725 StorageSlotContent::Value(Word::empty()),
726 StorageSlotContent::Value(Word::from([1, 2, 3, 4u32])),
727 StorageSlotContent::Map(storage_map),
728 ],
729 );
730
731 assert_eq!(account_with_patched, final_account);
732
733 Ok(())
734 }
735
736 #[test]
737 fn apply_patch_replaces_code() -> anyhow::Result<()> {
738 let account_id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE)?;
739 let init_nonce = Felt::from(1_u32);
740 let mut account = build_account(vec![], init_nonce, vec![]);
741 let new_code =
742 AccountCode::from_components(&[NoopAuthComponent.into(), AddComponent.into()])?;
743 assert_ne!(account.code(), &new_code);
744
745 let patch = AccountPatch::new(
746 account_id,
747 AccountStoragePatch::new(),
748 AccountVaultPatch::default(),
749 AccountCodePatch::new(Some(new_code.clone())),
750 Some(Felt::from(2_u32)),
751 )?;
752
753 account.apply_patch(&patch)?;
754 assert_eq!(account.code(), &new_code);
755 assert_eq!(account.nonce(), Felt::from(2_u32));
756
757 Ok(())
758 }
759
760 #[test]
761 fn apply_patch_rejects_non_increasing_nonce() -> anyhow::Result<()> {
762 let account_id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE)?;
763 let init_nonce = 5_u32;
764 let mut account = build_account(vec![], Felt::from(init_nonce), vec![]);
765
766 let patch_smaller = AccountPatch::new(
768 account_id,
769 AccountStoragePatch::new(),
770 AccountVaultPatch::default(),
771 AccountCodePatch::default(),
772 Some(Felt::from(init_nonce - 1)),
773 )?;
774 let err = account.apply_patch(&patch_smaller).unwrap_err();
775 assert_matches!(err, AccountError::NonceMustIncrease { .. });
776
777 Ok(())
778 }
779
780 #[test]
781 fn apply_patch_rejects_id_mismatch() -> anyhow::Result<()> {
782 let other_account_id =
783 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2)?;
784 let init_nonce = Felt::from(1_u32);
785 let mut account = build_account(vec![], init_nonce, vec![]);
786
787 let patch = AccountPatch::new(
788 other_account_id,
789 AccountStoragePatch::default(),
790 AccountVaultPatch::default(),
791 AccountCodePatch::default(),
792 Some(Felt::from(2_u32)),
793 )?;
794
795 let err = account.apply_patch(&patch).unwrap_err();
796 assert_matches!(err, AccountError::PatchAccountIdMismatch { .. });
797
798 Ok(())
799 }
800
801 #[test]
802 fn apply_empty_account_patch() -> anyhow::Result<()> {
803 let nonce = Felt::from(2u8);
804 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
805 let empty_patch = AccountPatch::new(
806 id,
807 AccountStoragePatch::default(),
808 AccountVaultPatch::default(),
809 AccountCodePatch::default(),
810 None,
811 )?;
812 let init_account = build_account(vec![], nonce, vec![]);
813
814 let mut account_with_patch = init_account.clone();
815 account_with_patch.apply_patch(&empty_patch)?;
816
817 assert_eq!(init_account, account_with_patch, "account should be unchanged");
818
819 Ok(())
820 }
821
822 #[test]
823 fn apply_empty_account_patch_with_incremented_nonce() -> anyhow::Result<()> {
824 let initial_nonce = Felt::from(2u8);
825 let final_nonce = initial_nonce + Felt::ONE;
826
827 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
828 let empty_patch = AccountPatch::new(
829 id,
830 AccountStoragePatch::default(),
831 AccountVaultPatch::default(),
832 AccountCodePatch::default(),
833 Some(final_nonce),
834 )?;
835
836 let init_account = build_account(vec![], initial_nonce, vec![]);
837 let final_account = build_account(vec![], final_nonce, vec![]);
838
839 let mut account_with_patch = init_account.clone();
840 account_with_patch.apply_patch(&empty_patch)?;
841
842 assert_eq!(final_account, account_with_patch);
843
844 Ok(())
845 }
846
847 pub fn build_account_delta(
848 added_assets: Vec<Asset>,
849 removed_assets: Vec<Asset>,
850 nonce_delta: Felt,
851 storage_patch: AccountStoragePatch,
852 ) -> AccountDelta {
853 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
854 let vault_delta = AccountVaultDelta::from_iters(added_assets, removed_assets);
855 AccountDelta::new(id, storage_patch, vault_delta, AccountCodePatch::default(), nonce_delta)
856 .unwrap()
857 }
858
859 pub fn build_account_patch(
860 final_nonce: Felt,
861 added_assets: Vec<Asset>,
862 removed_assets: Vec<Asset>,
863 storage_patch: AccountStoragePatch,
864 ) -> AccountPatch {
865 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
866 let vault_patch = AccountVaultPatch::from_iters(added_assets, removed_assets);
867 AccountPatch::new(
868 id,
869 storage_patch,
870 vault_patch,
871 AccountCodePatch::default(),
872 Some(final_nonce),
873 )
874 .unwrap()
875 }
876
877 pub fn build_account(
878 assets: Vec<Asset>,
879 nonce: Felt,
880 slots: Vec<StorageSlotContent>,
881 ) -> Account {
882 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
883 let code = AccountCode::mock();
884
885 let vault = AssetVault::new(&assets).unwrap();
886
887 let slots = slots
888 .into_iter()
889 .enumerate()
890 .map(|(idx, slot)| StorageSlot::new(StorageSlotName::mock(idx), slot))
891 .collect();
892
893 let storage = AccountStorage::new(slots).unwrap();
894
895 Account::new_existing(id, vault, storage, code, nonce)
896 }
897
898 #[test]
901 fn account_new_rejects_callback_slot_with_disabled_flag() -> anyhow::Result<()> {
902 let account = AccountBuilder::new([5; 32])
903 .with_component(NoopAuthComponent)
904 .with_component(AddComponent)
905 .build_existing()?;
906 assert_eq!(account.id().asset_callback_flag(), AssetCallbackFlag::Disabled);
907
908 let (id, vault, storage, code, nonce, _seed) = account.into_parts();
909
910 let mut slots = storage.into_slots();
911 slots.push(StorageSlot::with_value(
912 AssetCallbacks::on_before_asset_added_to_account_slot().clone(),
913 Word::from([1u32, 2, 3, 4]),
914 ));
915 let storage = AccountStorage::new(slots)?;
916
917 let err = Account::new(id, vault, storage, code, nonce, None).unwrap_err();
918 assert_matches!(err, AccountError::AssetCallbackSlotWithDisabledFlag(_));
919
920 Ok(())
921 }
922
923 #[test]
925 fn seed_validation() -> anyhow::Result<()> {
926 let account = AccountBuilder::new([5; 32])
927 .with_component(NoopAuthComponent)
928 .with_component(AddComponent)
929 .build()?;
930 let (id, vault, storage, code, _nonce, seed) = account.into_parts();
931 assert!(seed.is_some());
932
933 let other_seed = AccountId::compute_account_seed(
934 [9; 32],
935 AccountType::Public,
936 AssetCallbackFlag::Disabled,
937 AccountIdVersion::Version1,
938 code.commitment(),
939 storage.to_commitment(),
940 )?;
941
942 let err = Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ONE, seed)
944 .unwrap_err();
945 assert_matches!(err, AccountError::ExistingAccountWithSeed);
946
947 let err = Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ZERO, None)
949 .unwrap_err();
950 assert_matches!(err, AccountError::NewAccountMissingSeed);
951
952 let err = Account::new(
955 id,
956 vault.clone(),
957 storage.clone(),
958 code.clone(),
959 Felt::ZERO,
960 Some(other_seed),
961 )
962 .unwrap_err();
963 assert_matches!(err, AccountError::AccountIdSeedMismatch { .. });
964
965 let err = Account::new(
968 id,
969 vault.clone(),
970 storage.clone(),
971 code.clone(),
972 Felt::ZERO,
973 Some(Word::from([1, 2, 3, 4u32])),
974 )
975 .unwrap_err();
976 assert_matches!(err, AccountError::SeedConvertsToInvalidAccountId(_));
977
978 Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ONE, None)?;
981
982 Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ZERO, seed)?;
985
986 Ok(())
987 }
988
989 #[test]
990 fn incrementing_nonce_should_remove_seed() -> anyhow::Result<()> {
991 let mut account = AccountBuilder::new([5; 32])
992 .with_component(NoopAuthComponent)
993 .with_component(AddComponent)
994 .build()?;
995 account.increment_nonce(Felt::ONE)?;
996
997 assert_matches!(account.seed(), None);
998
999 let _partial_account = PartialAccount::from(&account);
1002
1003 Ok(())
1004 }
1005
1006 #[test]
1007 fn account_deserialization_rejects_unsupported_version() {
1008 let error = Account::read_from_bytes(&[0]).unwrap_err();
1009
1010 assert_matches!(error, DeserializationError::InvalidValue(message) => {
1011 assert!(message.contains("account version is 0"));
1012 });
1013 }
1014}