1use alloc::collections::BTreeMap;
2use alloc::string::{String, ToString};
3use alloc::vec::Vec;
4use core::fmt;
5
6use miden_core::Felt;
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;
28use crate::utils::parse_hex_string_as_word;
29
30impl AccountComponentMetadata {
34 pub fn from_toml(toml_string: &str) -> Result<Self, AccountComponentTemplateError> {
43 let component: AccountComponentMetadata = toml::from_str(toml_string)
44 .map_err(AccountComponentTemplateError::TomlDeserializationError)?;
45
46 component.validate()?;
47 Ok(component)
48 }
49
50 pub fn as_toml(&self) -> Result<String, AccountComponentTemplateError> {
52 let toml =
53 toml::to_string(self).map_err(AccountComponentTemplateError::TomlSerializationError)?;
54 Ok(toml)
55 }
56}
57
58impl Serialize for WordRepresentation {
62 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
63 where
64 S: Serializer,
65 {
66 match self {
67 WordRepresentation::Template { identifier, r#type } => {
68 let mut state = serializer.serialize_struct("WordRepresentation", 3)?;
69 state.serialize_field("name", &identifier.name())?;
70 state.serialize_field("description", &identifier.description())?;
71 state.serialize_field("type", r#type)?;
72 state.end()
73 },
74 WordRepresentation::Value { identifier, value } => {
75 let mut state = serializer.serialize_struct("WordRepresentation", 3)?;
76
77 state.serialize_field("name", &identifier.as_ref().map(|id| id.name()))?;
78 state.serialize_field(
79 "description",
80 &identifier.as_ref().map(|id| id.description()),
81 )?;
82 state.serialize_field("value", value)?;
83 state.end()
84 },
85 }
86 }
87}
88
89impl<'de> Deserialize<'de> for WordRepresentation {
90 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
91 where
92 D: Deserializer<'de>,
93 {
94 struct WordRepresentationVisitor;
95
96 impl<'de> Visitor<'de> for WordRepresentationVisitor {
97 type Value = WordRepresentation;
98
99 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
100 formatter.write_str("a string or a map representing a WordRepresentation")
101 }
102
103 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
105 where
106 E: Error,
107 {
108 let parsed_value = parse_hex_string_as_word(value).map_err(|_err| {
109 E::invalid_value(
110 serde::de::Unexpected::Str(value),
111 &"a valid hexadecimal string",
112 )
113 })?;
114 Ok(parsed_value.into())
115 }
116
117 fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
118 where
119 E: Error,
120 {
121 self.visit_str(&value)
122 }
123
124 fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
125 where
126 A: SeqAccess<'de>,
127 {
128 let elements: Vec<FeltRepresentation> =
130 Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))?;
131 if elements.len() != 4 {
132 return Err(Error::invalid_length(
133 elements.len(),
134 &"expected an array of 4 elements",
135 ));
136 }
137 let value: [FeltRepresentation; 4] =
138 elements.try_into().expect("length was checked");
139 Ok(WordRepresentation::new_value(value, None))
140 }
141
142 fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
143 where
144 M: MapAccess<'de>,
145 {
146 #[derive(Deserialize, Debug)]
147 struct WordRepresentationHelper {
148 name: Option<String>,
149 description: Option<String>,
150 value: Option<[FeltRepresentation; 4]>,
152 #[serde(rename = "type")]
153 r#type: Option<TemplateType>,
154 }
155
156 let helper =
157 WordRepresentationHelper::deserialize(MapAccessDeserializer::new(map))?;
158
159 if let Some(value) = helper.value {
160 let identifier = helper
161 .name
162 .map(|n| parse_field_identifier::<M::Error>(n, helper.description.clone()))
163 .transpose()?;
164 Ok(WordRepresentation::Value { value, identifier })
165 } else {
166 let identifier = expect_parse_field_identifier::<M::Error>(
168 helper.name,
169 helper.description,
170 "word template",
171 )?;
172 let r#type = helper.r#type.unwrap_or_else(TemplateType::native_word);
173 Ok(WordRepresentation::Template { r#type, identifier })
174 }
175 }
176 }
177
178 deserializer.deserialize_any(WordRepresentationVisitor)
179 }
180}
181
182impl Serialize for FeltRepresentation {
186 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
187 where
188 S: Serializer,
189 {
190 match self {
191 FeltRepresentation::Value { identifier, value } => {
192 let hex = value.to_string();
193 if identifier.is_none() {
194 serializer.serialize_str(&hex)
195 } else {
196 let mut state = serializer.serialize_struct("FeltRepresentation", 3)?;
197 if let Some(id) = identifier {
198 state.serialize_field("name", &id.name)?;
199 state.serialize_field("description", &id.description)?;
200 }
201 state.serialize_field("value", &hex)?;
202 state.end()
203 }
204 },
205 FeltRepresentation::Template { identifier, r#type } => {
206 let mut state = serializer.serialize_struct("FeltRepresentation", 3)?;
207 state.serialize_field("name", &identifier.name)?;
208 state.serialize_field("description", &identifier.description)?;
209 state.serialize_field("type", r#type)?;
210 state.end()
211 },
212 }
213 }
214}
215
216impl<'de> Deserialize<'de> for FeltRepresentation {
217 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
218 where
219 D: Deserializer<'de>,
220 {
221 #[derive(Deserialize)]
227 #[serde(untagged)]
228 enum Intermediate {
229 Map {
230 name: Option<String>,
231 description: Option<String>,
232 #[serde(default)]
233 value: Option<String>,
234 #[serde(rename = "type")]
235 r#type: Option<TemplateType>,
236 },
237 Scalar(String),
238 }
239
240 let intermediate = Intermediate::deserialize(deserializer)?;
241 match intermediate {
242 Intermediate::Scalar(s) => {
243 let felt = Felt::parse_felt(&s)
244 .map_err(|e| D::Error::custom(format!("failed to parse Felt: {e}")))?;
245 Ok(FeltRepresentation::Value { identifier: None, value: felt })
246 },
247 Intermediate::Map { name, description, value, r#type } => {
248 let felt_type = r#type.unwrap_or_else(TemplateType::native_felt);
250 if let Some(val_str) = value {
251 let felt =
253 TEMPLATE_REGISTRY.try_parse_felt(&felt_type, &val_str).map_err(|e| {
254 D::Error::custom(format!("failed to parse {felt_type} as Felt: {e}"))
255 })?;
256 let identifier = name
257 .map(|n| parse_field_identifier::<D::Error>(n, description.clone()))
258 .transpose()?;
259 Ok(FeltRepresentation::Value { identifier, value: felt })
260 } else {
261 let identifier = expect_parse_field_identifier::<D::Error>(
263 name,
264 description,
265 "map template",
266 )?;
267 Ok(FeltRepresentation::Template { r#type: felt_type, identifier })
268 }
269 },
270 }
271 }
272}
273
274#[derive(Debug, Deserialize, Serialize)]
279#[serde(untagged)]
280enum StorageValues {
281 Words(Vec<[FeltRepresentation; 4]>),
283 MapEntries(Vec<MapEntry>),
285}
286
287#[derive(Default, Debug, Deserialize, Serialize)]
291struct RawStorageEntry {
292 #[serde(flatten)]
293 identifier: Option<FieldIdentifier>,
294 slot: Option<u8>,
295 slots: Option<Vec<u8>>,
296 #[serde(rename = "type")]
297 word_type: Option<TemplateType>,
298 value: Option<[FeltRepresentation; 4]>,
299 values: Option<StorageValues>,
300}
301
302impl From<StorageEntry> for RawStorageEntry {
303 fn from(entry: StorageEntry) -> Self {
304 match entry {
305 StorageEntry::Value { slot, word_entry } => match word_entry {
306 WordRepresentation::Value { identifier, value } => RawStorageEntry {
307 slot: Some(slot),
308 identifier,
309 value: Some(value),
310 ..Default::default()
311 },
312 WordRepresentation::Template { identifier, r#type } => RawStorageEntry {
313 slot: Some(slot),
314 identifier: Some(identifier),
315 word_type: Some(r#type),
316 ..Default::default()
317 },
318 },
319 StorageEntry::Map { slot, map } => RawStorageEntry {
320 slot: Some(slot),
321 identifier: Some(FieldIdentifier {
322 name: map.name().clone(),
323 description: map.description().cloned(),
324 }),
325 values: Some(StorageValues::MapEntries(map.into())),
326 ..Default::default()
327 },
328 StorageEntry::MultiSlot { slots, word_entries } => match word_entries {
329 MultiWordRepresentation::Value { identifier, values } => RawStorageEntry {
330 slot: None,
331 identifier: Some(identifier),
332 slots: Some(slots.collect()),
333 values: Some(StorageValues::Words(values)),
334 ..Default::default()
335 },
336 },
337 }
338 }
339}
340
341impl Serialize for StorageEntry {
342 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
343 where
344 S: Serializer,
345 {
346 let raw_storage_entry: RawStorageEntry = self.clone().into();
347 raw_storage_entry.serialize(serializer)
348 }
349}
350
351impl<'de> Deserialize<'de> for StorageEntry {
352 fn deserialize<D>(deserializer: D) -> Result<StorageEntry, D::Error>
353 where
354 D: Deserializer<'de>,
355 {
356 let raw = RawStorageEntry::deserialize(deserializer)?;
357
358 if let Some(word_entry) = raw.value {
359 let slot = raw.slot.ok_or_else(|| missing_field_for("slot", "value entry"))?;
361 let identifier = raw.identifier;
362 Ok(StorageEntry::Value {
363 slot,
364 word_entry: WordRepresentation::Value { value: word_entry, identifier },
365 })
366 } else if let Some(StorageValues::MapEntries(map_entries)) = raw.values {
367 let identifier =
369 raw.identifier.ok_or_else(|| missing_field_for("identifier", "map entry"))?;
370 let name = identifier.name;
371 let slot = raw.slot.ok_or_else(|| missing_field_for("slot", "map entry"))?;
372 let mut map = MapRepresentation::new(map_entries, name);
373 if let Some(desc) = identifier.description {
374 map = map.with_description(desc);
375 }
376 Ok(StorageEntry::Map { slot, map })
377 } else if let Some(StorageValues::Words(values)) = raw.values {
378 let identifier = raw
379 .identifier
380 .ok_or_else(|| missing_field_for("identifier", "multislot entry"))?;
381
382 let mut slots =
383 raw.slots.ok_or_else(|| missing_field_for("slots", "multislot entry"))?;
384
385 slots.sort_unstable();
387 for pair in slots.windows(2) {
388 if pair[1] != pair[0] + 1 {
389 return Err(serde::de::Error::custom(format!(
390 "`slots` in the `{}` storage entry are not contiguous",
391 identifier.name
392 )));
393 }
394 }
395 let start = slots[0];
396 let end = slots.last().expect("checked validity") + 1;
397 Ok(StorageEntry::new_multislot(identifier, start..end, values))
398 } else if let Some(word_type) = raw.word_type {
399 let slot = raw.slot.ok_or_else(|| missing_field_for("slot", "single-slot entry"))?;
401 let identifier = raw
402 .identifier
403 .ok_or_else(|| missing_field_for("identifier", "single-slot entry"))?;
404 let word_entry = WordRepresentation::Template { r#type: word_type, identifier };
405 Ok(StorageEntry::Value { slot, word_entry })
406 } else {
407 Err(D::Error::custom("placeholder storage entries require the `type` field"))
408 }
409 }
410}
411
412impl InitStorageData {
416 pub fn from_toml(toml_str: &str) -> Result<Self, InitStorageDataError> {
428 let value: toml::Value = toml::from_str(toml_str)?;
429 let mut placeholders = BTreeMap::new();
430 Self::flatten_parse_toml_value(StorageValueName::empty(), &value, &mut placeholders)?;
432 Ok(InitStorageData::new(placeholders))
433 }
434
435 fn flatten_parse_toml_value(
441 prefix: StorageValueName,
442 value: &toml::Value,
443 map: &mut BTreeMap<StorageValueName, String>,
444 ) -> Result<(), InitStorageDataError> {
445 match value {
446 toml::Value::Table(table) => {
447 if !prefix.as_str().is_empty() && table.is_empty() {
449 return Err(InitStorageDataError::EmptyTable(prefix.as_str().into()));
450 }
451 for (key, val) in table {
452 let new_key = StorageValueName::new(key.to_string())
454 .map_err(InitStorageDataError::InvalidStorageValueName)?;
455 let new_prefix = prefix.clone().with_suffix(&new_key);
456 Self::flatten_parse_toml_value(new_prefix, val, map)?;
457 }
458 },
459 toml::Value::Array(_) => {
460 return Err(InitStorageDataError::ArraysNotSupported);
461 },
462 toml_value => {
463 let value = match toml_value {
465 toml::Value::String(s) => s.clone(),
466 _ => value.to_string(),
467 };
468 map.insert(prefix, value);
469 },
470 }
471 Ok(())
472 }
473}
474
475#[derive(Debug, Error)]
476pub enum InitStorageDataError {
477 #[error("failed to parse TOML")]
478 InvalidToml(#[from] toml::de::Error),
479
480 #[error("empty table encountered for key `{0}`")]
481 EmptyTable(String),
482
483 #[error("invalid input: arrays are not supported")]
484 ArraysNotSupported,
485
486 #[error("invalid storage value name")]
487 InvalidStorageValueName(#[source] StorageValueNameError),
488}
489
490impl Serialize for FieldIdentifier {
491 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
492 where
493 S: Serializer,
494 {
495 let mut map = serializer.serialize_map(Some(2))?;
496 map.serialize_entry("name", &self.name)?;
497 map.serialize_entry("description", &self.description)?;
498 map.end()
499 }
500}
501
502struct FieldIdentifierVisitor;
503
504impl<'de> Visitor<'de> for FieldIdentifierVisitor {
505 type Value = FieldIdentifier;
506
507 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
508 formatter.write_str("a map with 'name' and optionally 'description'")
509 }
510
511 fn visit_map<M>(self, mut map: M) -> Result<FieldIdentifier, M::Error>
512 where
513 M: MapAccess<'de>,
514 {
515 let mut name = None;
516 let mut description = None;
517 while let Some(key) = map.next_key::<String>()? {
518 match key.as_str() {
519 "name" => {
520 name = Some(map.next_value()?);
521 },
522 "description" => {
523 let d: String = map.next_value()?;
524 description = if d.trim().is_empty() { None } else { Some(d) };
526 },
527 _ => {
528 let _: de::IgnoredAny = map.next_value()?;
530 },
531 }
532 }
533 let name = name.ok_or_else(|| de::Error::missing_field("name"))?;
534 Ok(FieldIdentifier { name, description })
535 }
536}
537
538impl<'de> Deserialize<'de> for FieldIdentifier {
539 fn deserialize<D>(deserializer: D) -> Result<FieldIdentifier, D::Error>
540 where
541 D: Deserializer<'de>,
542 {
543 deserializer.deserialize_map(FieldIdentifierVisitor)
544 }
545}
546
547fn missing_field_for<E: serde::de::Error>(field: &str, context: &str) -> E {
551 E::custom(format!("missing '{field}' field for {context}"))
552}
553
554fn expect_parse_field_identifier<E: serde::de::Error>(
556 n: Option<String>,
557 description: Option<String>,
558 context: &str,
559) -> Result<FieldIdentifier, E> {
560 let name = n.ok_or_else(|| missing_field_for("name", context))?;
561 parse_field_identifier(name, description)
562}
563
564fn parse_field_identifier<E: serde::de::Error>(
566 n: String,
567 description: Option<String>,
568) -> Result<FieldIdentifier, E> {
569 StorageValueName::new(n)
570 .map_err(|err| E::custom(format!("invalid `name`: {err}")))
571 .map(|storage_name| {
572 if let Some(desc) = description {
573 FieldIdentifier::with_description(storage_name, desc)
574 } else {
575 FieldIdentifier::with_name(storage_name)
576 }
577 })
578}
579
580#[cfg(test)]
584mod tests {
585 use alloc::string::ToString;
586 use core::error::Error;
587
588 use super::*;
589 use crate::account::component::toml::InitStorageDataError;
590
591 #[test]
592 fn from_toml_str_with_nested_table_and_flattened() {
593 let toml_table = r#"
594 [token_metadata]
595 max_supply = "1000000000"
596 symbol = "ETH"
597 decimals = "9"
598 "#;
599
600 let toml_inline = r#"
601 token_metadata.max_supply = "1000000000"
602 token_metadata.symbol = "ETH"
603 token_metadata.decimals = "9"
604 "#;
605
606 let storage_table = InitStorageData::from_toml(toml_table).unwrap();
607 let storage_inline = InitStorageData::from_toml(toml_inline).unwrap();
608
609 assert_eq!(storage_table.placeholders(), storage_inline.placeholders());
610 }
611
612 #[test]
613 fn from_toml_str_with_deeply_nested_tables() {
614 let toml_str = r#"
615 [a]
616 b = "0xb"
617
618 [a.c]
619 d = "0xd"
620
621 [x.y.z]
622 w = 42 # NOTE: This gets parsed as string
623 "#;
624
625 let storage = InitStorageData::from_toml(toml_str).expect("Failed to parse TOML");
626 let key1 = StorageValueName::new("a.b".to_string()).unwrap();
627 let key2 = StorageValueName::new("a.c.d".to_string()).unwrap();
628 let key3 = StorageValueName::new("x.y.z.w".to_string()).unwrap();
629
630 assert_eq!(storage.get(&key1).unwrap(), "0xb");
631 assert_eq!(storage.get(&key2).unwrap(), "0xd");
632 assert_eq!(storage.get(&key3).unwrap(), "42");
633 }
634
635 #[test]
636 fn test_error_on_array() {
637 let toml_str = r#"
638 token_metadata.v = [1, 2, 3]
639 "#;
640
641 let result = InitStorageData::from_toml(toml_str);
642 assert_matches::assert_matches!(
643 result.unwrap_err(),
644 InitStorageDataError::ArraysNotSupported
645 );
646 }
647
648 #[test]
649 fn error_on_empty_subtable() {
650 let toml_str = r#"
651 [a]
652 b = {}
653 "#;
654
655 let result = InitStorageData::from_toml(toml_str);
656 assert_matches::assert_matches!(result.unwrap_err(), InitStorageDataError::EmptyTable(_));
657 }
658
659 #[test]
660 fn error_on_duplicate_keys() {
661 let toml_str = r#"
662 token_metadata.max_supply = "1000000000"
663 token_metadata.max_supply = "500000000"
664 "#;
665
666 let result = InitStorageData::from_toml(toml_str).unwrap_err();
667 assert_matches::assert_matches!(result, InitStorageDataError::InvalidToml(_));
669 assert!(result.source().unwrap().to_string().contains("duplicate"));
670 }
671}