Skip to main content

miden_objects/conversion/
account.rs

1use alloc::collections::BTreeSet;
2use alloc::format;
3use alloc::string::ToString;
4use alloc::vec::Vec;
5
6use miden_protocol::account::{
7    AccountHeader,
8    AccountId,
9    AccountStorageHeader,
10    PartialAccount,
11    PartialStorage,
12    PartialStorageMap,
13    StorageMapKey,
14    StorageSlotHeader,
15    StorageSlotId,
16    StorageSlotName,
17    StorageSlotType,
18};
19use miden_protocol::asset::{AssetId, PartialVault};
20use miden_protocol::block::account_tree::AccountWitness;
21use miden_protocol::{Felt, Word};
22
23use super::{MessageDecodeExt, required};
24use crate::{ConversionError, ConversionResultExt, proto};
25
26impl TryFrom<proto::account::AccountId> for AccountId {
27    type Error = ConversionError;
28
29    fn try_from(message: proto::account::AccountId) -> Result<Self, Self::Error> {
30        let bytes: [u8; AccountId::SERIALIZED_SIZE] =
31            message.id.as_slice().try_into().map_err(ConversionError::new)?;
32
33        AccountId::try_from(bytes).map_err(ConversionError::new)
34    }
35}
36
37impl From<&AccountId> for proto::account::AccountId {
38    fn from(account_id: &AccountId) -> Self {
39        let id: [u8; AccountId::SERIALIZED_SIZE] = (*account_id).into();
40        Self { id: id.into() }
41    }
42}
43
44impl From<AccountId> for proto::account::AccountId {
45    fn from(account_id: AccountId) -> Self {
46        (&account_id).into()
47    }
48}
49
50// STORAGE SLOT ID
51// ================================================================================================
52
53impl TryFrom<proto::account::StorageSlotId> for StorageSlotId {
54    type Error = ConversionError;
55
56    fn try_from(message: proto::account::StorageSlotId) -> Result<Self, Self::Error> {
57        let decoder = message.decoder();
58        let suffix = required!(decoder, message.suffix)?;
59        let prefix = required!(decoder, message.prefix)?;
60        Ok(Self::new(suffix, prefix))
61    }
62}
63
64impl From<StorageSlotId> for proto::account::StorageSlotId {
65    fn from(id: StorageSlotId) -> Self {
66        Self {
67            suffix: Some(id.suffix().into()),
68            prefix: Some(id.prefix().into()),
69        }
70    }
71}
72
73impl From<&StorageSlotId> for proto::account::StorageSlotId {
74    fn from(id: &StorageSlotId) -> Self {
75        (*id).into()
76    }
77}
78
79/// Decodes a protobuf storage slot type into its domain representation.
80///
81/// Protobuf reserves discriminant 0 for an unspecified value, while the domain
82/// enum uses discriminants 0 and 1 for `Value` and `Map`, respectively.
83fn decode_storage_slot_type(slot_type: i32) -> Result<StorageSlotType, ConversionError> {
84    match proto::account::StorageSlotType::try_from(slot_type) {
85        Ok(proto::account::StorageSlotType::Value) => Ok(StorageSlotType::Value),
86        Ok(proto::account::StorageSlotType::Map) => Ok(StorageSlotType::Map),
87        Ok(proto::account::StorageSlotType::Unspecified) => {
88            Err(ConversionError::message("storage slot type is unspecified"))
89        },
90        Err(error) => Err(ConversionError::with_source(
91            format!("unknown storage slot type {slot_type}"),
92            error,
93        )),
94    }
95}
96
97/// Encodes a domain storage slot type using its protobuf representation.
98fn encode_storage_slot_type(slot_type: StorageSlotType) -> i32 {
99    match slot_type {
100        StorageSlotType::Value => proto::account::StorageSlotType::Value as i32,
101        StorageSlotType::Map => proto::account::StorageSlotType::Map as i32,
102    }
103}
104
105impl TryFrom<proto::account::AccountStorageHeader> for AccountStorageHeader {
106    type Error = ConversionError;
107
108    fn try_from(message: proto::account::AccountStorageHeader) -> Result<Self, Self::Error> {
109        let slots = message
110            .slots
111            .into_iter()
112            .map(|slot| {
113                let decoder = slot.decoder();
114                let name = StorageSlotName::new(slot.slot_name)?;
115                let slot_type = decode_storage_slot_type(slot.slot_type).context("slot_type")?;
116                let commitment = required!(decoder, slot.commitment)?;
117                Ok(StorageSlotHeader::new(name, slot_type, commitment))
118            })
119            .collect::<Result<Vec<_>, ConversionError>>()
120            .context("slots")?;
121        AccountStorageHeader::new(slots).map_err(ConversionError::new)
122    }
123}
124
125impl From<&AccountStorageHeader> for proto::account::AccountStorageHeader {
126    fn from(account_storage_header: &AccountStorageHeader) -> Self {
127        Self {
128            slots: account_storage_header
129                .slots()
130                .map(|slot| proto::account::account_storage_header::StorageSlot {
131                    slot_name: slot.name().to_string(),
132                    slot_type: encode_storage_slot_type(slot.slot_type()),
133                    commitment: Some(slot.value().into()),
134                })
135                .collect(),
136        }
137    }
138}
139
140impl From<AccountStorageHeader> for proto::account::AccountStorageHeader {
141    fn from(account_storage_header: AccountStorageHeader) -> Self {
142        (&account_storage_header).into()
143    }
144}
145
146fn decode_account_version(version: i32) -> Result<(), ConversionError> {
147    match proto::account::AccountVersion::try_from(version) {
148        Ok(proto::account::AccountVersion::V1) => Ok(()),
149        Ok(proto::account::AccountVersion::Unspecified) => {
150            Err(ConversionError::message("account header version is unspecified"))
151        },
152        Err(error) => Err(ConversionError::with_source(
153            format!("unknown account header version {version}"),
154            error,
155        )),
156    }
157}
158
159// PARTIAL STORAGE MAP
160// ================================================================================================
161
162impl TryFrom<proto::account::PartialStorageMap> for PartialStorageMap {
163    type Error = ConversionError;
164
165    fn try_from(message: proto::account::PartialStorageMap) -> Result<Self, Self::Error> {
166        let decoder = message.decoder();
167        let smt = required!(decoder, message.smt)?;
168        let keys = message
169            .keys
170            .into_iter()
171            .enumerate()
172            .map(|(index, key)| {
173                Word::try_from(key)
174                    .map(StorageMapKey::from_raw)
175                    .context(format!("keys[{index}]"))
176            })
177            .collect::<Result<Vec<_>, _>>()?;
178
179        PartialStorageMap::try_from_parts(smt, keys).map_err(ConversionError::new)
180    }
181}
182
183impl From<&PartialStorageMap> for proto::account::PartialStorageMap {
184    fn from(map: &PartialStorageMap) -> Self {
185        Self {
186            smt: Some(map.partial_smt().clone().into()),
187            keys: map.entries().map(|(key, _)| Word::from(*key).into()).collect(),
188        }
189    }
190}
191
192impl From<PartialStorageMap> for proto::account::PartialStorageMap {
193    fn from(map: PartialStorageMap) -> Self {
194        (&map).into()
195    }
196}
197
198// PARTIAL STORAGE
199// ================================================================================================
200
201impl TryFrom<proto::account::PartialStorage> for PartialStorage {
202    type Error = ConversionError;
203
204    fn try_from(message: proto::account::PartialStorage) -> Result<Self, Self::Error> {
205        let decoder = message.decoder();
206        let header = required!(decoder, message.header)?;
207        let mut roots = BTreeSet::new();
208        let maps = message
209            .maps
210            .into_iter()
211            .enumerate()
212            .map(|(index, map)| {
213                let map_context = format!("maps[{index}]");
214                let map = PartialStorageMap::try_from(map).context(&map_context)?;
215                if !roots.insert(map.root()) {
216                    return Err(ConversionError::message("duplicate partial storage map root")
217                        .context(map_context));
218                }
219                Ok(map)
220            })
221            .collect::<Result<Vec<_>, _>>()?;
222
223        PartialStorage::new(header, maps).map_err(ConversionError::new)
224    }
225}
226
227impl From<&PartialStorage> for proto::account::PartialStorage {
228    fn from(storage: &PartialStorage) -> Self {
229        Self {
230            header: Some(storage.header().into()),
231            maps: storage.maps().map(Into::into).collect(),
232        }
233    }
234}
235
236impl From<PartialStorage> for proto::account::PartialStorage {
237    fn from(storage: PartialStorage) -> Self {
238        (&storage).into()
239    }
240}
241
242// PARTIAL VAULT
243// ================================================================================================
244
245impl TryFrom<proto::account::PartialVault> for PartialVault {
246    type Error = ConversionError;
247
248    fn try_from(message: proto::account::PartialVault) -> Result<Self, Self::Error> {
249        let decoder = message.decoder();
250        let smt = required!(decoder, message.smt)?;
251        let asset_ids = message
252            .asset_ids
253            .into_iter()
254            .enumerate()
255            .map(|(index, id)| {
256                let asset_id_context = format!("asset_ids[{index}]");
257                Word::try_from(id)
258                    .context(&asset_id_context)
259                    .and_then(|id| AssetId::try_from(id).context(asset_id_context))
260            })
261            .collect::<Result<Vec<_>, _>>()?;
262
263        PartialVault::try_from_parts(smt, asset_ids).map_err(ConversionError::new)
264    }
265}
266
267impl From<&PartialVault> for proto::account::PartialVault {
268    fn from(vault: &PartialVault) -> Self {
269        Self {
270            smt: Some(vault.partial_smt().clone().into()),
271            asset_ids: vault.asset_ids().map(|id| Word::from(id).into()).collect(),
272        }
273    }
274}
275
276impl From<PartialVault> for proto::account::PartialVault {
277    fn from(vault: PartialVault) -> Self {
278        (&vault).into()
279    }
280}
281
282// PARTIAL ACCOUNT
283// ================================================================================================
284
285impl TryFrom<proto::account::PartialAccount> for PartialAccount {
286    type Error = ConversionError;
287
288    fn try_from(message: proto::account::PartialAccount) -> Result<Self, Self::Error> {
289        let decoder = message.decoder();
290        let account_id = required!(decoder, message.account_id)?;
291        let nonce = required!(decoder, message.nonce)?;
292        let code = required!(decoder, message.code)?;
293        let storage = required!(decoder, message.storage)?;
294        let vault = required!(decoder, message.vault)?;
295        let seed = message.seed.map(Word::try_from).transpose().context("seed")?;
296
297        PartialAccount::new(account_id, nonce, code, storage, vault, seed)
298            .map_err(ConversionError::new)
299    }
300}
301
302impl From<&PartialAccount> for proto::account::PartialAccount {
303    fn from(account: &PartialAccount) -> Self {
304        Self {
305            account_id: Some(account.id().into()),
306            nonce: Some(account.nonce().into()),
307            code: Some(account.code().into()),
308            storage: Some(account.storage().into()),
309            vault: Some(account.vault().into()),
310            seed: account.seed().map(Into::into),
311        }
312    }
313}
314
315impl From<PartialAccount> for proto::account::PartialAccount {
316    fn from(account: PartialAccount) -> Self {
317        (&account).into()
318    }
319}
320
321impl TryFrom<proto::account::AccountHeader> for AccountHeader {
322    type Error = ConversionError;
323
324    fn try_from(message: proto::account::AccountHeader) -> Result<Self, Self::Error> {
325        decode_account_version(message.version).context("version")?;
326
327        let decoder = message.decoder();
328        let account_id = required!(decoder, message.account_id)?;
329        let vault_root = required!(decoder, message.vault_root)?;
330        let storage_commitment = required!(decoder, message.storage_commitment)?;
331        let code_commitment = required!(decoder, message.code_commitment)?;
332        let nonce = Felt::try_from(message.nonce).map_err(ConversionError::new).context("nonce")?;
333        Ok(AccountHeader::new(
334            account_id,
335            nonce,
336            vault_root,
337            storage_commitment,
338            code_commitment,
339        ))
340    }
341}
342
343impl From<&AccountHeader> for proto::account::AccountHeader {
344    fn from(account_header: &AccountHeader) -> Self {
345        Self {
346            version: proto::account::AccountVersion::V1 as i32,
347            account_id: Some(account_header.id().into()),
348            vault_root: Some(account_header.vault_root().into()),
349            storage_commitment: Some(account_header.storage_commitment().into()),
350            code_commitment: Some(account_header.code_commitment().into()),
351            nonce: account_header.nonce().as_canonical_u64(),
352        }
353    }
354}
355
356impl From<AccountHeader> for proto::account::AccountHeader {
357    fn from(account_header: AccountHeader) -> Self {
358        (&account_header).into()
359    }
360}
361
362impl TryFrom<proto::account::AccountWitness> for AccountWitness {
363    type Error = ConversionError;
364
365    fn try_from(message: proto::account::AccountWitness) -> Result<Self, Self::Error> {
366        let decoder = message.decoder();
367        let witness_id = required!(decoder, message.witness_id)?;
368        let commitment = required!(decoder, message.commitment)?;
369        let path = required!(decoder, message.path)?;
370
371        AccountWitness::new(witness_id, commitment, path).map_err(ConversionError::new)
372    }
373}
374
375impl From<&AccountWitness> for proto::account::AccountWitness {
376    fn from(witness: &AccountWitness) -> Self {
377        Self {
378            witness_id: Some(witness.id().into()),
379            commitment: Some(witness.state_commitment().into()),
380            path: Some(witness.path().clone().into()),
381        }
382    }
383}
384
385impl From<AccountWitness> for proto::account::AccountWitness {
386    fn from(witness: AccountWitness) -> Self {
387        (&witness).into()
388    }
389}