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 AccountIdPrefixV1,
21 AccountIdV1,
22 AccountIdVersion,
23 AccountType,
24};
25
26pub(crate) mod name_validation;
27
28pub mod auth;
29
30mod access;
31pub use access::RoleSymbol;
32
33mod builder;
34pub use builder::AccountBuilder;
35
36pub mod code;
37pub use code::AccountCode;
38pub use code::procedure::AccountProcedureRoot;
39
40pub mod component;
41pub use component::{AccountComponent, AccountComponentCode, AccountComponentMetadata};
42
43pub mod interface;
44pub use interface::AccountComponentName;
45
46pub mod delta;
47pub use delta::{
48 AccountDelta,
49 AccountStorageDelta,
50 AccountVaultDelta,
51 FungibleAssetDelta,
52 NonFungibleAssetDelta,
53 NonFungibleDeltaAction,
54 StorageMapDelta,
55 StorageSlotDelta,
56};
57
58pub mod storage;
59pub use storage::{
60 AccountStorage,
61 AccountStorageHeader,
62 PartialStorage,
63 PartialStorageMap,
64 StorageMap,
65 StorageMapKey,
66 StorageMapKeyHash,
67 StorageMapWitness,
68 StorageSlot,
69 StorageSlotContent,
70 StorageSlotHeader,
71 StorageSlotId,
72 StorageSlotName,
73 StorageSlotType,
74};
75
76mod header;
77pub use header::AccountHeader;
78
79mod file;
80pub use file::AccountFile;
81
82mod partial;
83pub use partial::PartialAccount;
84
85#[derive(Debug, Clone, PartialEq, Eq)]
107pub struct Account {
108 id: AccountId,
109 vault: AssetVault,
110 storage: AccountStorage,
111 code: AccountCode,
112 nonce: Felt,
113 seed: Option<Word>,
114}
115
116impl Account {
117 pub fn new(
130 id: AccountId,
131 vault: AssetVault,
132 storage: AccountStorage,
133 code: AccountCode,
134 nonce: Felt,
135 seed: Option<Word>,
136 ) -> Result<Self, AccountError> {
137 validate_account_seed(id, code.commitment(), storage.to_commitment(), seed, nonce)?;
138
139 Ok(Self::new_unchecked(id, vault, storage, code, nonce, seed))
140 }
141
142 pub fn new_unchecked(
149 id: AccountId,
150 vault: AssetVault,
151 storage: AccountStorage,
152 code: AccountCode,
153 nonce: Felt,
154 seed: Option<Word>,
155 ) -> Self {
156 Self { id, vault, storage, code, nonce, seed }
157 }
158
159 pub(super) fn initialize_from_components(
182 components: Vec<AccountComponent>,
183 ) -> Result<(AccountCode, AccountStorage), AccountError> {
184 let code = AccountCode::from_components_unchecked(&components)?;
185 let storage = AccountStorage::from_components(components)?;
186
187 Ok((code, storage))
188 }
189
190 pub fn builder(init_seed: [u8; 32]) -> AccountBuilder {
195 AccountBuilder::new(init_seed)
196 }
197
198 pub fn to_commitment(&self) -> Word {
205 AccountHeader::from(self).to_commitment()
206 }
207
208 pub fn initial_commitment(&self) -> Word {
218 if self.is_new() {
219 Word::empty()
220 } else {
221 self.to_commitment()
222 }
223 }
224
225 pub fn id(&self) -> AccountId {
227 self.id
228 }
229
230 pub fn vault(&self) -> &AssetVault {
232 &self.vault
233 }
234
235 pub fn storage(&self) -> &AccountStorage {
237 &self.storage
238 }
239
240 pub fn code(&self) -> &AccountCode {
242 &self.code
243 }
244
245 pub fn nonce(&self) -> Felt {
247 self.nonce
248 }
249
250 pub fn seed(&self) -> Option<Word> {
254 self.seed
255 }
256
257 pub fn is_public(&self) -> bool {
259 self.id().is_public()
260 }
261
262 pub fn is_private(&self) -> bool {
264 self.id().is_private()
265 }
266
267 pub fn is_new(&self) -> bool {
272 self.nonce == ZERO
273 }
274
275 pub fn into_parts(
277 self,
278 ) -> (AccountId, AssetVault, AccountStorage, AccountCode, Felt, Option<Word>) {
279 (self.id, self.vault, self.storage, self.code, self.nonce, self.seed)
280 }
281
282 pub fn apply_delta(&mut self, delta: &AccountDelta) -> Result<(), AccountError> {
298 if delta.is_full_state() {
299 return Err(AccountError::ApplyFullStateDeltaToAccount);
300 }
301
302 self.vault
305 .apply_delta(delta.vault())
306 .map_err(AccountError::AssetVaultUpdateError)?;
307
308 self.storage.apply_delta(delta.storage())?;
310
311 self.increment_nonce(delta.nonce_delta())?;
313
314 Ok(())
315 }
316
317 pub fn increment_nonce(&mut self, nonce_delta: Felt) -> Result<(), AccountError> {
324 let new_nonce = self.nonce + nonce_delta;
325
326 if new_nonce.as_canonical_u64() < self.nonce.as_canonical_u64() {
327 return Err(AccountError::NonceOverflow {
328 current: self.nonce,
329 increment: nonce_delta,
330 new: new_nonce,
331 });
332 }
333
334 self.nonce = new_nonce;
335
336 if !self.is_new() {
341 self.seed = None;
342 }
343
344 Ok(())
345 }
346
347 #[cfg(any(feature = "testing", test))]
351 pub fn vault_mut(&mut self) -> &mut AssetVault {
353 &mut self.vault
354 }
355
356 #[cfg(any(feature = "testing", test))]
357 pub fn storage_mut(&mut self) -> &mut AccountStorage {
359 &mut self.storage
360 }
361}
362
363impl TryFrom<Account> for AccountDelta {
364 type Error = AccountError;
365
366 fn try_from(account: Account) -> Result<Self, Self::Error> {
375 let Account { id, vault, storage, code, nonce, seed } = account;
376
377 if seed.is_some() {
378 return Err(AccountError::DeltaFromAccountWithSeed);
379 }
380
381 let slot_deltas = storage
382 .into_slots()
383 .into_iter()
384 .map(StorageSlot::into_parts)
385 .map(|(slot_name, slot_content)| (slot_name, StorageSlotDelta::from(slot_content)))
386 .collect();
387 let storage_delta = AccountStorageDelta::from_raw(slot_deltas);
388
389 let mut fungible_delta = FungibleAssetDelta::default();
390 let mut non_fungible_delta = NonFungibleAssetDelta::default();
391 for asset in vault.assets() {
392 match asset {
394 Asset::Fungible(fungible_asset) => {
395 fungible_delta
396 .add(fungible_asset)
397 .expect("delta should allow representing valid fungible assets");
398 },
399 Asset::NonFungible(non_fungible_asset) => {
400 non_fungible_delta
401 .add(non_fungible_asset)
402 .expect("delta should allow representing valid non-fungible assets");
403 },
404 }
405 }
406 let vault_delta = AccountVaultDelta::new(fungible_delta, non_fungible_delta);
407
408 let nonce_delta = nonce;
411
412 let delta = AccountDelta::new(id, storage_delta, vault_delta, nonce_delta)
415 .expect("nonce_delta should be greater than 0")
416 .with_code(Some(code));
417
418 Ok(delta)
419 }
420}
421
422impl SequentialCommit for Account {
423 type Commitment = Word;
424
425 fn to_elements(&self) -> Vec<Felt> {
426 AccountHeader::from(self).to_elements()
427 }
428
429 fn to_commitment(&self) -> Self::Commitment {
430 AccountHeader::from(self).to_commitment()
431 }
432}
433
434impl Serializable for Account {
438 fn write_into<W: ByteWriter>(&self, target: &mut W) {
439 let Account { id, vault, storage, code, nonce, seed } = self;
440
441 id.write_into(target);
442 vault.write_into(target);
443 storage.write_into(target);
444 code.write_into(target);
445 nonce.write_into(target);
446 seed.write_into(target);
447 }
448
449 fn get_size_hint(&self) -> usize {
450 self.id.get_size_hint()
451 + self.vault.get_size_hint()
452 + self.storage.get_size_hint()
453 + self.code.get_size_hint()
454 + self.nonce.get_size_hint()
455 + self.seed.get_size_hint()
456 }
457}
458
459impl Deserializable for Account {
460 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
461 let id = AccountId::read_from(source)?;
462 let vault = AssetVault::read_from(source)?;
463 let storage = AccountStorage::read_from(source)?;
464 let code = AccountCode::read_from(source)?;
465 let nonce = Felt::read_from(source)?;
466 let seed = <Option<Word>>::read_from(source)?;
467
468 Self::new(id, vault, storage, code, nonce, seed)
469 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
470 }
471}
472
473pub(super) fn validate_account_seed(
478 id: AccountId,
479 code_commitment: Word,
480 storage_commitment: Word,
481 seed: Option<Word>,
482 nonce: Felt,
483) -> Result<(), AccountError> {
484 let account_is_new = nonce == ZERO;
485
486 match (account_is_new, seed) {
487 (true, Some(seed)) => {
488 let account_id =
489 AccountId::new(seed, id.version(), code_commitment, storage_commitment)
490 .map_err(AccountError::SeedConvertsToInvalidAccountId)?;
491
492 if account_id != id {
493 return Err(AccountError::AccountIdSeedMismatch {
494 expected: id,
495 actual: account_id,
496 });
497 }
498
499 Ok(())
500 },
501 (true, None) => Err(AccountError::NewAccountMissingSeed),
502 (false, Some(_)) => Err(AccountError::ExistingAccountWithSeed),
503 (false, None) => Ok(()),
504 }
505}
506
507#[cfg(test)]
511mod tests {
512 use alloc::vec::Vec;
513
514 use assert_matches::assert_matches;
515 use miden_crypto::utils::{Deserializable, Serializable};
516 use miden_crypto::{Felt, Word};
517
518 use super::{
519 AccountCode,
520 AccountDelta,
521 AccountId,
522 AccountStorage,
523 AccountStorageDelta,
524 AccountVaultDelta,
525 };
526 use crate::account::{
527 Account,
528 AccountBuilder,
529 AccountIdVersion,
530 AccountType,
531 PartialAccount,
532 StorageMap,
533 StorageMapDelta,
534 StorageMapKey,
535 StorageSlot,
536 StorageSlotContent,
537 StorageSlotName,
538 };
539 use crate::asset::{Asset, AssetVault, FungibleAsset, NonFungibleAsset};
540 use crate::errors::AccountError;
541 use crate::testing::account_id::{
542 ACCOUNT_ID_PRIVATE_SENDER,
543 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
544 };
545 use crate::testing::add_component::AddComponent;
546 use crate::testing::noop_auth_component::NoopAuthComponent;
547
548 #[test]
549 fn test_serde_account() {
550 let init_nonce = Felt::from(1_u32);
551 let asset_0 = FungibleAsset::mock(99);
552 let word = Word::from([1, 2, 3, 4u32]);
553 let storage_slot = StorageSlotContent::Value(word);
554 let account = build_account(vec![asset_0], init_nonce, vec![storage_slot]);
555
556 let serialized = account.to_bytes();
557 let deserialized = Account::read_from_bytes(&serialized).unwrap();
558 assert_eq!(deserialized, account);
559 }
560
561 #[test]
562 fn test_serde_account_delta() {
563 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
564 let nonce_delta = Felt::from(2_u32);
565 let asset_0 = FungibleAsset::mock(15);
566 let asset_1 = NonFungibleAsset::mock(&[5, 5, 5]);
567 let storage_delta = AccountStorageDelta::new()
568 .add_cleared_items([StorageSlotName::mock(0)])
569 .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))]);
570 let account_delta = build_account_delta(
571 account_id,
572 vec![asset_1],
573 vec![asset_0],
574 nonce_delta,
575 storage_delta,
576 );
577
578 let serialized = account_delta.to_bytes();
579 let deserialized = AccountDelta::read_from_bytes(&serialized).unwrap();
580 assert_eq!(deserialized, account_delta);
581 }
582
583 #[test]
584 fn valid_account_delta_is_correctly_applied() {
585 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
587 let init_nonce = Felt::from(1_u32);
588 let asset_0 = FungibleAsset::mock(100);
589 let asset_1 = NonFungibleAsset::mock(&[1, 2, 3]);
590
591 let storage_slot_value_0 = StorageSlotContent::Value(Word::from([1, 2, 3, 4u32]));
593 let storage_slot_value_1 = StorageSlotContent::Value(Word::from([5, 6, 7, 8u32]));
594 let mut storage_map = StorageMap::with_entries([
595 (
596 StorageMapKey::from_array([101, 102, 103, 104]),
597 Word::from([1_u32, 2_u32, 3_u32, 4_u32]),
598 ),
599 (
600 StorageMapKey::from_array([105, 106, 107, 108]),
601 Word::from([5_u32, 6_u32, 7_u32, 8_u32]),
602 ),
603 ])
604 .unwrap();
605 let storage_slot_map = StorageSlotContent::Map(storage_map.clone());
606
607 let mut account = build_account(
608 vec![asset_0],
609 init_nonce,
610 vec![storage_slot_value_0, storage_slot_value_1, storage_slot_map],
611 );
612
613 let key = StorageMapKey::from_array([101, 102, 103, 104]);
615 let value = Word::from([9, 10, 11, 12u32]);
616
617 let updated_map = StorageMapDelta::from_iters([], [(key, value)]);
618 storage_map.insert(key, value).unwrap();
619
620 let final_nonce = Felt::from(2_u32);
622 let storage_delta = AccountStorageDelta::new()
623 .add_cleared_items([StorageSlotName::mock(0)])
624 .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))])
625 .add_updated_maps([(StorageSlotName::mock(2), updated_map)]);
626 let account_delta = build_account_delta(
627 account_id,
628 vec![asset_1],
629 vec![asset_0],
630 final_nonce - init_nonce,
631 storage_delta,
632 );
633
634 account.apply_delta(&account_delta).unwrap();
636
637 let final_account = build_account(
638 vec![asset_1],
639 final_nonce,
640 vec![
641 StorageSlotContent::Value(Word::empty()),
642 StorageSlotContent::Value(Word::from([1, 2, 3, 4u32])),
643 StorageSlotContent::Map(storage_map),
644 ],
645 );
646
647 assert_eq!(account, final_account);
649 }
650
651 #[test]
652 #[should_panic]
653 fn valid_account_delta_with_unchanged_nonce() {
654 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
656 let init_nonce = Felt::from(1_u32);
657 let asset = FungibleAsset::mock(110);
658 let mut account =
659 build_account(vec![asset], init_nonce, vec![StorageSlotContent::Value(Word::empty())]);
660
661 let storage_delta = AccountStorageDelta::new()
663 .add_cleared_items([StorageSlotName::mock(0)])
664 .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))]);
665 let account_delta =
666 build_account_delta(account_id, vec![], vec![asset], init_nonce, storage_delta);
667
668 account.apply_delta(&account_delta).unwrap()
670 }
671
672 #[test]
673 #[should_panic]
674 fn valid_account_delta_with_decremented_nonce() {
675 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
677 let init_nonce = Felt::from(2_u32);
678 let asset = FungibleAsset::mock(100);
679 let mut account =
680 build_account(vec![asset], init_nonce, vec![StorageSlotContent::Value(Word::empty())]);
681
682 let final_nonce = Felt::from(1_u32);
684 let storage_delta = AccountStorageDelta::new()
685 .add_cleared_items([StorageSlotName::mock(0)])
686 .add_updated_values([(StorageSlotName::mock(1), Word::from([1, 2, 3, 4u32]))]);
687 let account_delta =
688 build_account_delta(account_id, vec![], vec![asset], final_nonce, storage_delta);
689
690 account.apply_delta(&account_delta).unwrap()
692 }
693
694 #[test]
695 fn empty_account_delta_with_incremented_nonce() {
696 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
698 let init_nonce = Felt::from(1_u32);
699 let word = Word::from([1, 2, 3, 4u32]);
700 let storage_slot = StorageSlotContent::Value(word);
701 let mut account = build_account(vec![], init_nonce, vec![storage_slot]);
702
703 let nonce_delta = Felt::from(1_u32);
705 let account_delta = AccountDelta::new(
706 account_id,
707 AccountStorageDelta::new(),
708 AccountVaultDelta::default(),
709 nonce_delta,
710 )
711 .unwrap();
712
713 account.apply_delta(&account_delta).unwrap()
715 }
716
717 pub fn build_account_delta(
718 account_id: AccountId,
719 added_assets: Vec<Asset>,
720 removed_assets: Vec<Asset>,
721 nonce_delta: Felt,
722 storage_delta: AccountStorageDelta,
723 ) -> AccountDelta {
724 let vault_delta = AccountVaultDelta::from_iters(added_assets, removed_assets);
725 AccountDelta::new(account_id, storage_delta, vault_delta, nonce_delta).unwrap()
726 }
727
728 pub fn build_account(
729 assets: Vec<Asset>,
730 nonce: Felt,
731 slots: Vec<StorageSlotContent>,
732 ) -> Account {
733 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
734 let code = AccountCode::mock();
735
736 let vault = AssetVault::new(&assets).unwrap();
737
738 let slots = slots
739 .into_iter()
740 .enumerate()
741 .map(|(idx, slot)| StorageSlot::new(StorageSlotName::mock(idx), slot))
742 .collect();
743
744 let storage = AccountStorage::new(slots).unwrap();
745
746 Account::new_existing(id, vault, storage, code, nonce)
747 }
748
749 #[test]
751 fn seed_validation() -> anyhow::Result<()> {
752 let account = AccountBuilder::new([5; 32])
753 .with_auth_component(NoopAuthComponent)
754 .with_component(AddComponent)
755 .build()?;
756 let (id, vault, storage, code, _nonce, seed) = account.into_parts();
757 assert!(seed.is_some());
758
759 let other_seed = AccountId::compute_account_seed(
760 [9; 32],
761 AccountType::Public,
762 AccountIdVersion::Version1,
763 code.commitment(),
764 storage.to_commitment(),
765 )?;
766
767 let err = Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ONE, seed)
769 .unwrap_err();
770 assert_matches!(err, AccountError::ExistingAccountWithSeed);
771
772 let err = Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ZERO, None)
774 .unwrap_err();
775 assert_matches!(err, AccountError::NewAccountMissingSeed);
776
777 let err = Account::new(
780 id,
781 vault.clone(),
782 storage.clone(),
783 code.clone(),
784 Felt::ZERO,
785 Some(other_seed),
786 )
787 .unwrap_err();
788 assert_matches!(err, AccountError::AccountIdSeedMismatch { .. });
789
790 let err = Account::new(
793 id,
794 vault.clone(),
795 storage.clone(),
796 code.clone(),
797 Felt::ZERO,
798 Some(Word::from([1, 2, 3, 4u32])),
799 )
800 .unwrap_err();
801 assert_matches!(err, AccountError::SeedConvertsToInvalidAccountId(_));
802
803 Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ONE, None)?;
806
807 Account::new(id, vault.clone(), storage.clone(), code.clone(), Felt::ZERO, seed)?;
810
811 Ok(())
812 }
813
814 #[test]
815 fn incrementing_nonce_should_remove_seed() -> anyhow::Result<()> {
816 let mut account = AccountBuilder::new([5; 32])
817 .with_auth_component(NoopAuthComponent)
818 .with_component(AddComponent)
819 .build()?;
820 account.increment_nonce(Felt::ONE)?;
821
822 assert_matches!(account.seed(), None);
823
824 let _partial_account = PartialAccount::from(&account);
827
828 Ok(())
829 }
830}