miden_protocol/account/patch/
update_details.rs1use crate::account::{Account, AccountId, AccountPatch};
2use crate::errors::{
3 AccountPatchError,
4 AccountUpdateDetailsValidationError,
5 AccountUpdateSizeValidationError,
6 NewPublicAccountValidationError,
7};
8use crate::utils::serde::{
9 ByteReader,
10 ByteWriter,
11 Deserializable,
12 DeserializationError,
13 Serializable,
14};
15use crate::{ACCOUNT_UPDATE_MAX_SIZE, Word};
16
17#[derive(Clone, Debug, PartialEq, Eq)]
30pub enum AccountUpdateDetails {
31 Private,
33
34 Public(AccountPatch),
37}
38
39impl AccountUpdateDetails {
40 const PRIVATE_TAG: u8 = 0;
41 const PUBLIC_TAG: u8 = 1;
42
43 pub fn is_private(&self) -> bool {
45 matches!(self, Self::Private)
46 }
47
48 pub fn is_public(&self) -> bool {
50 matches!(self, Self::Public(_))
51 }
52
53 pub(crate) fn validate_size(
56 &self,
57 account_id: AccountId,
58 ) -> Result<(), AccountUpdateSizeValidationError> {
59 let update_size = self.get_size_hint();
60 if update_size > ACCOUNT_UPDATE_MAX_SIZE as usize {
61 return Err(AccountUpdateSizeValidationError { account_id, update_size });
62 }
63
64 Ok(())
65 }
66
67 pub(crate) fn validate_for_account(
72 &self,
73 account_id: AccountId,
74 ) -> Result<Option<&AccountPatch>, AccountUpdateDetailsValidationError> {
75 match (self, account_id.is_private()) {
76 (Self::Private, true) => Ok(None),
77 (Self::Public(_), true) => {
78 Err(AccountUpdateDetailsValidationError::PrivateAccountWithDetails(account_id))
79 },
80 (Self::Private, false) => Err(
81 AccountUpdateDetailsValidationError::PublicStateAccountMissingDetails(account_id),
82 ),
83 (Self::Public(patch), false) if patch.id() != account_id => {
84 Err(AccountUpdateDetailsValidationError::AccountIdMismatch {
85 account_id,
86 patch_account_id: patch.id(),
87 })
88 },
89 (Self::Public(patch), false) => Ok(Some(patch)),
90 }
91 }
92
93 pub fn merge(self, other: AccountUpdateDetails) -> Result<Self, AccountPatchError> {
98 let merged_update = match (self, other) {
99 (AccountUpdateDetails::Private, AccountUpdateDetails::Private) => {
100 AccountUpdateDetails::Private
101 },
102 (AccountUpdateDetails::Public(mut patch), AccountUpdateDetails::Public(new_patch)) => {
103 patch.merge(new_patch)?;
104 AccountUpdateDetails::Public(patch)
105 },
106 (left, right) => {
107 return Err(AccountPatchError::IncompatibleAccountUpdates {
108 left_update_type: left.as_tag_str(),
109 right_update_type: right.as_tag_str(),
110 });
111 },
112 };
113
114 Ok(merged_update)
115 }
116
117 pub(crate) const fn as_tag_str(&self) -> &'static str {
119 match self {
120 AccountUpdateDetails::Private => "private",
121 AccountUpdateDetails::Public(_) => "public",
122 }
123 }
124}
125
126pub(crate) fn validate_new_public_account(
129 patch: &AccountPatch,
130 final_state_commitment: Word,
131) -> Result<(), NewPublicAccountValidationError> {
132 let account = Account::try_from(patch).map_err(|source| {
133 NewPublicAccountValidationError::RequiresFullStatePatch { id: patch.id(), source }
134 })?;
135 let account_commitment = account.to_commitment();
136 if account_commitment != final_state_commitment {
137 return Err(NewPublicAccountValidationError::FinalCommitmentMismatch {
138 final_state_commitment,
139 account_commitment,
140 });
141 }
142
143 Ok(())
144}
145
146impl Serializable for AccountUpdateDetails {
150 fn write_into<W: ByteWriter>(&self, target: &mut W) {
151 match self {
152 AccountUpdateDetails::Private => {
153 Self::PRIVATE_TAG.write_into(target);
154 },
155 AccountUpdateDetails::Public(public) => {
156 Self::PUBLIC_TAG.write_into(target);
157 public.write_into(target);
158 },
159 }
160 }
161
162 fn get_size_hint(&self) -> usize {
163 let u8_size = 0u8.get_size_hint();
165
166 match self {
167 AccountUpdateDetails::Private => u8_size,
168 AccountUpdateDetails::Public(public) => u8_size + public.get_size_hint(),
169 }
170 }
171}
172
173impl Deserializable for AccountUpdateDetails {
174 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
175 match u8::read_from(source)? {
176 Self::PRIVATE_TAG => Ok(Self::Private),
177 Self::PUBLIC_TAG => Ok(Self::Public(AccountPatch::read_from(source)?)),
178 variant => Err(DeserializationError::InvalidValue(format!(
179 "Unknown variant {variant} for AccountUpdateDetails"
180 ))),
181 }
182 }
183}
184
185#[cfg(test)]
189mod tests {
190 use super::AccountUpdateDetails;
191 use crate::account::{
192 AccountCode,
193 AccountId,
194 AccountPatch,
195 AccountStoragePatch,
196 AccountVaultPatch,
197 StorageMapKey,
198 StorageSlotName,
199 };
200 use crate::asset::{Asset, FungibleAsset, NonFungibleAsset};
201 use crate::testing::account_id::ACCOUNT_ID_PRIVATE_SENDER;
202 use crate::utils::serde::Serializable;
203 use crate::{ONE, Word};
204
205 #[test]
206 fn account_update_details_size_hint() -> anyhow::Result<()> {
207 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?;
208
209 let storage_patch = AccountStoragePatch::builder()
211 .create_value(StorageSlotName::mock(2), Word::from([1, 1, 1, 1u32]))
212 .create_value(StorageSlotName::mock(3), Word::from([1, 1, 0, 1u32]))
213 .create_map(
214 StorageSlotName::mock(4),
215 [(StorageMapKey::from_array([1, 1, 1, 1]), Word::from([1, 1, 1, 1u32]))],
216 )
217 .build();
218
219 let non_fungible: Asset = NonFungibleAsset::mock(&[6]);
220 let fungible: Asset = FungibleAsset::mock(42);
221 let vault_patch = AccountVaultPatch::with_assets([non_fungible, fungible]);
222
223 let account_patch = AccountPatch::new(
224 account_id,
225 storage_patch,
226 vault_patch,
227 Some(AccountCode::mock()),
228 Some(ONE),
229 )?;
230
231 let update_details_private = AccountUpdateDetails::Private;
232 assert_eq!(update_details_private.to_bytes().len(), update_details_private.get_size_hint());
233
234 let update_details_patch = AccountUpdateDetails::Public(account_patch);
235 assert_eq!(update_details_patch.to_bytes().len(), update_details_patch.get_size_hint());
236
237 assert!(update_details_patch.get_size_hint() > update_details_private.get_size_hint());
240
241 Ok(())
242 }
243}