1use alloc::string::ToString;
2use alloc::vec::Vec;
3
4use crate::asset::{Asset, AssetVault};
5use crate::errors::AccountError;
6use crate::utils::serde::{
7 ByteReader,
8 ByteWriter,
9 Deserializable,
10 DeserializationError,
11 Serializable,
12};
13use crate::{Felt, Hasher, Word, ZERO};
14
15mod account_id;
16pub use account_id::{
17 AccountId,
18 AccountIdPrefix,
19 AccountIdPrefixV0,
20 AccountIdV0,
21 AccountIdVersion,
22 AccountStorageMode,
23 AccountType,
24};
25
26pub mod auth;
27
28mod builder;
29pub use builder::AccountBuilder;
30
31pub mod code;
32pub use code::AccountCode;
33pub use code::procedure::AccountProcedureRoot;
34
35pub mod component;
36pub use component::{AccountComponent, AccountComponentCode, AccountComponentMetadata};
37
38pub mod delta;
39pub use delta::{
40 AccountDelta,
41 AccountStorageDelta,
42 AccountVaultDelta,
43 FungibleAssetDelta,
44 NonFungibleAssetDelta,
45 NonFungibleDeltaAction,
46 StorageMapDelta,
47 StorageSlotDelta,
48};
49
50pub mod storage;
51pub use storage::{
52 AccountStorage,
53 AccountStorageHeader,
54 PartialStorage,
55 PartialStorageMap,
56 StorageMap,
57 StorageMapWitness,
58 StorageSlot,
59 StorageSlotContent,
60 StorageSlotHeader,
61 StorageSlotId,
62 StorageSlotName,
63 StorageSlotType,
64};
65
66mod header;
67pub use header::AccountHeader;
68
69mod file;
70pub use file::AccountFile;
71
72mod partial;
73pub use partial::PartialAccount;
74
75#[derive(Debug, Clone, PartialEq, Eq)]
97pub struct Account {
98 id: AccountId,
99 vault: AssetVault,
100 storage: AccountStorage,
101 code: AccountCode,
102 nonce: Felt,
103 seed: Option<Word>,
104}
105
106impl Account {
107 pub fn new(
120 id: AccountId,
121 vault: AssetVault,
122 storage: AccountStorage,
123 code: AccountCode,
124 nonce: Felt,
125 seed: Option<Word>,
126 ) -> Result<Self, AccountError> {
127 validate_account_seed(id, code.commitment(), storage.to_commitment(), seed, nonce)?;
128
129 Ok(Self::new_unchecked(id, vault, storage, code, nonce, seed))
130 }
131
132 pub fn new_unchecked(
139 id: AccountId,
140 vault: AssetVault,
141 storage: AccountStorage,
142 code: AccountCode,
143 nonce: Felt,
144 seed: Option<Word>,
145 ) -> Self {
146 Self { id, vault, storage, code, nonce, seed }
147 }
148
149 pub(super) fn initialize_from_components(
183 account_type: AccountType,
184 components: Vec<AccountComponent>,
185 ) -> Result<(AccountCode, AccountStorage), AccountError> {
186 validate_components_support_account_type(&components, account_type)?;
187
188 let code = AccountCode::from_components_unchecked(&components)?;
189 let storage = AccountStorage::from_components(components, account_type)?;
190
191 Ok((code, storage))
192 }
193
194 pub fn builder(init_seed: [u8; 32]) -> AccountBuilder {
199 AccountBuilder::new(init_seed)
200 }
201
202 pub fn commitment(&self) -> Word {
211 hash_account(
212 self.id,
213 self.nonce,
214 self.vault.root(),
215 self.storage.to_commitment(),
216 self.code.commitment(),
217 )
218 }
219
220 pub fn initial_commitment(&self) -> Word {
230 if self.is_new() {
231 Word::empty()
232 } else {
233 self.commitment()
234 }
235 }
236
237 pub fn id(&self) -> AccountId {
239 self.id
240 }
241
242 pub fn account_type(&self) -> AccountType {
244 self.id.account_type()
245 }
246
247 pub fn vault(&self) -> &AssetVault {
249 &self.vault
250 }
251
252 pub fn storage(&self) -> &AccountStorage {
254 &self.storage
255 }
256
257 pub fn code(&self) -> &AccountCode {
259 &self.code
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_faucet(&self) -> bool {
276 self.id.is_faucet()
277 }
278
279 pub fn is_regular_account(&self) -> bool {
281 self.id.is_regular_account()
282 }
283
284 pub fn has_public_state(&self) -> bool {
287 self.id().has_public_state()
288 }
289
290 pub fn is_public(&self) -> bool {
292 self.id().is_public()
293 }
294
295 pub fn is_private(&self) -> bool {
297 self.id().is_private()
298 }
299
300 pub fn is_network(&self) -> bool {
302 self.id().is_network()
303 }
304
305 pub fn is_new(&self) -> bool {
310 self.nonce == ZERO
311 }
312
313 pub fn into_parts(
315 self,
316 ) -> (AccountId, AssetVault, AccountStorage, AccountCode, Felt, Option<Word>) {
317 (self.id, self.vault, self.storage, self.code, self.nonce, self.seed)
318 }
319
320 pub fn apply_delta(&mut self, delta: &AccountDelta) -> Result<(), AccountError> {
336 if delta.is_full_state() {
337 return Err(AccountError::ApplyFullStateDeltaToAccount);
338 }
339
340 self.vault
343 .apply_delta(delta.vault())
344 .map_err(AccountError::AssetVaultUpdateError)?;
345
346 self.storage.apply_delta(delta.storage())?;
348
349 self.increment_nonce(delta.nonce_delta())?;
351
352 Ok(())
353 }
354
355 pub fn increment_nonce(&mut self, nonce_delta: Felt) -> Result<(), AccountError> {
362 let new_nonce = self.nonce + nonce_delta;
363
364 if new_nonce.as_int() < self.nonce.as_int() {
365 return Err(AccountError::NonceOverflow {
366 current: self.nonce,
367 increment: nonce_delta,
368 new: new_nonce,
369 });
370 }
371
372 self.nonce = new_nonce;
373
374 if !self.is_new() {
379 self.seed = None;
380 }
381
382 Ok(())
383 }
384
385 #[cfg(any(feature = "testing", test))]
389 pub fn vault_mut(&mut self) -> &mut AssetVault {
391 &mut self.vault
392 }
393
394 #[cfg(any(feature = "testing", test))]
395 pub fn storage_mut(&mut self) -> &mut AccountStorage {
397 &mut self.storage
398 }
399}
400
401impl TryFrom<Account> for AccountDelta {
402 type Error = AccountError;
403
404 fn try_from(account: Account) -> Result<Self, Self::Error> {
413 let Account { id, vault, storage, code, nonce, seed } = account;
414
415 if seed.is_some() {
416 return Err(AccountError::DeltaFromAccountWithSeed);
417 }
418
419 let slot_deltas = storage
420 .into_slots()
421 .into_iter()
422 .map(StorageSlot::into_parts)
423 .map(|(slot_name, slot_content)| (slot_name, StorageSlotDelta::from(slot_content)))
424 .collect();
425 let storage_delta = AccountStorageDelta::from_raw(slot_deltas);
426
427 let mut fungible_delta = FungibleAssetDelta::default();
428 let mut non_fungible_delta = NonFungibleAssetDelta::default();
429 for asset in vault.assets() {
430 match asset {
432 Asset::Fungible(fungible_asset) => {
433 fungible_delta
434 .add(fungible_asset)
435 .expect("delta should allow representing valid fungible assets");
436 },
437 Asset::NonFungible(non_fungible_asset) => {
438 non_fungible_delta
439 .add(non_fungible_asset)
440 .expect("delta should allow representing valid non-fungible assets");
441 },
442 }
443 }
444 let vault_delta = AccountVaultDelta::new(fungible_delta, non_fungible_delta);
445
446 let nonce_delta = nonce;
449
450 let delta = AccountDelta::new(id, storage_delta, vault_delta, nonce_delta)
453 .expect("nonce_delta should be greater than 0")
454 .with_code(Some(code));
455
456 Ok(delta)
457 }
458}
459
460impl Serializable for Account {
464 fn write_into<W: ByteWriter>(&self, target: &mut W) {
465 let Account { id, vault, storage, code, nonce, seed } = self;
466
467 id.write_into(target);
468 vault.write_into(target);
469 storage.write_into(target);
470 code.write_into(target);
471 nonce.write_into(target);
472 seed.write_into(target);
473 }
474
475 fn get_size_hint(&self) -> usize {
476 self.id.get_size_hint()
477 + self.vault.get_size_hint()
478 + self.storage.get_size_hint()
479 + self.code.get_size_hint()
480 + self.nonce.get_size_hint()
481 + self.seed.get_size_hint()
482 }
483}
484
485impl Deserializable for Account {
486 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
487 let id = AccountId::read_from(source)?;
488 let vault = AssetVault::read_from(source)?;
489 let storage = AccountStorage::read_from(source)?;
490 let code = AccountCode::read_from(source)?;
491 let nonce = Felt::read_from(source)?;
492 let seed = <Option<Word>>::read_from(source)?;
493
494 Self::new(id, vault, storage, code, nonce, seed)
495 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
496 }
497}
498
499pub fn hash_account(
508 id: AccountId,
509 nonce: Felt,
510 vault_root: Word,
511 storage_commitment: Word,
512 code_commitment: Word,
513) -> Word {
514 let mut elements = [ZERO; 16];
515 elements[0] = id.suffix();
516 elements[1] = id.prefix().as_felt();
517 elements[3] = nonce;
518 elements[4..8].copy_from_slice(&*vault_root);
519 elements[8..12].copy_from_slice(&*storage_commitment);
520 elements[12..].copy_from_slice(&*code_commitment);
521 Hasher::hash_elements(&elements)
522}
523
524pub(super) fn validate_account_seed(
529 id: AccountId,
530 code_commitment: Word,
531 storage_commitment: Word,
532 seed: Option<Word>,
533 nonce: Felt,
534) -> Result<(), AccountError> {
535 let account_is_new = nonce == ZERO;
536
537 match (account_is_new, seed) {
538 (true, Some(seed)) => {
539 let account_id =
540 AccountId::new(seed, id.version(), code_commitment, storage_commitment)
541 .map_err(AccountError::SeedConvertsToInvalidAccountId)?;
542
543 if account_id != id {
544 return Err(AccountError::AccountIdSeedMismatch {
545 expected: id,
546 actual: account_id,
547 });
548 }
549
550 Ok(())
551 },
552 (true, None) => Err(AccountError::NewAccountMissingSeed),
553 (false, Some(_)) => Err(AccountError::ExistingAccountWithSeed),
554 (false, None) => Ok(()),
555 }
556}
557
558fn validate_components_support_account_type(
560 components: &[AccountComponent],
561 account_type: AccountType,
562) -> Result<(), AccountError> {
563 for (component_index, component) in components.iter().enumerate() {
564 if !component.supports_type(account_type) {
565 return Err(AccountError::UnsupportedComponentForAccountType {
566 account_type,
567 component_index,
568 });
569 }
570 }
571
572 Ok(())
573}
574
575#[cfg(test)]
579mod tests {
580 use alloc::vec::Vec;
581
582 use assert_matches::assert_matches;
583 use miden_assembly::Assembler;
584 use miden_core::FieldElement;
585 use miden_crypto::utils::{Deserializable, Serializable};
586 use miden_crypto::{Felt, Word};
587
588 use super::{
589 AccountCode,
590 AccountDelta,
591 AccountId,
592 AccountStorage,
593 AccountStorageDelta,
594 AccountVaultDelta,
595 };
596 use crate::account::AccountStorageMode::Network;
597 use crate::account::{
598 Account,
599 AccountBuilder,
600 AccountComponent,
601 AccountIdVersion,
602 AccountType,
603 PartialAccount,
604 StorageMap,
605 StorageMapDelta,
606 StorageSlot,
607 StorageSlotContent,
608 StorageSlotName,
609 };
610 use crate::asset::{Asset, AssetVault, FungibleAsset, NonFungibleAsset};
611 use crate::errors::AccountError;
612 use crate::testing::account_id::{
613 ACCOUNT_ID_PRIVATE_SENDER,
614 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
615 };
616 use crate::testing::add_component::AddComponent;
617 use crate::testing::noop_auth_component::NoopAuthComponent;
618
619 #[test]
620 fn test_serde_account() {
621 let init_nonce = Felt::new(1);
622 let asset_0 = FungibleAsset::mock(99);
623 let word = Word::from([1, 2, 3, 4u32]);
624 let storage_slot = StorageSlotContent::Value(word);
625 let account = build_account(vec![asset_0], init_nonce, vec![storage_slot]);
626
627 let serialized = account.to_bytes();
628 let deserialized = Account::read_from_bytes(&serialized).unwrap();
629 assert_eq!(deserialized, account);
630 }
631
632 #[test]
633 fn test_serde_account_delta() {
634 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
635 let nonce_delta = Felt::new(2);
636 let asset_0 = FungibleAsset::mock(15);
637 let asset_1 = NonFungibleAsset::mock(&[5, 5, 5]);
638 let storage_delta = AccountStorageDelta::new()
639 .add_cleared_items([StorageSlotName::mock(0)])
640 .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))]);
641 let account_delta = build_account_delta(
642 account_id,
643 vec![asset_1],
644 vec![asset_0],
645 nonce_delta,
646 storage_delta,
647 );
648
649 let serialized = account_delta.to_bytes();
650 let deserialized = AccountDelta::read_from_bytes(&serialized).unwrap();
651 assert_eq!(deserialized, account_delta);
652 }
653
654 #[test]
655 fn valid_account_delta_is_correctly_applied() {
656 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
658 let init_nonce = Felt::new(1);
659 let asset_0 = FungibleAsset::mock(100);
660 let asset_1 = NonFungibleAsset::mock(&[1, 2, 3]);
661
662 let storage_slot_value_0 = StorageSlotContent::Value(Word::from([1, 2, 3, 4u32]));
664 let storage_slot_value_1 = StorageSlotContent::Value(Word::from([5, 6, 7, 8u32]));
665 let mut storage_map = StorageMap::with_entries([
666 (
667 Word::new([Felt::new(101), Felt::new(102), Felt::new(103), Felt::new(104)]),
668 Word::from([
669 Felt::new(1_u64),
670 Felt::new(2_u64),
671 Felt::new(3_u64),
672 Felt::new(4_u64),
673 ]),
674 ),
675 (
676 Word::new([Felt::new(105), Felt::new(106), Felt::new(107), Felt::new(108)]),
677 Word::new([Felt::new(5_u64), Felt::new(6_u64), Felt::new(7_u64), Felt::new(8_u64)]),
678 ),
679 ])
680 .unwrap();
681 let storage_slot_map = StorageSlotContent::Map(storage_map.clone());
682
683 let mut account = build_account(
684 vec![asset_0],
685 init_nonce,
686 vec![storage_slot_value_0, storage_slot_value_1, storage_slot_map],
687 );
688
689 let new_map_entry = (
691 Word::new([Felt::new(101), Felt::new(102), Felt::new(103), Felt::new(104)]),
692 [Felt::new(9_u64), Felt::new(10_u64), Felt::new(11_u64), Felt::new(12_u64)],
693 );
694
695 let updated_map =
696 StorageMapDelta::from_iters([], [(new_map_entry.0, new_map_entry.1.into())]);
697 storage_map.insert(new_map_entry.0, new_map_entry.1.into()).unwrap();
698
699 let final_nonce = Felt::new(2);
701 let storage_delta = AccountStorageDelta::new()
702 .add_cleared_items([StorageSlotName::mock(0)])
703 .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))])
704 .add_updated_maps([(StorageSlotName::mock(2), updated_map)]);
705 let account_delta = build_account_delta(
706 account_id,
707 vec![asset_1],
708 vec![asset_0],
709 final_nonce - init_nonce,
710 storage_delta,
711 );
712
713 account.apply_delta(&account_delta).unwrap();
715
716 let final_account = build_account(
717 vec![asset_1],
718 final_nonce,
719 vec![
720 StorageSlotContent::Value(Word::empty()),
721 StorageSlotContent::Value(Word::from([1, 2, 3, 4u32])),
722 StorageSlotContent::Map(storage_map),
723 ],
724 );
725
726 assert_eq!(account, final_account);
728 }
729
730 #[test]
731 #[should_panic]
732 fn valid_account_delta_with_unchanged_nonce() {
733 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
735 let init_nonce = Felt::new(1);
736 let asset = FungibleAsset::mock(110);
737 let mut account =
738 build_account(vec![asset], init_nonce, vec![StorageSlotContent::Value(Word::empty())]);
739
740 let storage_delta = AccountStorageDelta::new()
742 .add_cleared_items([StorageSlotName::mock(0)])
743 .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))]);
744 let account_delta =
745 build_account_delta(account_id, vec![], vec![asset], init_nonce, storage_delta);
746
747 account.apply_delta(&account_delta).unwrap()
749 }
750
751 #[test]
752 #[should_panic]
753 fn valid_account_delta_with_decremented_nonce() {
754 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
756 let init_nonce = Felt::new(2);
757 let asset = FungibleAsset::mock(100);
758 let mut account =
759 build_account(vec![asset], init_nonce, vec![StorageSlotContent::Value(Word::empty())]);
760
761 let final_nonce = Felt::new(1);
763 let storage_delta = AccountStorageDelta::new()
764 .add_cleared_items([StorageSlotName::mock(0)])
765 .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))]);
766 let account_delta =
767 build_account_delta(account_id, vec![], vec![asset], final_nonce, storage_delta);
768
769 account.apply_delta(&account_delta).unwrap()
771 }
772
773 #[test]
774 fn empty_account_delta_with_incremented_nonce() {
775 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
777 let init_nonce = Felt::new(1);
778 let word = Word::from([1, 2, 3, 4u32]);
779 let storage_slot = StorageSlotContent::Value(word);
780 let mut account = build_account(vec![], init_nonce, vec![storage_slot]);
781
782 let nonce_delta = Felt::new(1);
784 let account_delta = AccountDelta::new(
785 account_id,
786 AccountStorageDelta::new(),
787 AccountVaultDelta::default(),
788 nonce_delta,
789 )
790 .unwrap();
791
792 account.apply_delta(&account_delta).unwrap()
794 }
795
796 pub fn build_account_delta(
797 account_id: AccountId,
798 added_assets: Vec<Asset>,
799 removed_assets: Vec<Asset>,
800 nonce_delta: Felt,
801 storage_delta: AccountStorageDelta,
802 ) -> AccountDelta {
803 let vault_delta = AccountVaultDelta::from_iters(added_assets, removed_assets);
804 AccountDelta::new(account_id, storage_delta, vault_delta, nonce_delta).unwrap()
805 }
806
807 pub fn build_account(
808 assets: Vec<Asset>,
809 nonce: Felt,
810 slots: Vec<StorageSlotContent>,
811 ) -> Account {
812 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
813 let code = AccountCode::mock();
814
815 let vault = AssetVault::new(&assets).unwrap();
816
817 let slots = slots
818 .into_iter()
819 .enumerate()
820 .map(|(idx, slot)| StorageSlot::new(StorageSlotName::mock(idx), slot))
821 .collect();
822
823 let storage = AccountStorage::new(slots).unwrap();
824
825 Account::new_existing(id, vault, storage, code, nonce)
826 }
827
828 #[test]
831 fn test_account_unsupported_component_type() {
832 let code1 = "pub proc foo add end";
833 let library1 = Assembler::default().assemble_library([code1]).unwrap();
834
835 let component1 = AccountComponent::new(library1, vec![])
837 .unwrap()
838 .with_supported_type(AccountType::FungibleFaucet)
839 .with_supported_type(AccountType::NonFungibleFaucet)
840 .with_supported_type(AccountType::RegularAccountImmutableCode);
841
842 let err = Account::initialize_from_components(
843 AccountType::RegularAccountUpdatableCode,
844 vec![component1],
845 )
846 .unwrap_err();
847
848 assert!(matches!(
849 err,
850 AccountError::UnsupportedComponentForAccountType {
851 account_type: AccountType::RegularAccountUpdatableCode,
852 component_index: 0
853 }
854 ))
855 }
856
857 #[test]
859 fn seed_validation() -> anyhow::Result<()> {
860 let account = AccountBuilder::new([5; 32])
861 .with_auth_component(NoopAuthComponent)
862 .with_component(AddComponent)
863 .build()?;
864 let (id, vault, storage, code, _nonce, seed) = account.into_parts();
865 assert!(seed.is_some());
866
867 let other_seed = AccountId::compute_account_seed(
868 [9; 32],
869 AccountType::FungibleFaucet,
870 Network,
871 AccountIdVersion::Version0,
872 code.commitment(),
873 storage.to_commitment(),
874 )?;
875
876 let err = Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ONE, seed)
878 .unwrap_err();
879 assert_matches!(err, AccountError::ExistingAccountWithSeed);
880
881 let err = Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ZERO, None)
883 .unwrap_err();
884 assert_matches!(err, AccountError::NewAccountMissingSeed);
885
886 let err = Account::new(
889 id,
890 vault.clone(),
891 storage.clone(),
892 code.clone(),
893 Felt::ZERO,
894 Some(other_seed),
895 )
896 .unwrap_err();
897 assert_matches!(err, AccountError::AccountIdSeedMismatch { .. });
898
899 let err = Account::new(
902 id,
903 vault.clone(),
904 storage.clone(),
905 code.clone(),
906 Felt::ZERO,
907 Some(Word::from([1, 2, 3, 4u32])),
908 )
909 .unwrap_err();
910 assert_matches!(err, AccountError::SeedConvertsToInvalidAccountId(_));
911
912 Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ONE, None)?;
915
916 Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ZERO, seed)?;
919
920 Ok(())
921 }
922
923 #[test]
924 fn incrementing_nonce_should_remove_seed() -> anyhow::Result<()> {
925 let mut account = AccountBuilder::new([5; 32])
926 .with_auth_component(NoopAuthComponent)
927 .with_component(AddComponent)
928 .build()?;
929 account.increment_nonce(Felt::ONE)?;
930
931 assert_matches!(account.seed(), None);
932
933 let _partial_account = PartialAccount::from(&account);
936
937 Ok(())
938 }
939}