Skip to main content

miden_objects/conversion/
account_patch.rs

1use 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
28// ACCOUNT CODE
29// ================================================================================================
30
31impl 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
67// STORAGE PATCHES
68// ================================================================================================
69
70const 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
252// VAULT AND ACCOUNT PATCHES
253// ================================================================================================
254
255fn 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}