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_canonical_u64() < self.nonce.as_canonical_u64() {
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_crypto::utils::{Deserializable, Serializable};
557 use miden_crypto::{Felt, Word};
558
559 use super::{
560 AccountCode,
561 AccountDelta,
562 AccountId,
563 AccountStorage,
564 AccountStorageDelta,
565 AccountVaultDelta,
566 };
567 use crate::account::AccountStorageMode::Network;
568 use crate::account::component::AccountComponentMetadata;
569 use crate::account::{
570 Account,
571 AccountBuilder,
572 AccountComponent,
573 AccountIdVersion,
574 AccountType,
575 PartialAccount,
576 StorageMap,
577 StorageMapDelta,
578 StorageMapKey,
579 StorageSlot,
580 StorageSlotContent,
581 StorageSlotName,
582 };
583 use crate::asset::{Asset, AssetVault, FungibleAsset, NonFungibleAsset};
584 use crate::errors::AccountError;
585 use crate::testing::account_id::{
586 ACCOUNT_ID_PRIVATE_SENDER,
587 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
588 };
589 use crate::testing::add_component::AddComponent;
590 use crate::testing::noop_auth_component::NoopAuthComponent;
591
592 #[test]
593 fn test_serde_account() {
594 let init_nonce = Felt::new(1);
595 let asset_0 = FungibleAsset::mock(99);
596 let word = Word::from([1, 2, 3, 4u32]);
597 let storage_slot = StorageSlotContent::Value(word);
598 let account = build_account(vec![asset_0], init_nonce, vec![storage_slot]);
599
600 let serialized = account.to_bytes();
601 let deserialized = Account::read_from_bytes(&serialized).unwrap();
602 assert_eq!(deserialized, account);
603 }
604
605 #[test]
606 fn test_serde_account_delta() {
607 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
608 let nonce_delta = Felt::new(2);
609 let asset_0 = FungibleAsset::mock(15);
610 let asset_1 = NonFungibleAsset::mock(&[5, 5, 5]);
611 let storage_delta = AccountStorageDelta::new()
612 .add_cleared_items([StorageSlotName::mock(0)])
613 .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))]);
614 let account_delta = build_account_delta(
615 account_id,
616 vec![asset_1],
617 vec![asset_0],
618 nonce_delta,
619 storage_delta,
620 );
621
622 let serialized = account_delta.to_bytes();
623 let deserialized = AccountDelta::read_from_bytes(&serialized).unwrap();
624 assert_eq!(deserialized, account_delta);
625 }
626
627 #[test]
628 fn valid_account_delta_is_correctly_applied() {
629 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
631 let init_nonce = Felt::new(1);
632 let asset_0 = FungibleAsset::mock(100);
633 let asset_1 = NonFungibleAsset::mock(&[1, 2, 3]);
634
635 let storage_slot_value_0 = StorageSlotContent::Value(Word::from([1, 2, 3, 4u32]));
637 let storage_slot_value_1 = StorageSlotContent::Value(Word::from([5, 6, 7, 8u32]));
638 let mut storage_map = StorageMap::with_entries([
639 (
640 StorageMapKey::from_array([101, 102, 103, 104]),
641 Word::from([
642 Felt::new(1_u64),
643 Felt::new(2_u64),
644 Felt::new(3_u64),
645 Felt::new(4_u64),
646 ]),
647 ),
648 (
649 StorageMapKey::from_array([105, 106, 107, 108]),
650 Word::new([Felt::new(5_u64), Felt::new(6_u64), Felt::new(7_u64), Felt::new(8_u64)]),
651 ),
652 ])
653 .unwrap();
654 let storage_slot_map = StorageSlotContent::Map(storage_map.clone());
655
656 let mut account = build_account(
657 vec![asset_0],
658 init_nonce,
659 vec![storage_slot_value_0, storage_slot_value_1, storage_slot_map],
660 );
661
662 let key = StorageMapKey::from_array([101, 102, 103, 104]);
664 let value = Word::from([9, 10, 11, 12u32]);
665
666 let updated_map = StorageMapDelta::from_iters([], [(key, value)]);
667 storage_map.insert(key, value).unwrap();
668
669 let final_nonce = Felt::new(2);
671 let storage_delta = AccountStorageDelta::new()
672 .add_cleared_items([StorageSlotName::mock(0)])
673 .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))])
674 .add_updated_maps([(StorageSlotName::mock(2), updated_map)]);
675 let account_delta = build_account_delta(
676 account_id,
677 vec![asset_1],
678 vec![asset_0],
679 final_nonce - init_nonce,
680 storage_delta,
681 );
682
683 account.apply_delta(&account_delta).unwrap();
685
686 let final_account = build_account(
687 vec![asset_1],
688 final_nonce,
689 vec![
690 StorageSlotContent::Value(Word::empty()),
691 StorageSlotContent::Value(Word::from([1, 2, 3, 4u32])),
692 StorageSlotContent::Map(storage_map),
693 ],
694 );
695
696 assert_eq!(account, final_account);
698 }
699
700 #[test]
701 #[should_panic]
702 fn valid_account_delta_with_unchanged_nonce() {
703 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
705 let init_nonce = Felt::new(1);
706 let asset = FungibleAsset::mock(110);
707 let mut account =
708 build_account(vec![asset], init_nonce, vec![StorageSlotContent::Value(Word::empty())]);
709
710 let storage_delta = AccountStorageDelta::new()
712 .add_cleared_items([StorageSlotName::mock(0)])
713 .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))]);
714 let account_delta =
715 build_account_delta(account_id, vec![], vec![asset], init_nonce, storage_delta);
716
717 account.apply_delta(&account_delta).unwrap()
719 }
720
721 #[test]
722 #[should_panic]
723 fn valid_account_delta_with_decremented_nonce() {
724 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
726 let init_nonce = Felt::new(2);
727 let asset = FungibleAsset::mock(100);
728 let mut account =
729 build_account(vec![asset], init_nonce, vec![StorageSlotContent::Value(Word::empty())]);
730
731 let final_nonce = Felt::new(1);
733 let storage_delta = AccountStorageDelta::new()
734 .add_cleared_items([StorageSlotName::mock(0)])
735 .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))]);
736 let account_delta =
737 build_account_delta(account_id, vec![], vec![asset], final_nonce, storage_delta);
738
739 account.apply_delta(&account_delta).unwrap()
741 }
742
743 #[test]
744 fn empty_account_delta_with_incremented_nonce() {
745 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
747 let init_nonce = Felt::new(1);
748 let word = Word::from([1, 2, 3, 4u32]);
749 let storage_slot = StorageSlotContent::Value(word);
750 let mut account = build_account(vec![], init_nonce, vec![storage_slot]);
751
752 let nonce_delta = Felt::new(1);
754 let account_delta = AccountDelta::new(
755 account_id,
756 AccountStorageDelta::new(),
757 AccountVaultDelta::default(),
758 nonce_delta,
759 )
760 .unwrap();
761
762 account.apply_delta(&account_delta).unwrap()
764 }
765
766 pub fn build_account_delta(
767 account_id: AccountId,
768 added_assets: Vec<Asset>,
769 removed_assets: Vec<Asset>,
770 nonce_delta: Felt,
771 storage_delta: AccountStorageDelta,
772 ) -> AccountDelta {
773 let vault_delta = AccountVaultDelta::from_iters(added_assets, removed_assets);
774 AccountDelta::new(account_id, storage_delta, vault_delta, nonce_delta).unwrap()
775 }
776
777 pub fn build_account(
778 assets: Vec<Asset>,
779 nonce: Felt,
780 slots: Vec<StorageSlotContent>,
781 ) -> Account {
782 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
783 let code = AccountCode::mock();
784
785 let vault = AssetVault::new(&assets).unwrap();
786
787 let slots = slots
788 .into_iter()
789 .enumerate()
790 .map(|(idx, slot)| StorageSlot::new(StorageSlotName::mock(idx), slot))
791 .collect();
792
793 let storage = AccountStorage::new(slots).unwrap();
794
795 Account::new_existing(id, vault, storage, code, nonce)
796 }
797
798 #[test]
801 fn test_account_unsupported_component_type() {
802 let code1 = "pub proc foo add end";
803 let library1 = Assembler::default().assemble_library([code1]).unwrap();
804
805 let metadata = AccountComponentMetadata::new(
807 "test::component1",
808 [
809 AccountType::FungibleFaucet,
810 AccountType::NonFungibleFaucet,
811 AccountType::RegularAccountImmutableCode,
812 ],
813 );
814 let component1 = AccountComponent::new(library1, vec![], metadata).unwrap();
815
816 let err = Account::initialize_from_components(
817 AccountType::RegularAccountUpdatableCode,
818 vec![component1],
819 )
820 .unwrap_err();
821
822 assert!(matches!(
823 err,
824 AccountError::UnsupportedComponentForAccountType {
825 account_type: AccountType::RegularAccountUpdatableCode,
826 component_index: 0
827 }
828 ))
829 }
830
831 #[test]
833 fn seed_validation() -> anyhow::Result<()> {
834 let account = AccountBuilder::new([5; 32])
835 .with_auth_component(NoopAuthComponent)
836 .with_component(AddComponent)
837 .build()?;
838 let (id, vault, storage, code, _nonce, seed) = account.into_parts();
839 assert!(seed.is_some());
840
841 let other_seed = AccountId::compute_account_seed(
842 [9; 32],
843 AccountType::FungibleFaucet,
844 Network,
845 AccountIdVersion::Version0,
846 code.commitment(),
847 storage.to_commitment(),
848 )?;
849
850 let err = Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ONE, seed)
852 .unwrap_err();
853 assert_matches!(err, AccountError::ExistingAccountWithSeed);
854
855 let err = Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ZERO, None)
857 .unwrap_err();
858 assert_matches!(err, AccountError::NewAccountMissingSeed);
859
860 let err = Account::new(
863 id,
864 vault.clone(),
865 storage.clone(),
866 code.clone(),
867 Felt::ZERO,
868 Some(other_seed),
869 )
870 .unwrap_err();
871 assert_matches!(err, AccountError::AccountIdSeedMismatch { .. });
872
873 let err = Account::new(
876 id,
877 vault.clone(),
878 storage.clone(),
879 code.clone(),
880 Felt::ZERO,
881 Some(Word::from([1, 2, 3, 4u32])),
882 )
883 .unwrap_err();
884 assert_matches!(err, AccountError::SeedConvertsToInvalidAccountId(_));
885
886 Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ONE, None)?;
889
890 Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ZERO, seed)?;
893
894 Ok(())
895 }
896
897 #[test]
898 fn incrementing_nonce_should_remove_seed() -> anyhow::Result<()> {
899 let mut account = AccountBuilder::new([5; 32])
900 .with_auth_component(NoopAuthComponent)
901 .with_component(AddComponent)
902 .build()?;
903 account.increment_nonce(Felt::ONE)?;
904
905 assert_matches!(account.seed(), None);
906
907 let _partial_account = PartialAccount::from(&account);
910
911 Ok(())
912 }
913}