1mod code;
2mod vault;
3
4mod storage;
5mod update_details;
6use alloc::string::ToString;
7use alloc::vec::Vec;
8
9pub use code::AccountCodePatch;
10pub use storage::{
11 AccountStoragePatch,
12 StorageMapPatch,
13 StorageMapPatchEntries,
14 StoragePatchOperation,
15 StorageSlotPatch,
16 StorageValuePatch,
17};
18pub use update_details::AccountUpdateDetails;
19pub(crate) use update_details::validate_new_public_account;
20pub use vault::AccountVaultPatch;
21
22use crate::account::{Account, AccountId, AccountStorage};
23use crate::asset::AssetVault;
24use crate::crypto::SequentialCommit;
25use crate::errors::{AccountError, AccountPatchError};
26use crate::utils::serde::{
27 ByteReader,
28 ByteWriter,
29 Deserializable,
30 DeserializationError,
31 Serializable,
32};
33use crate::{Felt, Hasher, Word};
34
35#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct AccountPatch {
57 account_id: AccountId,
59 storage: AccountStoragePatch,
61 vault: AccountVaultPatch,
63 code: AccountCodePatch,
65 final_nonce: Option<Felt>,
69}
70
71impl AccountPatch {
72 const DOMAIN: Felt = Felt::new_unchecked(0x02_0000);
80
81 const VERSION_1: u8 = 1;
87
88 pub fn new(
108 account_id: AccountId,
109 storage: AccountStoragePatch,
110 vault: AccountVaultPatch,
111 code: AccountCodePatch,
112 final_nonce: Option<Felt>,
113 ) -> Result<Self, AccountPatchError> {
114 if final_nonce.is_some_and(|final_nonce| final_nonce == Felt::ZERO) {
118 return Err(AccountPatchError::FinalNonceIsZero);
119 }
120
121 if (!storage.is_empty() || !vault.is_empty() || !code.is_empty()) && final_nonce.is_none() {
124 return Err(AccountPatchError::StateChangeRequiresNonceUpdate);
125 }
126
127 if final_nonce.is_some_and(|final_nonce| final_nonce == Felt::ONE) && code.is_empty() {
131 return Err(AccountPatchError::CodeMustBeProvidedForNewAccounts);
132 }
133
134 Ok(Self {
135 account_id,
136 storage,
137 vault,
138 code,
139 final_nonce,
140 })
141 }
142
143 pub fn empty(account_id: AccountId) -> Self {
145 AccountPatch::new(
146 account_id,
147 AccountStoragePatch::default(),
148 AccountVaultPatch::default(),
149 AccountCodePatch::default(),
150 None,
151 )
152 .expect("empty patch should be valid")
153 }
154
155 pub fn merge(&mut self, other: Self) -> Result<(), AccountPatchError> {
184 if self.account_id != other.account_id {
185 return Err(AccountPatchError::AccountIdMismatch {
186 expected: self.account_id,
187 actual: other.account_id,
188 });
189 }
190
191 match (self.final_nonce, other.final_nonce) {
192 (None, None) => return Ok(()),
194
195 (None, Some(_)) => {
197 *self = other;
198 return Ok(());
199 },
200
201 (Some(_), None) => return Ok(()),
203
204 (Some(current), Some(new)) => {
205 if new <= current {
206 return Err(AccountPatchError::NonceMustIncrease { current, new });
207 }
208 self.final_nonce = Some(new);
209 },
210 }
211
212 self.storage.merge(other.storage)?;
213 self.vault.merge(other.vault);
214 self.code.merge(other.code);
215
216 Ok(())
217 }
218
219 pub fn id(&self) -> AccountId {
224 self.account_id
225 }
226
227 pub fn storage(&self) -> &AccountStoragePatch {
229 &self.storage
230 }
231
232 pub fn vault(&self) -> &AccountVaultPatch {
234 &self.vault
235 }
236
237 pub fn code(&self) -> &AccountCodePatch {
239 &self.code
240 }
241
242 pub fn final_nonce(&self) -> Option<Felt> {
245 self.final_nonce
246 }
247
248 pub fn is_empty(&self) -> bool {
251 self.final_nonce.is_none()
254 }
255
256 pub fn to_commitment(&self) -> Word {
322 <Self as SequentialCommit>::to_commitment(self)
323 }
324
325 pub fn try_to_new_account(&self) -> Result<Account, AccountError> {
348 let (Some(code), Some(nonce)) = (self.code.as_code(), self.final_nonce) else {
349 return Err(AccountError::NewAccountRequiresCodeAndNonce);
350 };
351
352 if self.storage.contains_non_create_ops() {
353 return Err(AccountError::NewAccountStorageRequiresCreateOps);
354 }
355
356 let mut vault = AssetVault::default();
357 vault.apply_patch(&self.vault).map_err(AccountError::AssetVaultUpdateError)?;
358
359 let mut storage = AccountStorage::default();
360 storage.apply_patch(&self.storage)?;
361
362 Account::new(self.account_id, vault, storage, code.clone(), nonce, None)
363 }
364}
365
366impl SequentialCommit for AccountPatch {
367 type Commitment = Word;
368
369 fn to_commitment(&self) -> Word {
373 let elements = self.to_elements();
374
375 if elements.is_empty() {
377 return Word::empty();
378 }
379
380 Hasher::hash_elements_in_domain(&elements, Self::DOMAIN)
381 }
382
383 fn to_elements(&self) -> Vec<Felt> {
387 if self.is_empty() {
389 return Vec::new();
390 }
391
392 let mut elements = Vec::with_capacity(8);
394
395 let final_nonce = self.final_nonce.expect("non-empty patches should have a new nonce set");
397 elements.extend_from_slice(&[
398 Felt::from(Self::VERSION_1),
399 final_nonce,
400 self.account_id.suffix(),
401 self.account_id.prefix().as_felt(),
402 ]);
403 elements.extend_from_slice(Word::empty().as_elements());
404
405 self.vault.append_patch_elements(&mut elements);
407
408 self.storage.append_patch_elements(&mut elements);
410
411 self.code.append_patch_elements(&mut elements);
413
414 debug_assert!(
415 elements.len() % (2 * crate::WORD_SIZE) == 0,
416 "expected elements to contain an even number of words, but it contained {} elements",
417 elements.len()
418 );
419
420 elements
421 }
422}
423
424impl Serializable for AccountPatch {
425 fn write_into<W: ByteWriter>(&self, target: &mut W) {
426 self.account_id.write_into(target);
427 self.storage.write_into(target);
428 self.vault.write_into(target);
429 self.code.write_into(target);
430 self.final_nonce.write_into(target);
431 }
432
433 fn get_size_hint(&self) -> usize {
434 self.account_id.get_size_hint()
435 + self.storage.get_size_hint()
436 + self.vault.get_size_hint()
437 + self.code.get_size_hint()
438 + self.final_nonce.get_size_hint()
439 }
440}
441
442impl Deserializable for AccountPatch {
443 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
444 let account_id = AccountId::read_from(source)?;
445 let storage = AccountStoragePatch::read_from(source)?;
446 let vault = AccountVaultPatch::read_from(source)?;
447 let code = AccountCodePatch::read_from(source)?;
448 let final_nonce = <Option<Felt>>::read_from(source)?;
449
450 Self::new(account_id, storage, vault, code, final_nonce)
451 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
452 }
453}
454
455#[cfg(test)]
459mod tests {
460 use assert_matches::assert_matches;
461 use miden_core::serde::Deserializable;
462 use rstest::rstest;
463
464 use super::{AccountCodePatch, AccountPatch, AccountVaultPatch};
465 use crate::account::{
466 AccountCode,
467 AccountId,
468 AccountStoragePatch,
469 StorageMapKey,
470 StorageMapPatch,
471 StorageSlotName,
472 StorageSlotPatch,
473 StorageValuePatch,
474 };
475 use crate::asset::{Asset, FungibleAsset, NonFungibleAsset};
476 use crate::errors::{AccountError, AccountPatchError};
477 use crate::testing::account_id::{
478 ACCOUNT_ID_PRIVATE_SENDER,
479 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
480 };
481 use crate::testing::add_component::AddComponent;
482 use crate::testing::noop_auth_component::NoopAuthComponent;
483 use crate::utils::serde::Serializable;
484 use crate::{Felt, Word};
485
486 #[test]
487 fn account_patch_serde() -> anyhow::Result<()> {
488 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
489 let asset_0 = FungibleAsset::mock(100);
490 let asset_1 = FungibleAsset::new(ACCOUNT_ID_PRIVATE_SENDER.try_into()?, 500_000)?.into();
491 let asset_2 = NonFungibleAsset::mock(&[10]);
492 let asset_3 = NonFungibleAsset::mock(&[20]);
493 let vault_patch = AccountVaultPatch::with_assets([asset_0, asset_1, asset_2, asset_3]);
494
495 let storage_patch = AccountStoragePatch::from_iters(
496 [StorageSlotName::mock(1)],
497 [
498 (StorageSlotName::mock(2), Word::from([1, 1, 1, 1u32])),
499 (StorageSlotName::mock(3), Word::from([1, 1, 0, 1u32])),
500 ],
501 [(
502 StorageSlotName::mock(4),
503 StorageMapPatch::from_iters(
504 [
505 StorageMapKey::from_array([1, 1, 1, 0]),
506 StorageMapKey::from_array([0, 1, 1, 1]),
507 ],
508 [(StorageMapKey::from_array([1, 1, 1, 1]), Word::from([1, 1, 1, 1u32]))],
509 ),
510 )],
511 );
512
513 assert_eq!(storage_patch.to_bytes().len(), storage_patch.get_size_hint());
514 assert_eq!(vault_patch.to_bytes().len(), vault_patch.get_size_hint());
515
516 let account_patch = AccountPatch::new(
517 account_id,
518 storage_patch,
519 vault_patch,
520 AccountCodePatch::default(),
521 Some(Felt::from(5u8)),
522 )?;
523 assert_eq!(AccountPatch::read_from_bytes(&account_patch.to_bytes())?, account_patch);
524 assert_eq!(account_patch.to_bytes().len(), account_patch.get_size_hint());
525
526 Ok(())
527 }
528
529 #[test]
532 fn account_patch_final_nonce_is_zero() -> anyhow::Result<()> {
533 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
534
535 let error = AccountPatch::new(
536 account_id,
537 AccountStoragePatch::new(),
538 AccountVaultPatch::default(),
539 AccountCodePatch::default(),
540 Some(Felt::ZERO),
541 )
542 .unwrap_err();
543
544 assert_matches!(error, AccountPatchError::FinalNonceIsZero);
545
546 Ok(())
547 }
548
549 #[rstest::rstest]
552 #[case::non_empty_storage(
553 AccountStoragePatch::from_iters([StorageSlotName::mock(1)], [], []),
554 AccountVaultPatch::default(),
555 AccountCodePatch::default(),
556 )]
557 #[case::non_empty_vault(
558 AccountStoragePatch::new(),
559 AccountVaultPatch::with_assets([FungibleAsset::mock(100)]),
560 AccountCodePatch::default(),
561 )]
562 #[case::non_empty_code(
563 AccountStoragePatch::new(),
564 AccountVaultPatch::default(),
565 AccountCodePatch::new(Some(AccountCode::mock()))
566 )]
567 #[test]
568 fn account_patch_with_state_change_requires_nonce_update(
569 #[case] storage: AccountStoragePatch,
570 #[case] vault: AccountVaultPatch,
571 #[case] code: AccountCodePatch,
572 ) -> anyhow::Result<()> {
573 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
574
575 let error = AccountPatch::new(account_id, storage, vault, code, None).unwrap_err();
576 assert_matches!(error, AccountPatchError::StateChangeRequiresNonceUpdate);
577
578 Ok(())
579 }
580
581 #[test]
584 fn account_patch_new_account_requires_code() -> anyhow::Result<()> {
585 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
586
587 let error = AccountPatch::new(
588 account_id,
589 AccountStoragePatch::new(),
590 AccountVaultPatch::default(),
591 AccountCodePatch::default(),
592 Some(Felt::ONE),
593 )
594 .unwrap_err();
595 assert_matches!(error, AccountPatchError::CodeMustBeProvidedForNewAccounts);
596
597 AccountPatch::new(
599 account_id,
600 AccountStoragePatch::new(),
601 AccountVaultPatch::default(),
602 AccountCodePatch::new(Some(AccountCode::mock())),
603 Some(Felt::ONE),
604 )?;
605
606 Ok(())
607 }
608
609 #[test]
612 fn account_patch_roundtrip() -> anyhow::Result<()> {
613 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
614 let code = AccountCode::mock();
615 let asset = FungibleAsset::mock(42);
616
617 let slot_name = StorageSlotName::mock(4);
618 let slot_value = Word::from([1, 2, 3, 4u32]);
619
620 let storage_patch = AccountStoragePatch::from_entries([(
622 slot_name.clone(),
623 StorageSlotPatch::Value(StorageValuePatch::Create { value: slot_value }),
624 )])?;
625
626 let patch = AccountPatch::new(
627 account_id,
628 storage_patch,
629 AccountVaultPatch::with_assets([asset]),
630 AccountCodePatch::new(Some(code.clone())),
631 Some(Felt::ONE),
632 )?;
633
634 let account = patch.try_to_new_account()?;
635
636 assert_eq!(account.id(), account_id);
637 assert_eq!(account.code(), &code);
638 assert_eq!(account.nonce(), Felt::ONE);
639 assert_eq!(account.storage().get_item(&slot_name)?, slot_value);
640 assert_eq!(account.vault().get(asset.id()), Some(asset));
641
642 let roundtripped_patch = AccountPatch::try_from(account)?;
644 assert_eq!(roundtripped_patch, patch);
645
646 Ok(())
647 }
648
649 #[rstest::rstest]
652 #[case::missing_code(Some(Felt::from(2_u32)))]
653 #[case::empty_patch(None)]
654 #[test]
655 fn account_patch_try_to_new_account_requires_code(
656 #[case] final_nonce: Option<Felt>,
657 ) -> anyhow::Result<()> {
658 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
659
660 let patch = AccountPatch::new(
661 account_id,
662 AccountStoragePatch::new(),
663 AccountVaultPatch::default(),
664 AccountCodePatch::default(),
665 final_nonce,
666 )?;
667 assert_matches!(
668 patch.try_to_new_account().unwrap_err(),
669 AccountError::NewAccountRequiresCodeAndNonce
670 );
671
672 Ok(())
673 }
674
675 #[rstest]
679 #[case::update(
680 AccountStoragePatch::builder().update_value(StorageSlotName::mock(1), Word::empty()).build()
681 )]
682 #[case::remove(
683 AccountStoragePatch::builder().remove_value(StorageSlotName::mock(1)).build()
684 )]
685 fn account_patch_try_to_new_account_rejects_non_create_op(
686 #[case] storage: AccountStoragePatch,
687 ) -> anyhow::Result<()> {
688 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
689
690 let patch = AccountPatch::new(
691 account_id,
692 storage,
693 AccountVaultPatch::default(),
694 AccountCodePatch::new(Some(AccountCode::mock())),
695 Some(Felt::from(2u32)),
696 )?;
697 assert_matches!(
698 patch.try_to_new_account().unwrap_err(),
699 AccountError::NewAccountStorageRequiresCreateOps
700 );
701
702 Ok(())
703 }
704
705 #[test]
707 fn account_patch_try_to_new_account_with_create_reconstructs() -> anyhow::Result<()> {
708 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
709 let code = AccountCode::mock();
710 let created_slot = StorageSlotName::mock(1);
711 let created_value = Word::from([7u32, 0, 0, 0]);
712
713 let storage = AccountStoragePatch::builder()
714 .create_value(created_slot.clone(), created_value)
715 .build();
716
717 let patch = AccountPatch::new(
718 account_id,
719 storage,
720 AccountVaultPatch::default(),
721 AccountCodePatch::new(Some(code.clone())),
722 Some(Felt::ONE),
723 )?;
724
725 let account = patch.try_to_new_account()?;
726 assert_eq!(account.code(), &code);
727 assert_eq!(account.storage().get_item(&created_slot)?, created_value);
728
729 Ok(())
730 }
731
732 fn upgraded_code() -> anyhow::Result<AccountCode> {
737 Ok(AccountCode::from_components(&[NoopAuthComponent.into(), AddComponent.into()])?)
738 }
739
740 fn update_patch(account_id: AccountId, final_nonce: u32) -> anyhow::Result<AccountPatch> {
742 let storage = AccountStoragePatch::from_iters(
743 [],
744 [(StorageSlotName::mock(1), Word::from([1u32, 0, 0, 0]))],
745 [],
746 );
747 AccountPatch::new(
748 account_id,
749 storage,
750 AccountVaultPatch::default(),
751 AccountCodePatch::default(),
752 Some(Felt::from(final_nonce)),
753 )
754 .map_err(Into::into)
755 }
756
757 #[test]
758 fn account_patch_merge_rejects_id_mismatch() -> anyhow::Result<()> {
759 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
760 let other_account_id =
761 AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE)?;
762
763 let mut patch = update_patch(account_id, 2)?;
764 let other = update_patch(other_account_id, 3)?;
765
766 assert_matches!(
767 patch.merge(other).unwrap_err(),
768 AccountPatchError::AccountIdMismatch { expected, actual } => {
769 assert_eq!(expected, account_id);
770 assert_eq!(actual, other_account_id);
771 }
772 );
773
774 Ok(())
775 }
776
777 #[test]
780 fn account_patch_merge_replaces_code() -> anyhow::Result<()> {
781 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
782 let code = upgraded_code()?;
783 let updated_slot = StorageSlotName::mock(2);
784 let updated_value = Word::from([2u32, 0, 0, 0]);
785
786 let mut patch = update_patch(account_id, 2)?;
787 let other = AccountPatch::new(
788 account_id,
789 AccountStoragePatch::builder()
790 .update_value(updated_slot.clone(), updated_value)
791 .build(),
792 AccountVaultPatch::default(),
793 AccountCodePatch::new(Some(code.clone())),
794 Some(Felt::from(3u32)),
795 )?;
796
797 patch.merge(other)?;
798
799 assert_eq!(patch.code().as_code(), Some(&code));
800 assert_eq!(patch.final_nonce(), Some(Felt::from(3u32)));
801 assert_eq!(patch.storage().num_slots(), 2);
802 assert_eq!(patch.storage().updated_value(&updated_slot), Some(updated_value));
803
804 Ok(())
805 }
806
807 #[rstest::rstest]
808 #[case::equal(3, 3)]
809 #[case::smaller(3, 2)]
810 fn account_patch_merge_rejects_non_increasing_nonce(
811 #[case] self_nonce: u32,
812 #[case] other_nonce: u32,
813 ) -> anyhow::Result<()> {
814 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
815 let mut patch = update_patch(account_id, self_nonce)?;
816 let other = update_patch(account_id, other_nonce)?;
817
818 assert_matches!(
819 patch.merge(other).unwrap_err(),
820 AccountPatchError::NonceMustIncrease { current, new } => {
821 assert_eq!(current, Felt::from(self_nonce));
822 assert_eq!(new, Felt::from(other_nonce));
823 }
824 );
825
826 Ok(())
827 }
828
829 #[test]
830 fn account_patch_merge_rejects_storage_slot_type_conflict() -> anyhow::Result<()> {
831 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
832 let shared_slot = StorageSlotName::mock(7);
833
834 let value_storage = AccountStoragePatch::from_iters(
835 [],
836 [(shared_slot.clone(), Word::from([9u32, 0, 0, 0]))],
837 [],
838 );
839 let map_storage = AccountStoragePatch::from_iters(
840 [],
841 [],
842 [(shared_slot.clone(), StorageMapPatch::from_iters([], []))],
843 );
844
845 let mut patch = AccountPatch::new(
846 account_id,
847 value_storage,
848 AccountVaultPatch::default(),
849 AccountCodePatch::default(),
850 Some(Felt::from(2u32)),
851 )?;
852 let other = AccountPatch::new(
853 account_id,
854 map_storage,
855 AccountVaultPatch::default(),
856 AccountCodePatch::default(),
857 Some(Felt::from(3u32)),
858 )?;
859
860 assert_matches!(
861 patch.merge(other).unwrap_err(),
862 AccountPatchError::StorageSlotUsedAsDifferentTypes(slot) => {
863 assert_eq!(slot, shared_slot);
864 }
865 );
866
867 Ok(())
868 }
869
870 #[test]
871 fn account_patch_merge_overrides_vault_entry() -> anyhow::Result<()> {
872 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
873 let asset_initial: Asset = FungibleAsset::mock(100);
874 let asset_updated: Asset = FungibleAsset::mock(250);
875 assert_eq!(asset_initial.id(), asset_updated.id());
876
877 let mut patch = AccountPatch::new(
878 account_id,
879 AccountStoragePatch::new(),
880 AccountVaultPatch::with_assets([asset_initial]),
881 AccountCodePatch::default(),
882 Some(Felt::from(2u32)),
883 )?;
884 let other = AccountPatch::new(
885 account_id,
886 AccountStoragePatch::new(),
887 AccountVaultPatch::with_assets([asset_updated]),
888 AccountCodePatch::default(),
889 Some(Felt::from(3u32)),
890 )?;
891
892 patch.merge(other)?;
893
894 assert_eq!(patch.vault().num_assets(), 1);
895 assert_eq!(
896 patch.vault().as_map().get(&asset_updated.id()).copied(),
897 Some(asset_updated.to_value_word())
898 );
899
900 Ok(())
901 }
902
903 #[test]
904 fn account_patch_merge_overrides_storage_value() -> anyhow::Result<()> {
905 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
906 let slot_name = StorageSlotName::mock(1);
907 let initial_value = Word::from([1u32, 0, 0, 0]);
908 let updated_value = Word::from([2u32, 0, 0, 0]);
909
910 let mut patch = AccountPatch::new(
911 account_id,
912 AccountStoragePatch::from_iters([], [(slot_name.clone(), initial_value)], []),
913 AccountVaultPatch::default(),
914 AccountCodePatch::default(),
915 Some(Felt::from(2u32)),
916 )?;
917 let other = AccountPatch::new(
918 account_id,
919 AccountStoragePatch::from_iters([], [(slot_name.clone(), updated_value)], []),
920 AccountVaultPatch::default(),
921 AccountCodePatch::default(),
922 Some(Felt::from(3u32)),
923 )?;
924
925 patch.merge(other)?;
926
927 assert_eq!(patch.storage().num_slots(), 1);
928 assert_eq!(patch.storage().updated_value(&slot_name), Some(updated_value));
929
930 Ok(())
931 }
932
933 #[test]
934 fn account_patch_merge_extends_storage_map() -> anyhow::Result<()> {
935 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
936 let map_slot = StorageSlotName::mock(1);
937 let key_self = StorageMapKey::from_array([1, 0, 0, 0]);
938 let value_self = Word::from([10u32, 0, 0, 0]);
939 let key_other = StorageMapKey::from_array([2, 0, 0, 0]);
940 let value_other = Word::from([20u32, 0, 0, 0]);
941
942 let mut patch = AccountPatch::new(
943 account_id,
944 AccountStoragePatch::from_iters(
945 [],
946 [],
947 [(map_slot.clone(), StorageMapPatch::from_iters([], [(key_self, value_self)]))],
948 ),
949 AccountVaultPatch::default(),
950 AccountCodePatch::default(),
951 Some(Felt::from(2u32)),
952 )?;
953 let other = AccountPatch::new(
954 account_id,
955 AccountStoragePatch::from_iters(
956 [],
957 [],
958 [(map_slot.clone(), StorageMapPatch::from_iters([], [(key_other, value_other)]))],
959 ),
960 AccountVaultPatch::default(),
961 AccountCodePatch::default(),
962 Some(Felt::from(3u32)),
963 )?;
964
965 patch.merge(other)?;
966
967 assert_eq!(patch.storage().num_slots(), 1);
968 assert_eq!(patch.storage().updated_map(&map_slot).unwrap().num_entries(), 2);
969 assert_eq!(patch.storage().updated_map_item(&map_slot, &key_self), Some(value_self));
970 assert_eq!(patch.storage().updated_map_item(&map_slot, &key_other), Some(value_other));
971
972 Ok(())
973 }
974
975 #[test]
978 fn account_patch_merge_creation_base_stays_creation_patch() -> anyhow::Result<()> {
979 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
980 let code = AccountCode::mock();
981 let slot_name = StorageSlotName::mock(1);
982 let created_value = Word::from([1u32, 0, 0, 0]);
983 let updated_value = Word::from([2u32, 0, 0, 0]);
984
985 let mut patch = AccountPatch::new(
987 account_id,
988 AccountStoragePatch::builder()
989 .create_value(slot_name.clone(), created_value)
990 .build(),
991 AccountVaultPatch::default(),
992 AccountCodePatch::new(Some(code.clone())),
993 Some(Felt::ONE),
994 )?;
995
996 let other = AccountPatch::new(
998 account_id,
999 AccountStoragePatch::builder()
1000 .update_value(slot_name.clone(), updated_value)
1001 .build(),
1002 AccountVaultPatch::default(),
1003 AccountCodePatch::default(),
1004 Some(Felt::from(2u32)),
1005 )?;
1006
1007 patch.merge(other)?;
1008
1009 assert_eq!(patch.code().as_code(), Some(&code));
1010 assert_eq!(patch.final_nonce(), Some(Felt::from(2u32)));
1011 assert!(!patch.storage().contains_non_create_ops());
1012 assert_eq!(patch.storage().created_value(&slot_name), Some(updated_value));
1013
1014 Ok(())
1015 }
1016
1017 #[test]
1019 fn account_patch_merge_empty_other_is_noop() -> anyhow::Result<()> {
1020 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
1021 let mut patch = update_patch(account_id, 4)?;
1022 let snapshot = patch.clone();
1023
1024 let empty = AccountPatch::empty(account_id);
1025
1026 patch.merge(empty)?;
1027 assert_eq!(patch, snapshot);
1028
1029 Ok(())
1030 }
1031
1032 #[test]
1034 fn account_patch_merge_empty_self_adopts_other() -> anyhow::Result<()> {
1035 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
1036 let mut empty = AccountPatch::empty(account_id);
1037 let other = update_patch(account_id, 7)?;
1038 let expected = other.clone();
1039
1040 empty.merge(other)?;
1041 assert_eq!(empty, expected);
1042
1043 Ok(())
1044 }
1045}