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