1use alloc::string::ToString;
2use alloc::vec::Vec;
3
4use crate::asset::{Asset, AssetVault};
5use crate::crypto::SequentialCommit;
6use crate::errors::AccountError;
7use crate::utils::serde::{
8 ByteReader,
9 ByteWriter,
10 Deserializable,
11 DeserializationError,
12 Serializable,
13};
14use crate::{Felt, Hasher, Word, ZERO};
15
16mod account_id;
17pub use account_id::{
18 AccountId,
19 AccountIdPrefix,
20 AccountIdPrefixV0,
21 AccountIdV0,
22 AccountIdVersion,
23 AccountStorageMode,
24 AccountType,
25};
26
27pub mod auth;
28
29mod builder;
30pub use builder::AccountBuilder;
31
32pub mod code;
33pub use code::AccountCode;
34pub use code::procedure::AccountProcedureRoot;
35
36pub mod component;
37pub use component::{AccountComponent, AccountComponentCode, AccountComponentMetadata};
38
39pub mod delta;
40pub use delta::{
41 AccountDelta,
42 AccountStorageDelta,
43 AccountVaultDelta,
44 FungibleAssetDelta,
45 NonFungibleAssetDelta,
46 NonFungibleDeltaAction,
47 StorageMapDelta,
48 StorageSlotDelta,
49};
50
51pub mod storage;
52pub use storage::{
53 AccountStorage,
54 AccountStorageHeader,
55 PartialStorage,
56 PartialStorageMap,
57 StorageMap,
58 StorageMapKey,
59 StorageMapKeyHash,
60 StorageMapWitness,
61 StorageSlot,
62 StorageSlotContent,
63 StorageSlotHeader,
64 StorageSlotId,
65 StorageSlotName,
66 StorageSlotType,
67};
68
69mod header;
70pub use header::AccountHeader;
71
72mod file;
73pub use file::AccountFile;
74
75mod partial;
76pub use partial::PartialAccount;
77
78#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct Account {
101 id: AccountId,
102 vault: AssetVault,
103 storage: AccountStorage,
104 code: AccountCode,
105 nonce: Felt,
106 seed: Option<Word>,
107}
108
109impl Account {
110 pub fn new(
123 id: AccountId,
124 vault: AssetVault,
125 storage: AccountStorage,
126 code: AccountCode,
127 nonce: Felt,
128 seed: Option<Word>,
129 ) -> Result<Self, AccountError> {
130 validate_account_seed(id, code.commitment(), storage.to_commitment(), seed, nonce)?;
131
132 Ok(Self::new_unchecked(id, vault, storage, code, nonce, seed))
133 }
134
135 pub fn new_unchecked(
142 id: AccountId,
143 vault: AssetVault,
144 storage: AccountStorage,
145 code: AccountCode,
146 nonce: Felt,
147 seed: Option<Word>,
148 ) -> Self {
149 Self { id, vault, storage, code, nonce, seed }
150 }
151
152 pub(super) fn initialize_from_components(
176 account_type: AccountType,
177 components: Vec<AccountComponent>,
178 ) -> Result<(AccountCode, AccountStorage), AccountError> {
179 validate_components_support_account_type(&components, account_type)?;
180
181 let code = AccountCode::from_components_unchecked(&components)?;
182 let storage = AccountStorage::from_components(components)?;
183
184 Ok((code, storage))
185 }
186
187 pub fn builder(init_seed: [u8; 32]) -> AccountBuilder {
192 AccountBuilder::new(init_seed)
193 }
194
195 pub fn to_commitment(&self) -> Word {
202 AccountHeader::from(self).to_commitment()
203 }
204
205 pub fn initial_commitment(&self) -> Word {
215 if self.is_new() {
216 Word::empty()
217 } else {
218 self.to_commitment()
219 }
220 }
221
222 pub fn id(&self) -> AccountId {
224 self.id
225 }
226
227 pub fn account_type(&self) -> AccountType {
229 self.id.account_type()
230 }
231
232 pub fn vault(&self) -> &AssetVault {
234 &self.vault
235 }
236
237 pub fn storage(&self) -> &AccountStorage {
239 &self.storage
240 }
241
242 pub fn code(&self) -> &AccountCode {
244 &self.code
245 }
246
247 pub fn nonce(&self) -> Felt {
249 self.nonce
250 }
251
252 pub fn seed(&self) -> Option<Word> {
256 self.seed
257 }
258
259 pub fn is_faucet(&self) -> bool {
261 self.id.is_faucet()
262 }
263
264 pub fn is_regular_account(&self) -> bool {
266 self.id.is_regular_account()
267 }
268
269 pub fn has_public_state(&self) -> bool {
272 self.id().has_public_state()
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_network(&self) -> bool {
287 self.id().is_network()
288 }
289
290 pub fn is_new(&self) -> bool {
295 self.nonce == ZERO
296 }
297
298 pub fn into_parts(
300 self,
301 ) -> (AccountId, AssetVault, AccountStorage, AccountCode, Felt, Option<Word>) {
302 (self.id, self.vault, self.storage, self.code, self.nonce, self.seed)
303 }
304
305 pub fn apply_delta(&mut self, delta: &AccountDelta) -> Result<(), AccountError> {
321 if delta.is_full_state() {
322 return Err(AccountError::ApplyFullStateDeltaToAccount);
323 }
324
325 self.vault
328 .apply_delta(delta.vault())
329 .map_err(AccountError::AssetVaultUpdateError)?;
330
331 self.storage.apply_delta(delta.storage())?;
333
334 self.increment_nonce(delta.nonce_delta())?;
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 if new_nonce.as_int() < self.nonce.as_int() {
350 return Err(AccountError::NonceOverflow {
351 current: self.nonce,
352 increment: nonce_delta,
353 new: new_nonce,
354 });
355 }
356
357 self.nonce = new_nonce;
358
359 if !self.is_new() {
364 self.seed = None;
365 }
366
367 Ok(())
368 }
369
370 #[cfg(any(feature = "testing", test))]
374 pub fn vault_mut(&mut self) -> &mut AssetVault {
376 &mut self.vault
377 }
378
379 #[cfg(any(feature = "testing", test))]
380 pub fn storage_mut(&mut self) -> &mut AccountStorage {
382 &mut self.storage
383 }
384}
385
386impl TryFrom<Account> for AccountDelta {
387 type Error = AccountError;
388
389 fn try_from(account: Account) -> Result<Self, Self::Error> {
398 let Account { id, vault, storage, code, nonce, seed } = account;
399
400 if seed.is_some() {
401 return Err(AccountError::DeltaFromAccountWithSeed);
402 }
403
404 let slot_deltas = storage
405 .into_slots()
406 .into_iter()
407 .map(StorageSlot::into_parts)
408 .map(|(slot_name, slot_content)| (slot_name, StorageSlotDelta::from(slot_content)))
409 .collect();
410 let storage_delta = AccountStorageDelta::from_raw(slot_deltas);
411
412 let mut fungible_delta = FungibleAssetDelta::default();
413 let mut non_fungible_delta = NonFungibleAssetDelta::default();
414 for asset in vault.assets() {
415 match asset {
417 Asset::Fungible(fungible_asset) => {
418 fungible_delta
419 .add(fungible_asset)
420 .expect("delta should allow representing valid fungible assets");
421 },
422 Asset::NonFungible(non_fungible_asset) => {
423 non_fungible_delta
424 .add(non_fungible_asset)
425 .expect("delta should allow representing valid non-fungible assets");
426 },
427 }
428 }
429 let vault_delta = AccountVaultDelta::new(fungible_delta, non_fungible_delta);
430
431 let nonce_delta = nonce;
434
435 let delta = AccountDelta::new(id, storage_delta, vault_delta, nonce_delta)
438 .expect("nonce_delta should be greater than 0")
439 .with_code(Some(code));
440
441 Ok(delta)
442 }
443}
444
445impl SequentialCommit for Account {
446 type Commitment = Word;
447
448 fn to_elements(&self) -> Vec<Felt> {
449 AccountHeader::from(self).to_elements()
450 }
451
452 fn to_commitment(&self) -> Self::Commitment {
453 AccountHeader::from(self).to_commitment()
454 }
455}
456
457impl Serializable for Account {
461 fn write_into<W: ByteWriter>(&self, target: &mut W) {
462 let Account { id, vault, storage, code, nonce, seed } = self;
463
464 id.write_into(target);
465 vault.write_into(target);
466 storage.write_into(target);
467 code.write_into(target);
468 nonce.write_into(target);
469 seed.write_into(target);
470 }
471
472 fn get_size_hint(&self) -> usize {
473 self.id.get_size_hint()
474 + self.vault.get_size_hint()
475 + self.storage.get_size_hint()
476 + self.code.get_size_hint()
477 + self.nonce.get_size_hint()
478 + self.seed.get_size_hint()
479 }
480}
481
482impl Deserializable for Account {
483 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
484 let id = AccountId::read_from(source)?;
485 let vault = AssetVault::read_from(source)?;
486 let storage = AccountStorage::read_from(source)?;
487 let code = AccountCode::read_from(source)?;
488 let nonce = Felt::read_from(source)?;
489 let seed = <Option<Word>>::read_from(source)?;
490
491 Self::new(id, vault, storage, code, nonce, seed)
492 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
493 }
494}
495
496pub(super) fn validate_account_seed(
501 id: AccountId,
502 code_commitment: Word,
503 storage_commitment: Word,
504 seed: Option<Word>,
505 nonce: Felt,
506) -> Result<(), AccountError> {
507 let account_is_new = nonce == ZERO;
508
509 match (account_is_new, seed) {
510 (true, Some(seed)) => {
511 let account_id =
512 AccountId::new(seed, id.version(), code_commitment, storage_commitment)
513 .map_err(AccountError::SeedConvertsToInvalidAccountId)?;
514
515 if account_id != id {
516 return Err(AccountError::AccountIdSeedMismatch {
517 expected: id,
518 actual: account_id,
519 });
520 }
521
522 Ok(())
523 },
524 (true, None) => Err(AccountError::NewAccountMissingSeed),
525 (false, Some(_)) => Err(AccountError::ExistingAccountWithSeed),
526 (false, None) => Ok(()),
527 }
528}
529
530fn validate_components_support_account_type(
532 components: &[AccountComponent],
533 account_type: AccountType,
534) -> Result<(), AccountError> {
535 for (component_index, component) in components.iter().enumerate() {
536 if !component.supports_type(account_type) {
537 return Err(AccountError::UnsupportedComponentForAccountType {
538 account_type,
539 component_index,
540 });
541 }
542 }
543
544 Ok(())
545}
546
547#[cfg(test)]
551mod tests {
552 use alloc::vec::Vec;
553
554 use assert_matches::assert_matches;
555 use miden_assembly::Assembler;
556 use miden_core::FieldElement;
557 use miden_crypto::utils::{Deserializable, Serializable};
558 use miden_crypto::{Felt, Word};
559
560 use super::{
561 AccountCode,
562 AccountDelta,
563 AccountId,
564 AccountStorage,
565 AccountStorageDelta,
566 AccountVaultDelta,
567 };
568 use crate::account::AccountStorageMode::Network;
569 use crate::account::component::AccountComponentMetadata;
570 use crate::account::{
571 Account,
572 AccountBuilder,
573 AccountComponent,
574 AccountIdVersion,
575 AccountType,
576 PartialAccount,
577 StorageMap,
578 StorageMapDelta,
579 StorageMapKey,
580 StorageSlot,
581 StorageSlotContent,
582 StorageSlotName,
583 };
584 use crate::asset::{Asset, AssetVault, FungibleAsset, NonFungibleAsset};
585 use crate::errors::AccountError;
586 use crate::testing::account_id::{
587 ACCOUNT_ID_PRIVATE_SENDER,
588 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
589 };
590 use crate::testing::add_component::AddComponent;
591 use crate::testing::noop_auth_component::NoopAuthComponent;
592
593 #[test]
594 fn test_serde_account() {
595 let init_nonce = Felt::new(1);
596 let asset_0 = FungibleAsset::mock(99);
597 let word = Word::from([1, 2, 3, 4u32]);
598 let storage_slot = StorageSlotContent::Value(word);
599 let account = build_account(vec![asset_0], init_nonce, vec![storage_slot]);
600
601 let serialized = account.to_bytes();
602 let deserialized = Account::read_from_bytes(&serialized).unwrap();
603 assert_eq!(deserialized, account);
604 }
605
606 #[test]
607 fn test_serde_account_delta() {
608 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
609 let nonce_delta = Felt::new(2);
610 let asset_0 = FungibleAsset::mock(15);
611 let asset_1 = NonFungibleAsset::mock(&[5, 5, 5]);
612 let storage_delta = AccountStorageDelta::new()
613 .add_cleared_items([StorageSlotName::mock(0)])
614 .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))]);
615 let account_delta = build_account_delta(
616 account_id,
617 vec![asset_1],
618 vec![asset_0],
619 nonce_delta,
620 storage_delta,
621 );
622
623 let serialized = account_delta.to_bytes();
624 let deserialized = AccountDelta::read_from_bytes(&serialized).unwrap();
625 assert_eq!(deserialized, account_delta);
626 }
627
628 #[test]
629 fn valid_account_delta_is_correctly_applied() {
630 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
632 let init_nonce = Felt::new(1);
633 let asset_0 = FungibleAsset::mock(100);
634 let asset_1 = NonFungibleAsset::mock(&[1, 2, 3]);
635
636 let storage_slot_value_0 = StorageSlotContent::Value(Word::from([1, 2, 3, 4u32]));
638 let storage_slot_value_1 = StorageSlotContent::Value(Word::from([5, 6, 7, 8u32]));
639 let mut storage_map = StorageMap::with_entries([
640 (
641 StorageMapKey::from_array([101, 102, 103, 104]),
642 Word::from([
643 Felt::new(1_u64),
644 Felt::new(2_u64),
645 Felt::new(3_u64),
646 Felt::new(4_u64),
647 ]),
648 ),
649 (
650 StorageMapKey::from_array([105, 106, 107, 108]),
651 Word::new([Felt::new(5_u64), Felt::new(6_u64), Felt::new(7_u64), Felt::new(8_u64)]),
652 ),
653 ])
654 .unwrap();
655 let storage_slot_map = StorageSlotContent::Map(storage_map.clone());
656
657 let mut account = build_account(
658 vec![asset_0],
659 init_nonce,
660 vec![storage_slot_value_0, storage_slot_value_1, storage_slot_map],
661 );
662
663 let key = StorageMapKey::from_array([101, 102, 103, 104]);
665 let value = Word::from([9, 10, 11, 12u32]);
666
667 let updated_map = StorageMapDelta::from_iters([], [(key, value)]);
668 storage_map.insert(key, value).unwrap();
669
670 let final_nonce = Felt::new(2);
672 let storage_delta = AccountStorageDelta::new()
673 .add_cleared_items([StorageSlotName::mock(0)])
674 .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))])
675 .add_updated_maps([(StorageSlotName::mock(2), updated_map)]);
676 let account_delta = build_account_delta(
677 account_id,
678 vec![asset_1],
679 vec![asset_0],
680 final_nonce - init_nonce,
681 storage_delta,
682 );
683
684 account.apply_delta(&account_delta).unwrap();
686
687 let final_account = build_account(
688 vec![asset_1],
689 final_nonce,
690 vec![
691 StorageSlotContent::Value(Word::empty()),
692 StorageSlotContent::Value(Word::from([1, 2, 3, 4u32])),
693 StorageSlotContent::Map(storage_map),
694 ],
695 );
696
697 assert_eq!(account, final_account);
699 }
700
701 #[test]
702 #[should_panic]
703 fn valid_account_delta_with_unchanged_nonce() {
704 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
706 let init_nonce = Felt::new(1);
707 let asset = FungibleAsset::mock(110);
708 let mut account =
709 build_account(vec![asset], init_nonce, vec![StorageSlotContent::Value(Word::empty())]);
710
711 let storage_delta = AccountStorageDelta::new()
713 .add_cleared_items([StorageSlotName::mock(0)])
714 .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))]);
715 let account_delta =
716 build_account_delta(account_id, vec![], vec![asset], init_nonce, storage_delta);
717
718 account.apply_delta(&account_delta).unwrap()
720 }
721
722 #[test]
723 #[should_panic]
724 fn valid_account_delta_with_decremented_nonce() {
725 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
727 let init_nonce = Felt::new(2);
728 let asset = FungibleAsset::mock(100);
729 let mut account =
730 build_account(vec![asset], init_nonce, vec![StorageSlotContent::Value(Word::empty())]);
731
732 let final_nonce = Felt::new(1);
734 let storage_delta = AccountStorageDelta::new()
735 .add_cleared_items([StorageSlotName::mock(0)])
736 .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))]);
737 let account_delta =
738 build_account_delta(account_id, vec![], vec![asset], final_nonce, storage_delta);
739
740 account.apply_delta(&account_delta).unwrap()
742 }
743
744 #[test]
745 fn empty_account_delta_with_incremented_nonce() {
746 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
748 let init_nonce = Felt::new(1);
749 let word = Word::from([1, 2, 3, 4u32]);
750 let storage_slot = StorageSlotContent::Value(word);
751 let mut account = build_account(vec![], init_nonce, vec![storage_slot]);
752
753 let nonce_delta = Felt::new(1);
755 let account_delta = AccountDelta::new(
756 account_id,
757 AccountStorageDelta::new(),
758 AccountVaultDelta::default(),
759 nonce_delta,
760 )
761 .unwrap();
762
763 account.apply_delta(&account_delta).unwrap()
765 }
766
767 pub fn build_account_delta(
768 account_id: AccountId,
769 added_assets: Vec<Asset>,
770 removed_assets: Vec<Asset>,
771 nonce_delta: Felt,
772 storage_delta: AccountStorageDelta,
773 ) -> AccountDelta {
774 let vault_delta = AccountVaultDelta::from_iters(added_assets, removed_assets);
775 AccountDelta::new(account_id, storage_delta, vault_delta, nonce_delta).unwrap()
776 }
777
778 pub fn build_account(
779 assets: Vec<Asset>,
780 nonce: Felt,
781 slots: Vec<StorageSlotContent>,
782 ) -> Account {
783 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
784 let code = AccountCode::mock();
785
786 let vault = AssetVault::new(&assets).unwrap();
787
788 let slots = slots
789 .into_iter()
790 .enumerate()
791 .map(|(idx, slot)| StorageSlot::new(StorageSlotName::mock(idx), slot))
792 .collect();
793
794 let storage = AccountStorage::new(slots).unwrap();
795
796 Account::new_existing(id, vault, storage, code, nonce)
797 }
798
799 #[test]
802 fn test_account_unsupported_component_type() {
803 let code1 = "pub proc foo add end";
804 let library1 = Assembler::default().assemble_library([code1]).unwrap();
805
806 let metadata = AccountComponentMetadata::new("test::component1")
808 .with_supported_type(AccountType::FungibleFaucet)
809 .with_supported_type(AccountType::NonFungibleFaucet)
810 .with_supported_type(AccountType::RegularAccountImmutableCode);
811 let component1 = AccountComponent::new(library1, vec![], metadata).unwrap();
812
813 let err = Account::initialize_from_components(
814 AccountType::RegularAccountUpdatableCode,
815 vec![component1],
816 )
817 .unwrap_err();
818
819 assert!(matches!(
820 err,
821 AccountError::UnsupportedComponentForAccountType {
822 account_type: AccountType::RegularAccountUpdatableCode,
823 component_index: 0
824 }
825 ))
826 }
827
828 #[test]
830 fn seed_validation() -> anyhow::Result<()> {
831 let account = AccountBuilder::new([5; 32])
832 .with_auth_component(NoopAuthComponent)
833 .with_component(AddComponent)
834 .build()?;
835 let (id, vault, storage, code, _nonce, seed) = account.into_parts();
836 assert!(seed.is_some());
837
838 let other_seed = AccountId::compute_account_seed(
839 [9; 32],
840 AccountType::FungibleFaucet,
841 Network,
842 AccountIdVersion::Version0,
843 code.commitment(),
844 storage.to_commitment(),
845 )?;
846
847 let err = Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ONE, seed)
849 .unwrap_err();
850 assert_matches!(err, AccountError::ExistingAccountWithSeed);
851
852 let err = Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ZERO, None)
854 .unwrap_err();
855 assert_matches!(err, AccountError::NewAccountMissingSeed);
856
857 let err = Account::new(
860 id,
861 vault.clone(),
862 storage.clone(),
863 code.clone(),
864 Felt::ZERO,
865 Some(other_seed),
866 )
867 .unwrap_err();
868 assert_matches!(err, AccountError::AccountIdSeedMismatch { .. });
869
870 let err = Account::new(
873 id,
874 vault.clone(),
875 storage.clone(),
876 code.clone(),
877 Felt::ZERO,
878 Some(Word::from([1, 2, 3, 4u32])),
879 )
880 .unwrap_err();
881 assert_matches!(err, AccountError::SeedConvertsToInvalidAccountId(_));
882
883 Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ONE, None)?;
886
887 Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ZERO, seed)?;
890
891 Ok(())
892 }
893
894 #[test]
895 fn incrementing_nonce_should_remove_seed() -> anyhow::Result<()> {
896 let mut account = AccountBuilder::new([5; 32])
897 .with_auth_component(NoopAuthComponent)
898 .with_component(AddComponent)
899 .build()?;
900 account.increment_nonce(Felt::ONE)?;
901
902 assert_matches!(account.seed(), None);
903
904 let _partial_account = PartialAccount::from(&account);
907
908 Ok(())
909 }
910}