miden_objects/conversion/
account_patch.rs1use alloc::borrow::ToOwned;
2use alloc::collections::BTreeMap;
3use alloc::format;
4use alloc::sync::Arc;
5use alloc::vec::Vec;
6
7use miden_protocol::Word;
8use miden_protocol::account::{
9 AccountCode,
10 AccountPatch,
11 AccountProcedureRoot,
12 AccountStoragePatch,
13 AccountUpdateDetails,
14 AccountVaultPatch,
15 StorageMapKey,
16 StorageMapPatch,
17 StorageMapPatchEntries,
18 StoragePatchOperation,
19 StorageSlotName,
20 StorageSlotPatch,
21 StorageValuePatch,
22};
23use miden_protocol::asset::AssetId;
24
25use super::{MessageDecodeExt, required};
26use crate::{ConversionError, ConversionResultExt, proto};
27
28impl From<&AccountCode> for proto::account::AccountCode {
32 fn from(code: &AccountCode) -> Self {
33 Self {
34 mast: Some(code.mast().as_ref().into()),
35 procedure_roots: code.procedure_roots().map(Into::into).collect(),
36 }
37 }
38}
39
40impl From<AccountCode> for proto::account::AccountCode {
41 fn from(code: AccountCode) -> Self {
42 Self::from(&code)
43 }
44}
45
46impl TryFrom<proto::account::AccountCode> for AccountCode {
47 type Error = ConversionError;
48
49 fn try_from(code: proto::account::AccountCode) -> Result<Self, Self::Error> {
50 let decoder = code.decoder();
51 let mast = required!(decoder, code.mast)?;
52 let procedure_roots = code
53 .procedure_roots
54 .into_iter()
55 .enumerate()
56 .map(|(index, root)| {
57 Word::try_from(root)
58 .map(AccountProcedureRoot::from_raw)
59 .context(format!("procedure_roots[{index}]"))
60 })
61 .collect::<Result<Vec<_>, _>>()?;
62
63 AccountCode::from_parts(Arc::new(mast), procedure_roots).map_err(ConversionError::new)
64 }
65}
66
67const fn encode_storage_operation(operation: StoragePatchOperation) -> i32 {
71 match operation {
72 StoragePatchOperation::Create => proto::account::StoragePatchOperation::Create as i32,
73 StoragePatchOperation::Update => proto::account::StoragePatchOperation::Update as i32,
74 StoragePatchOperation::Remove => proto::account::StoragePatchOperation::Remove as i32,
75 }
76}
77
78fn decode_storage_operation(operation: i32) -> Result<StoragePatchOperation, ConversionError> {
79 match proto::account::StoragePatchOperation::try_from(operation) {
80 Ok(proto::account::StoragePatchOperation::Create) => Ok(StoragePatchOperation::Create),
81 Ok(proto::account::StoragePatchOperation::Update) => Ok(StoragePatchOperation::Update),
82 Ok(proto::account::StoragePatchOperation::Remove) => Ok(StoragePatchOperation::Remove),
83 Ok(proto::account::StoragePatchOperation::Unspecified) => {
84 Err(ConversionError::message("storage patch operation is unspecified"))
85 },
86 Err(_) => {
87 Err(ConversionError::message(format!("unknown storage patch operation {operation}")))
88 },
89 }
90}
91
92impl From<&StorageValuePatch> for proto::account::StorageValuePatch {
93 fn from(patch: &StorageValuePatch) -> Self {
94 Self {
95 operation: encode_storage_operation(patch.patch_op()),
96 value: patch.value().map(Into::into),
97 }
98 }
99}
100
101impl TryFrom<proto::account::StorageValuePatch> for StorageValuePatch {
102 type Error = ConversionError;
103
104 fn try_from(patch: proto::account::StorageValuePatch) -> Result<Self, Self::Error> {
105 let operation = decode_storage_operation(patch.operation).context("operation")?;
106 match operation {
107 StoragePatchOperation::Create | StoragePatchOperation::Update => {
108 let decoder = patch.decoder();
109 let value = required!(decoder, patch.value)?;
110 Ok(if operation.is_create() {
111 StorageValuePatch::Create { value }
112 } else {
113 StorageValuePatch::Update { value }
114 })
115 },
116 StoragePatchOperation::Remove => {
117 if patch.value.is_some() {
118 return Err(ConversionError::message(
119 "value must be absent for a remove operation",
120 )
121 .context("value"));
122 }
123 Ok(StorageValuePatch::Remove)
124 },
125 }
126 }
127}
128
129impl From<&StorageMapPatch> for proto::account::StorageMapPatch {
130 fn from(patch: &StorageMapPatch) -> Self {
131 let entries = patch
132 .entries()
133 .into_iter()
134 .flat_map(StorageMapPatchEntries::as_map)
135 .map(|(key, value)| proto::account::StorageMapEntry {
136 key: Some(Word::from(*key).into()),
137 value: Some((*value).into()),
138 })
139 .collect();
140
141 Self {
142 operation: encode_storage_operation(patch.patch_op()),
143 entries,
144 }
145 }
146}
147
148impl TryFrom<proto::account::StorageMapPatch> for StorageMapPatch {
149 type Error = ConversionError;
150
151 fn try_from(patch: proto::account::StorageMapPatch) -> Result<Self, Self::Error> {
152 let operation = decode_storage_operation(patch.operation).context("operation")?;
153 if operation.is_remove() {
154 if !patch.entries.is_empty() {
155 return Err(ConversionError::message(
156 "entries must be empty for a remove operation",
157 )
158 .context("entries"));
159 }
160 return Ok(StorageMapPatch::Remove);
161 }
162
163 let mut entries = BTreeMap::new();
164 for (index, entry) in patch.entries.into_iter().enumerate() {
165 let decoder = entry.decoder();
166 let entry_context = format!("entries[{index}]");
167 let key = StorageMapKey::from_raw(
168 required!(decoder, entry.key).context(entry_context.clone())?,
169 );
170 let value = required!(decoder, entry.value).context(entry_context.clone())?;
171 if entries.insert(key, value).is_some() {
172 return Err(ConversionError::message("duplicate storage map key")
173 .context(format!("{entry_context}.key")));
174 }
175 }
176
177 let entries = StorageMapPatchEntries::from_raw(entries);
178 match operation {
179 StoragePatchOperation::Create => Ok(StorageMapPatch::Create { entries }),
180 StoragePatchOperation::Update if entries.is_empty() => {
181 Err(ConversionError::message("entries must be non-empty for an update operation")
182 .context("entries"))
183 },
184 StoragePatchOperation::Update => Ok(StorageMapPatch::Update { entries }),
185 StoragePatchOperation::Remove => unreachable!("remove handled above"),
186 }
187 }
188}
189
190impl From<&AccountStoragePatch> for proto::account::AccountStoragePatch {
191 fn from(patch: &AccountStoragePatch) -> Self {
192 Self {
193 slots: patch
194 .slots()
195 .map(|(slot_name, slot_patch)| {
196 use proto::account::storage_slot_patch::Patch;
197
198 let patch = match slot_patch {
199 StorageSlotPatch::Value(value) => Patch::Value(value.into()),
200 StorageSlotPatch::Map(map) => Patch::Map(map.into()),
201 };
202 proto::account::StorageSlotPatch {
203 slot_name: slot_name.as_str().to_owned(),
204 patch: Some(patch),
205 }
206 })
207 .collect(),
208 }
209 }
210}
211
212impl TryFrom<proto::account::AccountStoragePatch> for AccountStoragePatch {
213 type Error = ConversionError;
214
215 fn try_from(patch: proto::account::AccountStoragePatch) -> Result<Self, Self::Error> {
216 use proto::account::storage_slot_patch::Patch;
217
218 let slots = patch
219 .slots
220 .into_iter()
221 .enumerate()
222 .map(|(index, slot)| {
223 let slot_path = format!("slots[{index}]");
224 let slot_name = StorageSlotName::new(slot.slot_name)
225 .map_err(ConversionError::from)
226 .context("slot_name")
227 .context(slot_path.clone())?;
228 let patch = match slot.patch {
229 Some(Patch::Value(value)) => StorageSlotPatch::Value(
230 value.try_into().context("patch").context(slot_path.clone())?,
231 ),
232 Some(Patch::Map(map)) => StorageSlotPatch::Map(
233 map.try_into().context("patch").context(slot_path.clone())?,
234 ),
235 None => {
236 return Err(ConversionError::missing_field::<
237 proto::account::StorageSlotPatch,
238 >("patch")
239 .context(slot_path));
240 },
241 };
242 Ok((slot_name, patch))
243 })
244 .collect::<Result<Vec<_>, ConversionError>>()?;
245
246 AccountStoragePatch::from_entries(slots)
247 .map_err(ConversionError::new)
248 .context("slots")
249 }
250}
251
252fn decode_account_patch_version(version: i32) -> Result<(), ConversionError> {
256 match proto::account::AccountPatchVersion::try_from(version) {
257 Ok(proto::account::AccountPatchVersion::V1) => Ok(()),
258 Ok(proto::account::AccountPatchVersion::Unspecified) => {
259 Err(ConversionError::message("account patch version is unspecified"))
260 },
261 Err(error) => Err(ConversionError::with_source(
262 format!("unknown account patch version {version}"),
263 error,
264 )),
265 }
266}
267
268impl From<&AccountVaultPatch> for proto::account::AccountVaultPatch {
269 fn from(patch: &AccountVaultPatch) -> Self {
270 Self {
271 entries: patch
272 .iter()
273 .map(|(asset_id, value)| proto::account::AccountVaultPatchEntry {
274 asset_id: Some(asset_id.to_word().into()),
275 value: Some((*value).into()),
276 })
277 .collect(),
278 }
279 }
280}
281
282impl TryFrom<proto::account::AccountVaultPatch> for AccountVaultPatch {
283 type Error = ConversionError;
284
285 fn try_from(patch: proto::account::AccountVaultPatch) -> Result<Self, Self::Error> {
286 let mut entries = BTreeMap::new();
287 for (index, entry) in patch.entries.into_iter().enumerate() {
288 let decoder = entry.decoder();
289 let asset_id: Word =
290 required!(decoder, entry.asset_id).context(format!("entries[{index}]"))?;
291 let asset_id = AssetId::try_from(asset_id)
292 .map_err(ConversionError::from)
293 .context("asset_id")
294 .context(format!("entries[{index}]"))?;
295 let value = required!(decoder, entry.value).context(format!("entries[{index}]"))?;
296 if entries.insert(asset_id, value).is_some() {
297 return Err(ConversionError::message("duplicate vault asset ID")
298 .context(format!("entries[{index}].asset_id")));
299 }
300 }
301
302 AccountVaultPatch::new(entries)
303 .map_err(ConversionError::from)
304 .context("entries")
305 }
306}
307
308impl From<&AccountPatch> for proto::account::AccountPatch {
309 fn from(patch: &AccountPatch) -> Self {
310 Self {
311 version: proto::account::AccountPatchVersion::V1 as i32,
312 account_id: Some(patch.id().into()),
313 storage: Some(patch.storage().into()),
314 vault: Some(patch.vault().into()),
315 code: patch.code().map(Into::into),
316 final_nonce: patch.final_nonce().map(Into::into),
317 }
318 }
319}
320
321impl From<AccountPatch> for proto::account::AccountPatch {
322 fn from(patch: AccountPatch) -> Self {
323 Self::from(&patch)
324 }
325}
326
327impl TryFrom<proto::account::AccountPatch> for AccountPatch {
328 type Error = ConversionError;
329
330 fn try_from(patch: proto::account::AccountPatch) -> Result<Self, Self::Error> {
331 decode_account_patch_version(patch.version).context("version")?;
332
333 let decoder = patch.decoder();
334 let account_id = required!(decoder, patch.account_id)?;
335 let storage = required!(decoder, patch.storage)?;
336 let vault = required!(decoder, patch.vault)?;
337 let code = patch.code.map(TryInto::try_into).transpose().context("code")?;
338 let final_nonce =
339 patch.final_nonce.map(TryInto::try_into).transpose().context("final_nonce")?;
340
341 AccountPatch::new(account_id, storage, vault, code, final_nonce)
342 .map_err(ConversionError::new)
343 }
344}
345
346impl From<&AccountUpdateDetails> for proto::account::AccountUpdateDetails {
347 fn from(details: &AccountUpdateDetails) -> Self {
348 use proto::account::account_update_details::Update;
349
350 let update = match details {
351 AccountUpdateDetails::Private => {
352 Update::Private(proto::account::PrivateAccountUpdate {})
353 },
354 AccountUpdateDetails::Public(patch) => Update::Public(patch.into()),
355 };
356 Self { update: Some(update) }
357 }
358}
359
360impl From<AccountUpdateDetails> for proto::account::AccountUpdateDetails {
361 fn from(details: AccountUpdateDetails) -> Self {
362 Self::from(&details)
363 }
364}
365
366impl TryFrom<proto::account::AccountUpdateDetails> for AccountUpdateDetails {
367 type Error = ConversionError;
368
369 fn try_from(details: proto::account::AccountUpdateDetails) -> Result<Self, Self::Error> {
370 use proto::account::account_update_details::Update;
371
372 match details.update {
373 Some(Update::Private(_)) => Ok(AccountUpdateDetails::Private),
374 Some(Update::Public(patch)) => {
375 patch.try_into().map(AccountUpdateDetails::Public).context("public")
376 },
377 None => Err(ConversionError::missing_field::<proto::account::AccountUpdateDetails>(
378 "update",
379 )),
380 }
381 }
382}