miden_objects/account/component/template/storage/
toml.rs

1use alloc::{
2    string::{String, ToString},
3    vec::Vec,
4};
5use core::fmt;
6
7use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
8use vm_core::Felt;
9use vm_processor::Digest;
10
11use super::{
12    FeltRepresentation, MapRepresentation, StorageEntry, StoragePlaceholder, WordRepresentation,
13};
14use crate::{
15    account::AccountComponentMetadata, errors::AccountComponentTemplateError,
16    utils::parse_hex_string_as_word,
17};
18
19// ACCOUNT COMPONENT METADATA TOML FROM/TO
20// ================================================================================================
21
22impl AccountComponentMetadata {
23    /// Deserializes `toml_string` and validates the resulting [AccountComponentMetadata]
24    ///
25    /// # Errors
26    ///
27    /// - If deserialization fails
28    /// - If the template specifies storage slots with duplicates.
29    /// - If the template includes slot numbers that do not start at zero.
30    /// - If storage slots in the template are not contiguous.
31    pub fn from_toml(toml_string: &str) -> Result<Self, AccountComponentTemplateError> {
32        let component: AccountComponentMetadata = toml::from_str(toml_string)
33            .map_err(AccountComponentTemplateError::DeserializationError)?;
34        component.validate()?;
35        Ok(component)
36    }
37
38    /// Serializes the account component template into a TOML string.
39    pub fn as_toml(&self) -> Result<String, AccountComponentTemplateError> {
40        let toml = toml::to_string(self).unwrap();
41        Ok(toml)
42    }
43}
44
45// WORD REPRESENTATION SERIALIZATION
46// ================================================================================================
47
48impl serde::Serialize for WordRepresentation {
49    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
50    where
51        S: serde::Serializer,
52    {
53        use serde::ser::SerializeSeq;
54        match self {
55            WordRepresentation::Value(word) => {
56                // Ensure that the length of the vector is exactly 4
57                let word = Digest::from(word);
58                serializer.serialize_str(&word.to_string())
59            },
60            WordRepresentation::Array(words) => {
61                let mut seq = serializer.serialize_seq(Some(4))?;
62                for word in words {
63                    seq.serialize_element(word)?;
64                }
65                seq.end()
66            },
67            WordRepresentation::Template(key) => key.serialize(serializer),
68        }
69    }
70}
71
72impl<'de> serde::Deserialize<'de> for WordRepresentation {
73    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
74    where
75        D: serde::Deserializer<'de>,
76    {
77        use serde::de::{Error, SeqAccess, Visitor};
78        struct WordRepresentationVisitor;
79
80        impl<'de> Visitor<'de> for WordRepresentationVisitor {
81            type Value = WordRepresentation;
82
83            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
84                formatter.write_str("a single hex/decimal Word or an array of 4 elements")
85            }
86
87            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
88            where
89                E: Error,
90            {
91                // Attempt to deserialize as storage placeholder first
92                if let Ok(tk) = StoragePlaceholder::try_from(value) {
93                    return Ok(WordRepresentation::Template(tk));
94                }
95
96                // try hex parsing otherwise
97                let word = parse_hex_string_as_word(value).map_err(|_err| {
98                    E::invalid_value(
99                        serde::de::Unexpected::Str(value),
100                        &"a valid hexadecimal string or storage placeholder (in '{{key}}' format)",
101                    )
102                })?;
103
104                Ok(WordRepresentation::Value(word))
105            }
106
107            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
108            where
109                A: SeqAccess<'de>,
110            {
111                let mut elements = Vec::with_capacity(4);
112                while let Some(felt_repr) = seq.next_element::<FeltRepresentation>()? {
113                    elements.push(felt_repr);
114                }
115
116                if elements.len() == 4 {
117                    let array: [FeltRepresentation; 4] =
118                        elements.clone().try_into().map_err(|_| {
119                            Error::invalid_length(
120                                elements.len(),
121                                &"expected an array of 4 elements",
122                            )
123                        })?;
124                    Ok(WordRepresentation::Array(array))
125                } else {
126                    Err(Error::invalid_length(elements.len(), &"expected an array of 4 elements"))
127                }
128            }
129        }
130
131        deserializer.deserialize_any(WordRepresentationVisitor)
132    }
133}
134
135// FELT REPRESENTATION SERIALIZATION
136// ================================================================================================
137
138impl<'de> serde::Deserialize<'de> for FeltRepresentation {
139    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
140    where
141        D: serde::Deserializer<'de>,
142    {
143        let value = String::deserialize(deserializer)?;
144        if let Some(hex_str) = value.strip_prefix("0x").or_else(|| value.strip_prefix("0X")) {
145            let felt_value = u64::from_str_radix(hex_str, 16).map_err(serde::de::Error::custom)?;
146            Ok(FeltRepresentation::Hexadecimal(Felt::new(felt_value)))
147        } else if let Ok(decimal_value) = value.parse::<u64>() {
148            Ok(FeltRepresentation::Decimal(
149                Felt::try_from(decimal_value).map_err(serde::de::Error::custom)?,
150            ))
151        } else if let Ok(key) = StoragePlaceholder::try_from(&value) {
152            Ok(FeltRepresentation::Template(key))
153        } else {
154            Err(serde::de::Error::custom(
155                "deserialized string value is not a valid variant of FeltRepresentation",
156            ))
157        }
158    }
159}
160
161impl serde::Serialize for FeltRepresentation {
162    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
163    where
164        S: serde::Serializer,
165    {
166        match self {
167            FeltRepresentation::Hexadecimal(felt) => {
168                let output = format!("0x{:x}", felt.as_int());
169                serializer.serialize_str(&output)
170            },
171            FeltRepresentation::Decimal(felt) => {
172                let output = felt.as_int().to_string();
173                serializer.serialize_str(&output)
174            },
175            FeltRepresentation::Template(key) => key.serialize(serializer),
176        }
177    }
178}
179
180// KEY SERIALIZATION
181// ================================================================================================
182
183impl serde::Serialize for StoragePlaceholder {
184    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
185    where
186        S: serde::Serializer,
187    {
188        serializer.serialize_str(&self.to_string())
189    }
190}
191
192impl<'de> serde::Deserialize<'de> for StoragePlaceholder {
193    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
194    where
195        D: serde::Deserializer<'de>,
196    {
197        let s = String::deserialize(deserializer)?;
198        StoragePlaceholder::try_from(s.as_str()).map_err(serde::de::Error::custom)
199    }
200}
201
202// STORAGE VALUES
203// ================================================================================================
204
205/// Represents the type of values that can be found in a storage slot's `values` field.
206#[derive(serde::Deserialize, serde::Serialize)]
207#[serde(untagged)]
208enum StorageValues {
209    /// List of individual words (for multi-slot entries).
210    Words(Vec<WordRepresentation>),
211    /// List of key-value entries (for map storage slots).
212    MapEntries(MapRepresentation),
213}
214
215impl StorageValues {
216    pub fn is_list_of_words(&self) -> bool {
217        match self {
218            StorageValues::Words(_) => true,
219            StorageValues::MapEntries(_) => false,
220        }
221    }
222
223    pub fn into_words(self) -> Option<Vec<WordRepresentation>> {
224        match self {
225            StorageValues::Words(vec) => Some(vec),
226            StorageValues::MapEntries(_) => None,
227        }
228    }
229
230    pub fn into_map_entries(self) -> Option<MapRepresentation> {
231        match self {
232            StorageValues::Words(_) => None,
233            StorageValues::MapEntries(map) => Some(map),
234        }
235    }
236
237    pub fn len(&self) -> Option<usize> {
238        match self {
239            StorageValues::Words(vec) => Some(vec.len()),
240            StorageValues::MapEntries(map) => map.len(),
241        }
242    }
243}
244
245// STORAGE ENTRY SERIALIZATION
246// ================================================================================================
247
248/// Used as a helper for validating and (de)serializing storage entries
249#[derive(Default, Deserialize, Serialize)]
250struct RawStorageEntry {
251    name: String,
252    description: Option<String>,
253    slot: Option<u8>,
254    slots: Option<Vec<u8>>,
255    value: Option<WordRepresentation>,
256    values: Option<StorageValues>,
257}
258
259impl From<StorageEntry> for RawStorageEntry {
260    fn from(entry: StorageEntry) -> Self {
261        match entry {
262            StorageEntry::Value { name, description, slot, value } => RawStorageEntry {
263                name,
264                description,
265                slot: Some(slot),
266                value: Some(value),
267                ..Default::default()
268            },
269            StorageEntry::Map { name, description, slot, map: values } => RawStorageEntry {
270                name,
271                description,
272                slot: Some(slot),
273                values: Some(StorageValues::MapEntries(values)),
274                ..Default::default()
275            },
276            StorageEntry::MultiSlot { name, description, slots, values } => RawStorageEntry {
277                name,
278                description,
279                slots: Some(slots),
280                values: Some(StorageValues::Words(values)),
281                ..Default::default()
282            },
283        }
284    }
285}
286
287impl Serialize for StorageEntry {
288    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
289    where
290        S: Serializer,
291    {
292        let raw_storage_entry: RawStorageEntry = self.clone().into();
293        raw_storage_entry.serialize(serializer)
294    }
295}
296
297impl<'de> Deserialize<'de> for StorageEntry {
298    fn deserialize<D>(deserializer: D) -> Result<StorageEntry, D::Error>
299    where
300        D: Deserializer<'de>,
301    {
302        let raw = RawStorageEntry::deserialize(deserializer)?;
303
304        // Determine presence of fields and do early validation
305        let slot_present = raw.slot.is_some();
306        let value_present = raw.value.is_some();
307
308        // Use a match on the combination of presence flags to choose variant
309        match (raw.slots, raw.values) {
310            (None, None) => {
311                // Expect a Value variant: "slot" and "value" must be present
312                // "slots" and "values" must not be present.
313                Ok(StorageEntry::Value {
314                    name: raw.name,
315                    description: raw.description,
316                    slot: raw
317                        .slot
318                        .ok_or(D::Error::custom("missing 'slot' field for single-slot entry"))?,
319                    value: raw
320                        .value
321                        .ok_or(D::Error::custom("missing 'value' field for single-slot entry"))?,
322                })
323            },
324            (Some(_), None) => {
325                Err(D::Error::custom("`slots` is defined but no `values` field was found"))
326            },
327            (None, Some(values)) => {
328                // Expect a Map variant:
329                //   - `slot` must be present
330                //   - `values` must be present and convertible to map entries
331                //   - `slots` must not be present
332                //   - `value` must not be present
333                if value_present {
334                    return Err(D::Error::custom(
335                        "fields 'value' and 'values' are mutually exclusive",
336                    ));
337                }
338
339                let map_entries = values
340                    .into_map_entries()
341                    .ok_or_else(|| D::Error::custom("invalid 'values' for map entry"))?;
342
343                Ok(StorageEntry::Map {
344                    name: raw.name,
345                    description: raw.description,
346                    slot: raw.slot.ok_or(D::Error::missing_field("slot"))?,
347                    map: map_entries,
348                })
349            },
350            (Some(slots), Some(values)) => {
351                // Expect a MultiSlot variant:
352                //   - `slots` must be present
353                //   - `values` must be present and represent words
354                //   - `slot` must not be present
355                //   - `value` must not be present
356                if slot_present {
357                    return Err(D::Error::custom(
358                        "fields 'slot' and 'slots' are mutually exclusive",
359                    ));
360                }
361                if value_present {
362                    return Err(D::Error::custom(
363                        "fields 'value' and 'values' are mutually exclusive",
364                    ));
365                }
366
367                let has_list_of_values = values.is_list_of_words();
368                if has_list_of_values {
369                    let slots_count = slots.len();
370                    let values_count = values.len().expect("checked that it's a list of values");
371                    if slots_count != values_count {
372                        return Err(D::Error::custom(format!(
373                            "number of slots ({}) does not match number of values ({}) for multi-slot storage entry",
374                            slots_count, values_count
375                        )));
376                    }
377                }
378
379                Ok(StorageEntry::MultiSlot {
380                    name: raw.name,
381                    description: raw.description,
382                    slots,
383                    values: values
384                        .into_words()
385                        .ok_or_else(|| D::Error::custom("invalid values for multi-slot"))?,
386                })
387            },
388        }
389    }
390}