1use crate::asset::AssetVault;
2use crate::utils::serde::{
3 ByteReader,
4 ByteWriter,
5 Deserializable,
6 DeserializationError,
7 Serializable,
8};
9use crate::{AccountError, Felt, Hasher, Word, ZERO};
10
11mod account_id;
12pub use account_id::{
13 AccountId,
14 AccountIdPrefix,
15 AccountIdPrefixV0,
16 AccountIdV0,
17 AccountIdVersion,
18 AccountStorageMode,
19 AccountType,
20 NetworkId,
21};
22
23pub mod auth;
24
25pub use auth::AuthSecretKey;
26
27mod builder;
28pub use builder::AccountBuilder;
29
30pub mod code;
31pub use code::AccountCode;
32pub use code::procedure::AccountProcedureInfo;
33
34pub mod component;
35pub use component::{
36 AccountComponent,
37 AccountComponentMetadata,
38 AccountComponentTemplate,
39 FeltRepresentation,
40 InitStorageData,
41 MapEntry,
42 MapRepresentation,
43 PlaceholderTypeRequirement,
44 StorageEntry,
45 StorageValueName,
46 StorageValueNameError,
47 TemplateType,
48 TemplateTypeError,
49 WordRepresentation,
50};
51
52pub mod delta;
53pub use delta::{
54 AccountDelta,
55 AccountStorageDelta,
56 AccountVaultDelta,
57 FungibleAssetDelta,
58 NonFungibleAssetDelta,
59 NonFungibleDeltaAction,
60 StorageMapDelta,
61};
62
63mod storage;
64pub use storage::{
65 AccountStorage,
66 AccountStorageHeader,
67 PartialStorage,
68 PartialStorageMap,
69 StorageMap,
70 StorageSlot,
71 StorageSlotType,
72};
73
74mod header;
75pub use header::AccountHeader;
76
77mod file;
78pub use file::AccountFile;
79
80mod partial;
81pub use partial::PartialAccount;
82
83#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct Account {
106 id: AccountId,
107 vault: AssetVault,
108 storage: AccountStorage,
109 code: AccountCode,
110 nonce: Felt,
111}
112
113impl Account {
114 pub fn from_parts(
119 id: AccountId,
120 vault: AssetVault,
121 storage: AccountStorage,
122 code: AccountCode,
123 nonce: Felt,
124 ) -> Self {
125 Self { id, vault, storage, code, nonce }
126 }
127
128 pub(super) fn initialize_from_components(
170 account_type: AccountType,
171 components: &[AccountComponent],
172 ) -> Result<(AccountCode, AccountStorage), AccountError> {
173 validate_components_support_account_type(components, account_type)?;
174
175 let code = AccountCode::from_components_unchecked(components, account_type)?;
176 let storage = AccountStorage::from_components(components, account_type)?;
177
178 Ok((code, storage))
179 }
180
181 pub fn builder(init_seed: [u8; 32]) -> AccountBuilder {
186 AccountBuilder::new(init_seed)
187 }
188
189 pub fn commitment(&self) -> Word {
198 hash_account(
199 self.id,
200 self.nonce,
201 self.vault.root(),
202 self.storage.commitment(),
203 self.code.commitment(),
204 )
205 }
206
207 pub fn init_commitment(&self) -> Word {
217 if self.is_new() {
218 Word::empty()
219 } else {
220 self.commitment()
221 }
222 }
223
224 pub fn id(&self) -> AccountId {
226 self.id
227 }
228
229 pub fn account_type(&self) -> AccountType {
231 self.id.account_type()
232 }
233
234 pub fn vault(&self) -> &AssetVault {
236 &self.vault
237 }
238
239 pub fn storage(&self) -> &AccountStorage {
241 &self.storage
242 }
243
244 pub fn code(&self) -> &AccountCode {
246 &self.code
247 }
248
249 pub fn nonce(&self) -> Felt {
251 self.nonce
252 }
253
254 pub fn is_faucet(&self) -> bool {
256 self.id.is_faucet()
257 }
258
259 pub fn is_regular_account(&self) -> bool {
261 self.id.is_regular_account()
262 }
263
264 pub fn is_onchain(&self) -> bool {
267 self.id().is_onchain()
268 }
269
270 pub fn is_public(&self) -> bool {
272 self.id().is_public()
273 }
274
275 pub fn is_private(&self) -> bool {
277 self.id().is_private()
278 }
279
280 pub fn is_network(&self) -> bool {
282 self.id().is_network()
283 }
284
285 pub fn is_new(&self) -> bool {
287 self.nonce == ZERO
288 }
289
290 pub fn into_parts(self) -> (AccountId, AssetVault, AccountStorage, AccountCode, Felt) {
292 (self.id, self.vault, self.storage, self.code, self.nonce)
293 }
294
295 pub fn apply_delta(&mut self, delta: &AccountDelta) -> Result<(), AccountError> {
308 self.vault
311 .apply_delta(delta.vault())
312 .map_err(AccountError::AssetVaultUpdateError)?;
313
314 self.storage.apply_delta(delta.storage())?;
316
317 self.increment_nonce(delta.nonce_delta())?;
319
320 Ok(())
321 }
322
323 pub fn increment_nonce(&mut self, nonce_delta: Felt) -> Result<(), AccountError> {
330 let new_nonce = self.nonce + nonce_delta;
331
332 if new_nonce.as_int() < self.nonce.as_int() {
333 return Err(AccountError::NonceOverflow {
334 current: self.nonce,
335 increment: nonce_delta,
336 new: new_nonce,
337 });
338 }
339
340 self.nonce = new_nonce;
341
342 Ok(())
343 }
344
345 #[cfg(any(feature = "testing", test))]
349 pub fn vault_mut(&mut self) -> &mut AssetVault {
351 &mut self.vault
352 }
353
354 #[cfg(any(feature = "testing", test))]
355 pub fn storage_mut(&mut self) -> &mut AccountStorage {
357 &mut self.storage
358 }
359}
360
361impl Serializable for Account {
365 fn write_into<W: ByteWriter>(&self, target: &mut W) {
366 let Account { id, vault, storage, code, nonce } = self;
367
368 id.write_into(target);
369 vault.write_into(target);
370 storage.write_into(target);
371 code.write_into(target);
372 nonce.write_into(target);
373 }
374
375 fn get_size_hint(&self) -> usize {
376 self.id.get_size_hint()
377 + self.vault.get_size_hint()
378 + self.storage.get_size_hint()
379 + self.code.get_size_hint()
380 + self.nonce.get_size_hint()
381 }
382}
383
384impl Deserializable for Account {
385 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
386 let id = AccountId::read_from(source)?;
387 let vault = AssetVault::read_from(source)?;
388 let storage = AccountStorage::read_from(source)?;
389 let code = AccountCode::read_from(source)?;
390 let nonce = Felt::read_from(source)?;
391
392 Ok(Self::from_parts(id, vault, storage, code, nonce))
393 }
394}
395
396pub fn hash_account(
405 id: AccountId,
406 nonce: Felt,
407 vault_root: Word,
408 storage_commitment: Word,
409 code_commitment: Word,
410) -> Word {
411 let mut elements = [ZERO; 16];
412 elements[0] = id.suffix();
413 elements[1] = id.prefix().as_felt();
414 elements[3] = nonce;
415 elements[4..8].copy_from_slice(&*vault_root);
416 elements[8..12].copy_from_slice(&*storage_commitment);
417 elements[12..].copy_from_slice(&*code_commitment);
418 Hasher::hash_elements(&elements)
419}
420
421fn validate_components_support_account_type(
423 components: &[AccountComponent],
424 account_type: AccountType,
425) -> Result<(), AccountError> {
426 for (component_index, component) in components.iter().enumerate() {
427 if !component.supports_type(account_type) {
428 return Err(AccountError::UnsupportedComponentForAccountType {
429 account_type,
430 component_index,
431 });
432 }
433 }
434
435 Ok(())
436}
437
438#[cfg(test)]
442mod tests {
443 use alloc::vec::Vec;
444
445 use assert_matches::assert_matches;
446 use miden_assembly::Assembler;
447 use miden_crypto::utils::{Deserializable, Serializable};
448 use miden_crypto::{Felt, Word};
449
450 use super::{
451 AccountCode,
452 AccountDelta,
453 AccountId,
454 AccountStorage,
455 AccountStorageDelta,
456 AccountVaultDelta,
457 };
458 use crate::AccountError;
459 use crate::account::{
460 Account,
461 AccountComponent,
462 AccountType,
463 StorageMap,
464 StorageMapDelta,
465 StorageSlot,
466 };
467 use crate::asset::{Asset, AssetVault, FungibleAsset, NonFungibleAsset};
468 use crate::testing::account_id::{
469 ACCOUNT_ID_PRIVATE_SENDER,
470 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
471 };
472 use crate::testing::noop_auth_component::NoopAuthComponent;
473 use crate::testing::storage::AccountStorageDeltaBuilder;
474
475 #[test]
476 fn test_serde_account() {
477 let init_nonce = Felt::new(1);
478 let asset_0 = FungibleAsset::mock(99);
479 let word = Word::from([1, 2, 3, 4u32]);
480 let storage_slot = StorageSlot::Value(word);
481 let account = build_account(vec![asset_0], init_nonce, vec![storage_slot]);
482
483 let serialized = account.to_bytes();
484 let deserialized = Account::read_from_bytes(&serialized).unwrap();
485 assert_eq!(deserialized, account);
486 }
487
488 #[test]
489 fn test_serde_account_delta() {
490 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
491 let nonce_delta = Felt::new(2);
492 let asset_0 = FungibleAsset::mock(15);
493 let asset_1 = NonFungibleAsset::mock(&[5, 5, 5]);
494 let storage_delta = AccountStorageDeltaBuilder::new()
495 .add_cleared_items([0])
496 .add_updated_values([(1_u8, Word::from([1, 2, 3, 4u32]))])
497 .build()
498 .unwrap();
499 let account_delta = build_account_delta(
500 account_id,
501 vec![asset_1],
502 vec![asset_0],
503 nonce_delta,
504 storage_delta,
505 );
506
507 let serialized = account_delta.to_bytes();
508 let deserialized = AccountDelta::read_from_bytes(&serialized).unwrap();
509 assert_eq!(deserialized, account_delta);
510 }
511
512 #[test]
513 fn valid_account_delta_is_correctly_applied() {
514 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
516 let init_nonce = Felt::new(1);
517 let asset_0 = FungibleAsset::mock(100);
518 let asset_1 = NonFungibleAsset::mock(&[1, 2, 3]);
519
520 let storage_slot_value_0 = StorageSlot::Value(Word::from([1, 2, 3, 4u32]));
522 let storage_slot_value_1 = StorageSlot::Value(Word::from([5, 6, 7, 8u32]));
523 let mut storage_map = StorageMap::with_entries([
524 (
525 Word::new([Felt::new(101), Felt::new(102), Felt::new(103), Felt::new(104)]),
526 Word::from([
527 Felt::new(1_u64),
528 Felt::new(2_u64),
529 Felt::new(3_u64),
530 Felt::new(4_u64),
531 ]),
532 ),
533 (
534 Word::new([Felt::new(105), Felt::new(106), Felt::new(107), Felt::new(108)]),
535 Word::new([Felt::new(5_u64), Felt::new(6_u64), Felt::new(7_u64), Felt::new(8_u64)]),
536 ),
537 ])
538 .unwrap();
539 let storage_slot_map = StorageSlot::Map(storage_map.clone());
540
541 let mut account = build_account(
542 vec![asset_0],
543 init_nonce,
544 vec![storage_slot_value_0, storage_slot_value_1, storage_slot_map],
545 );
546
547 let new_map_entry = (
549 Word::new([Felt::new(101), Felt::new(102), Felt::new(103), Felt::new(104)]),
550 [Felt::new(9_u64), Felt::new(10_u64), Felt::new(11_u64), Felt::new(12_u64)],
551 );
552
553 let updated_map =
554 StorageMapDelta::from_iters([], [(new_map_entry.0, new_map_entry.1.into())]);
555 storage_map.insert(new_map_entry.0, new_map_entry.1.into());
556
557 let final_nonce = Felt::new(2);
559 let storage_delta = AccountStorageDeltaBuilder::new()
560 .add_cleared_items([0])
561 .add_updated_values([(1, Word::from([1, 2, 3, 4u32]))])
562 .add_updated_maps([(2, updated_map)])
563 .build()
564 .unwrap();
565 let account_delta = build_account_delta(
566 account_id,
567 vec![asset_1],
568 vec![asset_0],
569 final_nonce - init_nonce,
570 storage_delta,
571 );
572
573 account.apply_delta(&account_delta).unwrap();
575
576 let final_account = build_account(
577 vec![asset_1],
578 final_nonce,
579 vec![
580 StorageSlot::Value(Word::empty()),
581 StorageSlot::Value(Word::from([1, 2, 3, 4u32])),
582 StorageSlot::Map(storage_map),
583 ],
584 );
585
586 assert_eq!(account, final_account);
588 }
589
590 #[test]
591 #[should_panic]
592 fn valid_account_delta_with_unchanged_nonce() {
593 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
595 let init_nonce = Felt::new(1);
596 let asset = FungibleAsset::mock(110);
597 let mut account =
598 build_account(vec![asset], init_nonce, vec![StorageSlot::Value(Word::empty())]);
599
600 let storage_delta = AccountStorageDeltaBuilder::new()
602 .add_cleared_items([0])
603 .add_updated_values([(1_u8, Word::from([1, 2, 3, 4u32]))])
604 .build()
605 .unwrap();
606 let account_delta =
607 build_account_delta(account_id, vec![], vec![asset], init_nonce, storage_delta);
608
609 account.apply_delta(&account_delta).unwrap()
611 }
612
613 #[test]
614 #[should_panic]
615 fn valid_account_delta_with_decremented_nonce() {
616 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
618 let init_nonce = Felt::new(2);
619 let asset = FungibleAsset::mock(100);
620 let mut account =
621 build_account(vec![asset], init_nonce, vec![StorageSlot::Value(Word::empty())]);
622
623 let final_nonce = Felt::new(1);
625 let storage_delta = AccountStorageDeltaBuilder::new()
626 .add_cleared_items([0])
627 .add_updated_values([(1_u8, Word::from([1, 2, 3, 4u32]))])
628 .build()
629 .unwrap();
630 let account_delta =
631 build_account_delta(account_id, vec![], vec![asset], final_nonce, storage_delta);
632
633 account.apply_delta(&account_delta).unwrap()
635 }
636
637 #[test]
638 fn empty_account_delta_with_incremented_nonce() {
639 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
641 let init_nonce = Felt::new(1);
642 let word = Word::from([1, 2, 3, 4u32]);
643 let storage_slot = StorageSlot::Value(word);
644 let mut account = build_account(vec![], init_nonce, vec![storage_slot]);
645
646 let nonce_delta = Felt::new(1);
648 let account_delta = AccountDelta::new(
649 account_id,
650 AccountStorageDelta::new(),
651 AccountVaultDelta::default(),
652 nonce_delta,
653 )
654 .unwrap();
655
656 account.apply_delta(&account_delta).unwrap()
658 }
659
660 pub fn build_account_delta(
661 account_id: AccountId,
662 added_assets: Vec<Asset>,
663 removed_assets: Vec<Asset>,
664 nonce_delta: Felt,
665 storage_delta: AccountStorageDelta,
666 ) -> AccountDelta {
667 let vault_delta = AccountVaultDelta::from_iters(added_assets, removed_assets);
668 AccountDelta::new(account_id, storage_delta, vault_delta, nonce_delta).unwrap()
669 }
670
671 pub fn build_account(assets: Vec<Asset>, nonce: Felt, slots: Vec<StorageSlot>) -> Account {
672 let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
673 let code = AccountCode::mock();
674
675 let vault = AssetVault::new(&assets).unwrap();
676
677 let storage = AccountStorage::new(slots).unwrap();
678
679 Account::from_parts(id, vault, storage, code, nonce)
680 }
681
682 #[test]
685 fn test_account_unsupported_component_type() {
686 let code1 = "export.foo add end";
687 let library1 = Assembler::default().assemble_library([code1]).unwrap();
688
689 let component1 = AccountComponent::new(library1, vec![])
691 .unwrap()
692 .with_supported_type(AccountType::FungibleFaucet)
693 .with_supported_type(AccountType::NonFungibleFaucet)
694 .with_supported_type(AccountType::RegularAccountImmutableCode);
695
696 let err = Account::initialize_from_components(
697 AccountType::RegularAccountUpdatableCode,
698 &[component1],
699 )
700 .unwrap_err();
701
702 assert!(matches!(
703 err,
704 AccountError::UnsupportedComponentForAccountType {
705 account_type: AccountType::RegularAccountUpdatableCode,
706 component_index: 0
707 }
708 ))
709 }
710
711 #[test]
714 fn test_account_duplicate_exported_mast_root() {
715 let code1 = "export.foo add eq.1 end";
716 let code2 = "export.bar add eq.1 end";
717
718 let library1 = Assembler::default().assemble_library([code1]).unwrap();
719 let library2 = Assembler::default().assemble_library([code2]).unwrap();
720
721 let component1 = AccountComponent::new(library1, vec![]).unwrap().with_supports_all_types();
722 let component2 = AccountComponent::new(library2, vec![]).unwrap().with_supports_all_types();
723
724 let err = Account::initialize_from_components(
725 AccountType::RegularAccountUpdatableCode,
726 &[NoopAuthComponent.into(), component1, component2],
727 )
728 .unwrap_err();
729
730 assert_matches!(err, AccountError::AccountComponentDuplicateProcedureRoot(_))
731 }
732}