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

1use alloc::collections::BTreeMap;
2use alloc::string::{String, ToString};
3use alloc::vec::Vec;
4use core::fmt;
5
6use miden_core::{Felt, Word};
7use serde::de::value::MapAccessDeserializer;
8use serde::de::{self, Error, MapAccess, SeqAccess, Visitor};
9use serde::ser::{SerializeMap, SerializeStruct};
10use serde::{Deserialize, Deserializer, Serialize, Serializer};
11use thiserror::Error;
12
13use super::placeholder::TemplateType;
14use super::{
15    FeltRepresentation,
16    InitStorageData,
17    MapEntry,
18    MapRepresentation,
19    MultiWordRepresentation,
20    StorageEntry,
21    StorageValueNameError,
22    WordRepresentation,
23};
24use crate::account::component::FieldIdentifier;
25use crate::account::component::template::storage::placeholder::{TEMPLATE_REGISTRY, TemplateFelt};
26use crate::account::{AccountComponentMetadata, StorageValueName};
27use crate::errors::AccountComponentTemplateError;
28
29// ACCOUNT COMPONENT METADATA TOML FROM/TO
30// ================================================================================================
31
32impl AccountComponentMetadata {
33    /// Deserializes `toml_string` and validates the resulting [AccountComponentMetadata]
34    ///
35    /// # Errors
36    ///
37    /// - If deserialization fails
38    /// - If the template specifies storage slots with duplicates.
39    /// - If the template includes slot numbers that do not start at zero.
40    /// - If storage slots in the template are not contiguous.
41    pub fn from_toml(toml_string: &str) -> Result<Self, AccountComponentTemplateError> {
42        let component: AccountComponentMetadata = toml::from_str(toml_string)
43            .map_err(AccountComponentTemplateError::TomlDeserializationError)?;
44
45        component.validate()?;
46        Ok(component)
47    }
48
49    /// Serializes the account component template into a TOML string.
50    pub fn to_toml(&self) -> Result<String, AccountComponentTemplateError> {
51        let toml =
52            toml::to_string(self).map_err(AccountComponentTemplateError::TomlSerializationError)?;
53        Ok(toml)
54    }
55}
56
57// WORD REPRESENTATION SERIALIZATION
58// ================================================================================================
59
60impl Serialize for WordRepresentation {
61    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
62    where
63        S: Serializer,
64    {
65        match self {
66            WordRepresentation::Template { identifier, r#type } => {
67                let mut state = serializer.serialize_struct("WordRepresentation", 3)?;
68                state.serialize_field("name", &identifier.name())?;
69                state.serialize_field("description", &identifier.description())?;
70                state.serialize_field("type", r#type)?;
71                state.end()
72            },
73            WordRepresentation::Value { identifier, value } => {
74                let mut state = serializer.serialize_struct("WordRepresentation", 3)?;
75
76                state.serialize_field("name", &identifier.as_ref().map(|id| id.name()))?;
77                state.serialize_field(
78                    "description",
79                    &identifier.as_ref().map(|id| id.description()),
80                )?;
81                state.serialize_field("value", value)?;
82                state.end()
83            },
84        }
85    }
86}
87
88impl<'de> Deserialize<'de> for WordRepresentation {
89    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
90    where
91        D: Deserializer<'de>,
92    {
93        struct WordRepresentationVisitor;
94
95        impl<'de> Visitor<'de> for WordRepresentationVisitor {
96            type Value = WordRepresentation;
97
98            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
99                formatter.write_str("a string or a map representing a WordRepresentation")
100            }
101
102            // A bare string is interpreted it as a Value variant.
103            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
104            where
105                E: Error,
106            {
107                let parsed_value = Word::parse(value).map_err(|_err| {
108                    E::invalid_value(
109                        serde::de::Unexpected::Str(value),
110                        &"a valid hexadecimal string",
111                    )
112                })?;
113                Ok(<[Felt; _]>::from(&parsed_value).into())
114            }
115
116            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
117            where
118                E: Error,
119            {
120                self.visit_str(&value)
121            }
122
123            fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
124            where
125                A: SeqAccess<'de>,
126            {
127                // Deserialize as a list of felt representations
128                let elements: Vec<FeltRepresentation> =
129                    Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))?;
130                if elements.len() != 4 {
131                    return Err(Error::invalid_length(
132                        elements.len(),
133                        &"expected an array of 4 elements",
134                    ));
135                }
136                let value: [FeltRepresentation; 4] =
137                    elements.try_into().expect("length was checked");
138                Ok(WordRepresentation::new_value(value, None))
139            }
140
141            fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
142            where
143                M: MapAccess<'de>,
144            {
145                #[derive(Deserialize, Debug)]
146                struct WordRepresentationHelper {
147                    name: Option<String>,
148                    description: Option<String>,
149                    // The "value" field (if present) must be an array of 4 FeltRepresentations.
150                    value: Option<[FeltRepresentation; 4]>,
151                    #[serde(rename = "type")]
152                    r#type: Option<TemplateType>,
153                }
154
155                let helper =
156                    WordRepresentationHelper::deserialize(MapAccessDeserializer::new(map))?;
157
158                if let Some(value) = helper.value {
159                    let identifier = helper
160                        .name
161                        .map(|n| parse_field_identifier::<M::Error>(n, helper.description.clone()))
162                        .transpose()?;
163                    Ok(WordRepresentation::Value { value, identifier })
164                } else {
165                    // Otherwise, we expect a Template variant (name is required for identification)
166                    let identifier = expect_parse_field_identifier::<M::Error>(
167                        helper.name,
168                        helper.description,
169                        "word template",
170                    )?;
171                    let r#type = helper.r#type.unwrap_or_else(TemplateType::native_word);
172                    Ok(WordRepresentation::Template { r#type, identifier })
173                }
174            }
175        }
176
177        deserializer.deserialize_any(WordRepresentationVisitor)
178    }
179}
180
181// FELT REPRESENTATION SERIALIZATION
182// ================================================================================================
183
184impl Serialize for FeltRepresentation {
185    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
186    where
187        S: Serializer,
188    {
189        match self {
190            FeltRepresentation::Value { identifier, value } => {
191                let hex = value.to_string();
192                if identifier.is_none() {
193                    serializer.serialize_str(&hex)
194                } else {
195                    let mut state = serializer.serialize_struct("FeltRepresentation", 3)?;
196                    if let Some(id) = identifier {
197                        state.serialize_field("name", &id.name)?;
198                        state.serialize_field("description", &id.description)?;
199                    }
200                    state.serialize_field("value", &hex)?;
201                    state.end()
202                }
203            },
204            FeltRepresentation::Template { identifier, r#type } => {
205                let mut state = serializer.serialize_struct("FeltRepresentation", 3)?;
206                state.serialize_field("name", &identifier.name)?;
207                state.serialize_field("description", &identifier.description)?;
208                state.serialize_field("type", r#type)?;
209                state.end()
210            },
211        }
212    }
213}
214
215impl<'de> Deserialize<'de> for FeltRepresentation {
216    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
217    where
218        D: Deserializer<'de>,
219    {
220        // Felts can be deserialized as either:
221        //
222        // - Scalars (parsed from strings)
223        // - A table object that can or cannot hardcode a value. If not present, this is a
224        //   placeholder type
225        #[derive(Deserialize)]
226        #[serde(untagged)]
227        enum Intermediate {
228            Map {
229                name: Option<String>,
230                description: Option<String>,
231                #[serde(default)]
232                value: Option<String>,
233                #[serde(rename = "type")]
234                r#type: Option<TemplateType>,
235            },
236            Scalar(String),
237        }
238
239        let intermediate = Intermediate::deserialize(deserializer)?;
240        match intermediate {
241            Intermediate::Scalar(s) => {
242                let felt = Felt::parse_felt(&s)
243                    .map_err(|e| D::Error::custom(format!("failed to parse Felt: {e}")))?;
244                Ok(FeltRepresentation::Value { identifier: None, value: felt })
245            },
246            Intermediate::Map { name, description, value, r#type } => {
247                // Get the defined type, or the default if it was not specified
248                let felt_type = r#type.unwrap_or_else(TemplateType::native_felt);
249                if let Some(val_str) = value {
250                    // Parse into felt from the input string
251                    let felt =
252                        TEMPLATE_REGISTRY.try_parse_felt(&felt_type, &val_str).map_err(|e| {
253                            D::Error::custom(format!("failed to parse {felt_type} as Felt: {e}"))
254                        })?;
255                    let identifier = name
256                        .map(|n| parse_field_identifier::<D::Error>(n, description.clone()))
257                        .transpose()?;
258                    Ok(FeltRepresentation::Value { identifier, value: felt })
259                } else {
260                    // No value provided, so this is a placeholder
261                    let identifier = expect_parse_field_identifier::<D::Error>(
262                        name,
263                        description,
264                        "map template",
265                    )?;
266                    Ok(FeltRepresentation::Template { r#type: felt_type, identifier })
267                }
268            },
269        }
270    }
271}
272
273// STORAGE VALUES
274// ================================================================================================
275
276/// Represents the type of values that can be found in a storage slot's `values` field.
277#[derive(Debug, Deserialize, Serialize)]
278#[serde(untagged)]
279enum StorageValues {
280    /// List of individual words (for multi-slot entries).
281    Words(Vec<[FeltRepresentation; 4]>),
282    /// List of key-value entries (for map storage slots).
283    MapEntries(Vec<MapEntry>),
284}
285
286// STORAGE ENTRY SERIALIZATION
287// ================================================================================================
288
289#[derive(Default, Debug, Deserialize, Serialize)]
290struct RawStorageEntry {
291    #[serde(flatten)]
292    identifier: Option<FieldIdentifier>,
293    slot: Option<u8>,
294    slots: Option<Vec<u8>>,
295    #[serde(rename = "type")]
296    word_type: Option<TemplateType>,
297    value: Option<[FeltRepresentation; 4]>,
298    values: Option<StorageValues>,
299}
300
301impl From<StorageEntry> for RawStorageEntry {
302    fn from(entry: StorageEntry) -> Self {
303        match entry {
304            StorageEntry::Value { slot, word_entry } => match word_entry {
305                WordRepresentation::Value { identifier, value } => RawStorageEntry {
306                    slot: Some(slot),
307                    identifier,
308                    value: Some(value),
309                    ..Default::default()
310                },
311                WordRepresentation::Template { identifier, r#type } => RawStorageEntry {
312                    slot: Some(slot),
313                    identifier: Some(identifier),
314                    word_type: Some(r#type),
315                    ..Default::default()
316                },
317            },
318            StorageEntry::Map { slot, map } => match map {
319                MapRepresentation::Value { identifier, entries } => RawStorageEntry {
320                    slot: Some(slot),
321                    identifier: Some(FieldIdentifier {
322                        name: identifier.name,
323                        description: identifier.description,
324                    }),
325                    values: Some(StorageValues::MapEntries(entries)),
326                    ..Default::default()
327                },
328                MapRepresentation::Template { identifier } => RawStorageEntry {
329                    slot: Some(slot),
330                    identifier: Some(FieldIdentifier {
331                        name: identifier.name,
332                        description: identifier.description,
333                    }),
334                    word_type: Some(TemplateType::storage_map()),
335                    ..Default::default()
336                },
337            },
338            StorageEntry::MultiSlot { slots, word_entries } => match word_entries {
339                MultiWordRepresentation::Value { identifier, values } => RawStorageEntry {
340                    slot: None,
341                    identifier: Some(identifier),
342                    slots: Some(slots.collect()),
343                    values: Some(StorageValues::Words(values)),
344                    ..Default::default()
345                },
346            },
347        }
348    }
349}
350
351impl Serialize for StorageEntry {
352    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
353    where
354        S: Serializer,
355    {
356        let raw_storage_entry: RawStorageEntry = self.clone().into();
357        raw_storage_entry.serialize(serializer)
358    }
359}
360
361impl<'de> Deserialize<'de> for StorageEntry {
362    fn deserialize<D>(deserializer: D) -> Result<StorageEntry, D::Error>
363    where
364        D: Deserializer<'de>,
365    {
366        let raw = RawStorageEntry::deserialize(deserializer)?;
367
368        if let Some(word_entry) = raw.value {
369            // If a value was provided, this is a WordRepresentation::Value entry
370            let slot = raw.slot.ok_or_else(|| missing_field_for("slot", "value entry"))?;
371            let identifier = raw.identifier;
372            Ok(StorageEntry::Value {
373                slot,
374                word_entry: WordRepresentation::Value { value: word_entry, identifier },
375            })
376        } else if let Some(StorageValues::MapEntries(map_entries)) = raw.values {
377            // If `values` field contains key/value pairs, deserialize as map
378            let identifier =
379                raw.identifier.ok_or_else(|| missing_field_for("identifier", "map entry"))?;
380            let name = identifier.name;
381            let slot = raw.slot.ok_or_else(|| missing_field_for("slot", "map entry"))?;
382            if let Some(word_type) = raw.word_type.clone()
383                && word_type != TemplateType::storage_map()
384            {
385                return Err(D::Error::custom(
386                    "map storage entries with `values` must have `type = \"map\"`",
387                ));
388            }
389            let mut map = MapRepresentation::new_value(map_entries, name);
390            if let Some(desc) = identifier.description {
391                map = map.with_description(desc);
392            }
393            Ok(StorageEntry::Map { slot, map })
394        } else if let Some(word_type) = raw.word_type.clone()
395            && word_type == TemplateType::storage_map()
396        {
397            let identifier =
398                raw.identifier.ok_or_else(|| missing_field_for("identifier", "map entry"))?;
399            let slot = raw.slot.ok_or_else(|| missing_field_for("slot", "map entry"))?;
400            let FieldIdentifier { name, description } = identifier;
401
402            // If values is specified (even if empty), create a value map.
403            // Due to #[serde(untagged)] on StorageValues, values = [] gets deserialized
404            // as StorageValues::Words(vec![]), so we need to treat it as an empty map.
405            // Otherwise, create a template map.
406            let mut map = if raw.values.is_some() {
407                MapRepresentation::new_value(Vec::new(), name)
408            } else {
409                MapRepresentation::new_template(name)
410            };
411
412            if let Some(desc) = description {
413                map = map.with_description(desc);
414            }
415            Ok(StorageEntry::Map { slot, map })
416        } else if let Some(StorageValues::Words(values)) = raw.values {
417            let identifier = raw
418                .identifier
419                .ok_or_else(|| missing_field_for("identifier", "multislot entry"))?;
420
421            let mut slots =
422                raw.slots.ok_or_else(|| missing_field_for("slots", "multislot entry"))?;
423
424            // Sort so we can check contiguity
425            slots.sort_unstable();
426            for pair in slots.windows(2) {
427                if pair[1] != pair[0] + 1 {
428                    return Err(serde::de::Error::custom(format!(
429                        "`slots` in the `{}` storage entry are not contiguous",
430                        identifier.name
431                    )));
432                }
433            }
434            let start = slots[0];
435            let end = slots.last().expect("checked validity") + 1;
436            Ok(StorageEntry::new_multislot(identifier, start..end, values))
437        } else if let Some(word_type) = raw.word_type {
438            // If a type was provided instead, this is a WordRepresentation::Template entry
439            let slot = raw.slot.ok_or_else(|| missing_field_for("slot", "single-slot entry"))?;
440            let identifier = raw
441                .identifier
442                .ok_or_else(|| missing_field_for("identifier", "single-slot entry"))?;
443            let word_entry = WordRepresentation::Template { r#type: word_type, identifier };
444            Ok(StorageEntry::Value { slot, word_entry })
445        } else {
446            Err(D::Error::custom("placeholder storage entries require the `type` field"))
447        }
448    }
449}
450
451// INIT STORAGE DATA
452// ================================================================================================
453
454impl InitStorageData {
455    /// Creates an instance of [`InitStorageData`] from a TOML string.
456    ///
457    /// This method parses the provided TOML and flattens nested tables into
458    /// dot‑separated keys using [`StorageValueName`] as keys. All values are converted to plain
459    /// strings (so that, for example, `key = 10` and `key = "10"` both yield
460    /// `String::from("10")` as the value).
461    ///
462    /// # Errors
463    ///
464    /// - If duplicate keys or empty tables are found in the string
465    /// - If the TOML string includes arrays
466    pub fn from_toml(toml_str: &str) -> Result<Self, InitStorageDataError> {
467        let value: toml::Value = toml::from_str(toml_str)?;
468        let mut value_entries = BTreeMap::new();
469        let mut map_entries = BTreeMap::new();
470        // Start with an empty prefix (i.e. the default, which is an empty string)
471        Self::flatten_parse_toml_value(
472            StorageValueName::empty(),
473            value,
474            &mut value_entries,
475            &mut map_entries,
476        )?;
477
478        Ok(InitStorageData::new(value_entries, map_entries))
479    }
480
481    /// Recursively flattens a TOML `Value` into a flat mapping.
482    ///
483    /// When recursing into nested tables, keys are combined using
484    /// [`StorageValueName::with_suffix`]. If an encountered table is empty (and not the top-level),
485    /// an error is returned. Arrays are not supported.
486    fn flatten_parse_toml_value(
487        prefix: StorageValueName,
488        value: toml::Value,
489        value_entries: &mut BTreeMap<StorageValueName, String>,
490        map_entries: &mut BTreeMap<StorageValueName, Vec<(Word, Word)>>,
491    ) -> Result<(), InitStorageDataError> {
492        match value {
493            toml::Value::Table(table) => {
494                // If this is not the root and the table is empty, error
495                if !prefix.as_str().is_empty() && table.is_empty() {
496                    return Err(InitStorageDataError::EmptyTable(prefix.as_str().into()));
497                }
498                for (key, val) in table {
499                    // Create a new key and combine it with the current prefix.
500                    let new_key = StorageValueName::new(key.to_string())
501                        .map_err(InitStorageDataError::InvalidStorageValueName)?;
502                    let new_prefix = prefix.clone().with_suffix(&new_key);
503                    Self::flatten_parse_toml_value(new_prefix, val, value_entries, map_entries)?;
504                }
505            },
506            toml::Value::Array(items) if items.is_empty() => {
507                if prefix.as_str().is_empty() {
508                    return Err(InitStorageDataError::ArraysNotSupported);
509                }
510                map_entries.insert(prefix, Vec::new());
511            },
512            toml::Value::Array(items) => {
513                if prefix.as_str().is_empty()
514                    || !items.iter().all(|item| matches!(item, toml::Value::Table(_)))
515                {
516                    return Err(InitStorageDataError::ArraysNotSupported);
517                }
518
519                let entries = items
520                    .into_iter()
521                    .map(parse_map_entry_value)
522                    .collect::<Result<Vec<(Word, Word)>, _>>()?;
523                map_entries.insert(prefix, entries);
524            },
525            toml_value => {
526                // Get the string value, or convert to string if it's some other type
527                let value = match toml_value {
528                    toml::Value::String(s) => s.clone(),
529                    _ => toml_value.to_string(),
530                };
531                value_entries.insert(prefix, value);
532            },
533        }
534        Ok(())
535    }
536}
537
538#[derive(Debug, Error)]
539pub enum InitStorageDataError {
540    #[error("failed to parse TOML")]
541    InvalidToml(#[from] toml::de::Error),
542
543    #[error("empty table encountered for key `{0}`")]
544    EmptyTable(String),
545
546    #[error("invalid input: arrays are not supported")]
547    ArraysNotSupported,
548
549    #[error("invalid storage value name")]
550    InvalidStorageValueName(#[source] StorageValueNameError),
551
552    #[error("invalid map entry: {0}")]
553    InvalidMapEntry(String),
554}
555
556impl Serialize for FieldIdentifier {
557    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
558    where
559        S: Serializer,
560    {
561        let mut map = serializer.serialize_map(Some(2))?;
562        map.serialize_entry("name", &self.name)?;
563        map.serialize_entry("description", &self.description)?;
564        map.end()
565    }
566}
567
568struct FieldIdentifierVisitor;
569
570impl<'de> Visitor<'de> for FieldIdentifierVisitor {
571    type Value = FieldIdentifier;
572
573    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
574        formatter.write_str("a map with 'name' and optionally 'description'")
575    }
576
577    fn visit_map<M>(self, mut map: M) -> Result<FieldIdentifier, M::Error>
578    where
579        M: MapAccess<'de>,
580    {
581        let mut name = None;
582        let mut description = None;
583        while let Some(key) = map.next_key::<String>()? {
584            match key.as_str() {
585                "name" => {
586                    name = Some(map.next_value()?);
587                },
588                "description" => {
589                    let d: String = map.next_value()?;
590                    // Normalize empty or whitespace-only strings into None
591                    description = if d.trim().is_empty() { None } else { Some(d) };
592                },
593                _ => {
594                    // Ignore other values as FieldIdentifiers are flattened within other structs
595                    let _: de::IgnoredAny = map.next_value()?;
596                },
597            }
598        }
599        let name = name.ok_or_else(|| de::Error::missing_field("name"))?;
600        Ok(FieldIdentifier { name, description })
601    }
602}
603
604impl<'de> Deserialize<'de> for FieldIdentifier {
605    fn deserialize<D>(deserializer: D) -> Result<FieldIdentifier, D::Error>
606    where
607        D: Deserializer<'de>,
608    {
609        deserializer.deserialize_map(FieldIdentifierVisitor)
610    }
611}
612
613// UTILS / HELPERS
614// ================================================================================================
615
616fn missing_field_for<E: serde::de::Error>(field: &str, context: &str) -> E {
617    E::custom(format!("missing '{field}' field for {context}"))
618}
619
620/// Checks than an optional (but expected) name field has been defined and is correct.
621fn expect_parse_field_identifier<E: serde::de::Error>(
622    n: Option<String>,
623    description: Option<String>,
624    context: &str,
625) -> Result<FieldIdentifier, E> {
626    let name = n.ok_or_else(|| missing_field_for("name", context))?;
627    parse_field_identifier(name, description)
628}
629
630/// Tries to parse a string into a [FieldIdentifier].
631fn parse_field_identifier<E: serde::de::Error>(
632    n: String,
633    description: Option<String>,
634) -> Result<FieldIdentifier, E> {
635    StorageValueName::new(n)
636        .map_err(|err| E::custom(format!("invalid `name`: {err}")))
637        .map(|storage_name| {
638            if let Some(desc) = description {
639                FieldIdentifier::with_description(storage_name, desc)
640            } else {
641                FieldIdentifier::with_name(storage_name)
642            }
643        })
644}
645
646/// Parses a `{ key, value }` TOML table into a `(Word, Word)` pair, rejecting templates.
647fn parse_map_entry_value(item: toml::Value) -> Result<(Word, Word), InitStorageDataError> {
648    // Try to deserialize the user input as a map entry
649    let entry: MapEntry = MapEntry::deserialize(item)
650        .map_err(|err| InitStorageDataError::InvalidMapEntry(err.to_string()))?;
651
652    // Make sure the entry does not contain templates, only static
653    if entry.key().template_requirements(StorageValueName::empty()).next().is_some()
654        || entry.value().template_requirements(StorageValueName::empty()).next().is_some()
655    {
656        return Err(InitStorageDataError::InvalidMapEntry(
657            "map entries cannot contain templates".into(),
658        ));
659    }
660
661    // Interpret the user input as static words
662    let key = entry
663        .key()
664        .try_build_word(&InitStorageData::default(), StorageValueName::empty())
665        .map_err(|err| InitStorageDataError::InvalidMapEntry(err.to_string()))?;
666    let value = entry
667        .value()
668        .try_build_word(&InitStorageData::default(), StorageValueName::empty())
669        .map_err(|err| InitStorageDataError::InvalidMapEntry(err.to_string()))?;
670
671    Ok((key, value))
672}
673
674// TESTS
675// ================================================================================================
676
677#[cfg(test)]
678mod tests {
679    use alloc::string::ToString;
680    use core::error::Error;
681
682    use super::*;
683    use crate::account::component::toml::InitStorageDataError;
684
685    #[test]
686    fn from_toml_str_with_nested_table_and_flattened() {
687        let toml_table = r#"
688            [token_metadata]
689            max_supply = "1000000000"
690            symbol = "ETH"
691            decimals = "9"
692        "#;
693
694        let toml_inline = r#"
695            token_metadata.max_supply = "1000000000"
696            token_metadata.symbol = "ETH"
697            token_metadata.decimals = "9"
698        "#;
699
700        let storage_table = InitStorageData::from_toml(toml_table).unwrap();
701        let storage_inline = InitStorageData::from_toml(toml_inline).unwrap();
702
703        assert_eq!(storage_table.placeholders(), storage_inline.placeholders());
704    }
705
706    #[test]
707    fn from_toml_str_with_deeply_nested_tables() {
708        let toml_str = r#"
709            [a]
710            b = "0xb"
711
712            [a.c]
713            d = "0xd"
714
715            [x.y.z]
716            w = 42 # NOTE: This gets parsed as string
717        "#;
718
719        let storage = InitStorageData::from_toml(toml_str).expect("Failed to parse TOML");
720        let key1 = StorageValueName::new("a.b".to_string()).unwrap();
721        let key2 = StorageValueName::new("a.c.d".to_string()).unwrap();
722        let key3 = StorageValueName::new("x.y.z.w".to_string()).unwrap();
723
724        assert_eq!(storage.get(&key1).unwrap(), "0xb");
725        assert_eq!(storage.get(&key2).unwrap(), "0xd");
726        assert_eq!(storage.get(&key3).unwrap(), "42");
727    }
728
729    #[test]
730    fn test_error_on_array() {
731        let toml_str = r#"
732            token_metadata.v = [1, 2, 3]
733        "#;
734
735        let result = InitStorageData::from_toml(toml_str);
736        assert_matches::assert_matches!(
737            result.unwrap_err(),
738            InitStorageDataError::ArraysNotSupported
739        );
740    }
741
742    #[test]
743    fn parse_map_entries_from_array() {
744        let toml_str = r#"
745            my_map = [
746                { key = "0x0000000000000000000000000000000000000000000000000000000000000001", value = "0x0000000000000000000000000000000000000000000000000000000000000010" },
747                { key = "0x0000000000000000000000000000000000000000000000000000000000000002", value = ["1", "2", "3", "4"] }
748            ]
749        "#;
750
751        let storage = InitStorageData::from_toml(toml_str).expect("Failed to parse map entries");
752        let map_name = StorageValueName::new("my_map").unwrap();
753        let entries = storage.map_entries(&map_name).expect("map entries missing");
754        assert_eq!(entries.len(), 2);
755
756        let first_key =
757            Word::try_from("0x0000000000000000000000000000000000000000000000000000000000000001")
758                .unwrap();
759        assert_eq!(entries[0].0, first_key);
760
761        let second_value =
762            Word::from([Felt::new(1u64), Felt::new(2u64), Felt::new(3u64), Felt::new(4u64)]);
763        assert_eq!(entries[1].1, second_value);
764    }
765
766    #[test]
767    fn error_on_empty_subtable() {
768        let toml_str = r#"
769            [a]
770            b = {}
771        "#;
772
773        let result = InitStorageData::from_toml(toml_str);
774        assert_matches::assert_matches!(result.unwrap_err(), InitStorageDataError::EmptyTable(_));
775    }
776
777    #[test]
778    fn error_on_duplicate_keys() {
779        let toml_str = r#"
780            token_metadata.max_supply = "1000000000"
781            token_metadata.max_supply = "500000000"
782        "#;
783
784        let result = InitStorageData::from_toml(toml_str).unwrap_err();
785        // TOML does not support duplicate keys
786        assert_matches::assert_matches!(result, InitStorageDataError::InvalidToml(_));
787        assert!(result.source().unwrap().to_string().contains("duplicate"));
788    }
789}