1use alloc::{
2 string::{String, ToString},
3 vec::Vec,
4};
5use core::fmt;
6use std::collections::BTreeMap;
7
8use serde::{
9 Deserialize, Deserializer, Serialize, Serializer,
10 de::{Error, SeqAccess, Visitor, value::MapAccessDeserializer},
11 ser::SerializeStruct,
12};
13use thiserror::Error;
14use vm_core::Felt;
15
16use super::{
17 FeltRepresentation, InitStorageData, MapEntry, MapRepresentation, StorageEntry,
18 StorageValueNameError, WordRepresentation, placeholder::TemplateType,
19};
20use crate::{
21 account::{
22 AccountComponentMetadata, StorageValueName,
23 component::template::storage::placeholder::{TEMPLATE_REGISTRY, TemplateFelt},
24 },
25 errors::AccountComponentTemplateError,
26 utils::parse_hex_string_as_word,
27};
28
29impl AccountComponentMetadata {
33 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 pub fn as_toml(&self) -> Result<String, AccountComponentTemplateError> {
51 let toml = toml::to_string_pretty(self)
52 .map_err(AccountComponentTemplateError::TomlSerializationError)?;
53 Ok(toml)
54 }
55}
56
57impl 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 { name, description, r#type } => {
67 let mut state = serializer.serialize_struct("WordRepresentation", 3)?;
69 state.serialize_field("name", name)?;
70 state.serialize_field("description", description)?;
71 state.serialize_field("type", r#type)?;
72 state.end()
73 },
74 WordRepresentation::Value { name, description, value } => {
75 let mut state = serializer.serialize_struct("WordRepresentation", 3)?;
77 state.serialize_field("name", name)?;
78 state.serialize_field("description", description)?;
79 state.serialize_field("value", value)?;
80 state.end()
81 },
82 }
83 }
84}
85
86impl<'de> Deserialize<'de> for WordRepresentation {
87 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
88 where
89 D: Deserializer<'de>,
90 {
91 struct WordRepresentationVisitor;
92
93 impl<'de> Visitor<'de> for WordRepresentationVisitor {
94 type Value = WordRepresentation;
95
96 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
97 formatter.write_str("a string or a map representing a WordRepresentation")
98 }
99
100 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
102 where
103 E: Error,
104 {
105 let parsed_value = parse_hex_string_as_word(value).map_err(|_err| {
106 E::invalid_value(
107 serde::de::Unexpected::Str(value),
108 &"a valid hexadecimal string",
109 )
110 })?;
111 Ok(parsed_value.into())
112 }
113
114 fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
115 where
116 E: Error,
117 {
118 self.visit_str(&value)
119 }
120
121 fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
122 where
123 A: SeqAccess<'de>,
124 {
125 let elements: Vec<FeltRepresentation> =
127 Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))?;
128 if elements.len() != 4 {
129 return Err(Error::invalid_length(
130 elements.len(),
131 &"expected an array of 4 elements",
132 ));
133 }
134 let value: [FeltRepresentation; 4] =
135 elements.try_into().expect("length was checked");
136 Ok(WordRepresentation::new_value(value, None))
137 }
138
139 fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
140 where
141 M: serde::de::MapAccess<'de>,
142 {
143 #[derive(Deserialize, Debug)]
144 struct WordRepresentationHelper {
145 name: Option<String>,
146 description: Option<String>,
147 value: Option<[FeltRepresentation; 4]>,
149 #[serde(rename = "type")]
150 r#type: Option<TemplateType>,
151 }
152
153 let helper =
154 WordRepresentationHelper::deserialize(MapAccessDeserializer::new(map))?;
155
156 if let Some(value) = helper.value {
158 let name = helper.name.map(parse_name).transpose()?;
159 Ok(WordRepresentation::Value {
160 value,
161 name,
162 description: helper.description,
163 })
164 } else {
165 let name = expect_parse_value_name(helper.name, "word template")?;
167
168 let r#type = helper.r#type.unwrap_or(TemplateType::native_word());
170 Ok(WordRepresentation::Template {
171 r#type,
172 name,
173 description: helper.description,
174 })
175 }
176 }
177 }
178
179 deserializer.deserialize_any(WordRepresentationVisitor)
180 }
181}
182
183impl Serialize for FeltRepresentation {
187 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
188 where
189 S: Serializer,
190 {
191 match self {
192 FeltRepresentation::Value { name, description, value } => {
193 let hex = value.to_string();
194 if name.is_none() && description.is_none() {
195 serializer.serialize_str(&hex)
196 } else {
197 let mut state = serializer.serialize_struct("FeltRepresentation", 3)?;
198 state.serialize_field("name", name)?;
199 state.serialize_field("description", description)?;
200 state.serialize_field("value", &hex)?;
201 state.end()
202 }
203 },
204 FeltRepresentation::Template { name, description, r#type } => {
205 let mut state = serializer.serialize_struct("FeltRepresentation", 3)?;
206 state.serialize_field("name", name)?;
207 state.serialize_field("description", 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 #[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 {
245 name: None,
246 description: None,
247 value: felt,
248 })
249 },
250 Intermediate::Map { name, description, value, r#type } => {
251 let felt_type = r#type.unwrap_or(TemplateType::native_felt());
253
254 if let Some(val_str) = value {
255 let felt =
257 TEMPLATE_REGISTRY.try_parse_felt(&felt_type, &val_str).map_err(|e| {
258 D::Error::custom(format!("failed to parse {felt_type} as Felt: {}", e))
259 })?;
260 let name = name.map(parse_name).transpose()?;
261 Ok(FeltRepresentation::Value { value: felt, name, description })
262 } else {
263 let name = expect_parse_value_name(name, "map template")?;
265
266 Ok(FeltRepresentation::Template { r#type: felt_type, name, description })
267 }
268 },
269 }
270 }
271}
272
273#[derive(Debug, Deserialize, Serialize)]
278#[serde(untagged)]
279enum StorageValues {
280 Words(Vec<[FeltRepresentation; 4]>),
282 MapEntries(Vec<MapEntry>),
284}
285
286#[derive(Default, Debug, Deserialize, Serialize)]
290struct RawStorageEntry {
291 name: Option<String>,
292 description: Option<String>,
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 { name, description, value } => RawStorageEntry {
306 slot: Some(slot),
307 name: name.as_ref().map(StorageValueName::to_string),
308 description,
309 value: Some(value),
310 ..Default::default()
311 },
312 WordRepresentation::Template { name, description, r#type } => RawStorageEntry {
313 slot: Some(slot),
314 name: Some(name.to_string()),
315 description,
316 word_type: Some(r#type),
317 ..Default::default()
318 },
319 },
320 StorageEntry::Map { slot, map } => RawStorageEntry {
321 name: Some(map.name().to_string()),
322 description: map.description().cloned(),
323 slot: Some(slot),
324 values: Some(StorageValues::MapEntries(map.into())),
325 ..Default::default()
326 },
327 StorageEntry::MultiSlot { name, description, slots, values } => RawStorageEntry {
328 name: Some(name.to_string()),
329 description,
330 slots: Some(slots.collect()),
331 values: Some(StorageValues::Words(values)),
332 ..Default::default()
333 },
334 }
335 }
336}
337
338impl Serialize for StorageEntry {
339 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
340 where
341 S: Serializer,
342 {
343 let raw_storage_entry: RawStorageEntry = self.clone().into();
344 raw_storage_entry.serialize(serializer)
345 }
346}
347
348impl<'de> Deserialize<'de> for StorageEntry {
349 fn deserialize<D>(deserializer: D) -> Result<StorageEntry, D::Error>
350 where
351 D: Deserializer<'de>,
352 {
353 let raw = RawStorageEntry::deserialize(deserializer)?;
354
355 if let Some(word_entry) = raw.value {
356 let slot = raw.slot.ok_or_else(|| missing_field_for("slot", "value entry"))?;
358 let name = raw.name.map(parse_name).transpose()?;
359
360 Ok(StorageEntry::Value {
361 slot,
362 word_entry: WordRepresentation::Value {
363 value: word_entry,
364 name,
365 description: raw.description,
366 },
367 })
368 } else if let Some(StorageValues::MapEntries(map_entries)) = raw.values {
369 let name = expect_parse_value_name(raw.name, "map entry")?;
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(description) = raw.description {
374 map = map.with_description(description);
375 }
376
377 Ok(StorageEntry::Map { slot, map })
378 } else if let Some(StorageValues::Words(values)) = raw.values {
379 let name = expect_parse_value_name(raw.name, "multislot entry")?;
380 let mut slots =
381 raw.slots.ok_or_else(|| missing_field_for("slots", "multislot entry"))?;
382
383 slots.sort_unstable();
385
386 for pair in slots.windows(2) {
387 if pair[1] != pair[0] + 1 {
388 return Err(serde::de::Error::custom(format!(
389 "`slots` in the `{name}` storage entry are not contiguous"
390 )));
391 }
392 }
393
394 let start = slots[0];
395 let end = slots.last().expect("checked validity") + 1;
396
397 Ok(StorageEntry::new_multislot(name, raw.description, 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 name = expect_parse_value_name(raw.name, "single-slot entry")?;
402 let word_entry = WordRepresentation::Template {
403 r#type: word_type,
404 name,
405 description: raw.description,
406 };
407
408 Ok(StorageEntry::Value { slot, word_entry })
409 } else {
410 Err(D::Error::custom("placeholder storage entries require the `type` field"))
411 }
412 }
413}
414
415impl InitStorageData {
419 pub fn from_toml(toml_str: &str) -> Result<Self, InitStorageDataError> {
431 let value: toml::Value = toml::from_str(toml_str)?;
432 let mut placeholders = BTreeMap::new();
433 Self::flatten_parse_toml_value(StorageValueName::empty(), &value, &mut placeholders)?;
435 Ok(InitStorageData::new(placeholders))
436 }
437
438 fn flatten_parse_toml_value(
444 prefix: StorageValueName,
445 value: &toml::Value,
446 map: &mut BTreeMap<StorageValueName, String>,
447 ) -> Result<(), InitStorageDataError> {
448 match value {
449 toml::Value::Table(table) => {
450 if !prefix.as_str().is_empty() && table.is_empty() {
452 return Err(InitStorageDataError::EmptyTable(prefix.as_str().into()));
453 }
454 for (key, val) in table {
455 let new_key = StorageValueName::new(key.to_string())
457 .map_err(InitStorageDataError::InvalidStorageValueName)?;
458 let new_prefix = prefix.clone().with_suffix(&new_key);
459 Self::flatten_parse_toml_value(new_prefix, val, map)?;
460 }
461 },
462 toml::Value::Array(_) => {
463 return Err(InitStorageDataError::ArraysNotSupported);
464 },
465 toml_value => {
466 let value = match toml_value {
468 toml::Value::String(s) => s.clone(),
469 _ => value.to_string(),
470 };
471 map.insert(prefix, value);
472 },
473 }
474 Ok(())
475 }
476}
477
478#[derive(Debug, Error)]
479pub enum InitStorageDataError {
480 #[error("failed to parse TOML")]
481 InvalidToml(#[from] toml::de::Error),
482
483 #[error("empty table encountered for key `{0}`")]
484 EmptyTable(String),
485
486 #[error("invalid input: arrays are not supported")]
487 ArraysNotSupported,
488
489 #[error("invalid storage value name")]
490 InvalidStorageValueName(#[source] StorageValueNameError),
491}
492
493fn missing_field_for<E: serde::de::Error>(field: &str, context: &str) -> E {
497 E::custom(format!("missing '{}' field for {}", field, context))
498}
499
500fn expect_parse_value_name<E: serde::de::Error>(
502 n: Option<String>,
503 context: &str,
504) -> Result<StorageValueName, E> {
505 let name = n.ok_or_else(|| missing_field_for("name", context))?;
506 parse_name(name)
507}
508
509fn parse_name<E: serde::de::Error>(n: String) -> Result<StorageValueName, E> {
511 StorageValueName::new(n).map_err(|err| E::custom(format!("invalid `name`: {err}")))
512}
513
514#[cfg(test)]
518mod tests {
519 use alloc::string::ToString;
520 use core::error::Error;
521
522 use super::*;
523 use crate::account::component::toml::InitStorageDataError;
524
525 #[test]
526 fn from_toml_str_with_nested_table_and_flattened() {
527 let toml_table = r#"
528 [token_metadata]
529 max_supply = "1000000000"
530 symbol = "ETH"
531 decimals = "9"
532 "#;
533
534 let toml_inline = r#"
535 token_metadata.max_supply = "1000000000"
536 token_metadata.symbol = "ETH"
537 token_metadata.decimals = "9"
538 "#;
539
540 let storage_table = InitStorageData::from_toml(toml_table).unwrap();
541 let storage_inline = InitStorageData::from_toml(toml_inline).unwrap();
542
543 assert_eq!(storage_table.placeholders(), storage_inline.placeholders());
544 }
545
546 #[test]
547 fn from_toml_str_with_deeply_nested_tables() {
548 let toml_str = r#"
549 [a]
550 b = "0xb"
551
552 [a.c]
553 d = "0xd"
554
555 [x.y.z]
556 w = 42 # NOTE: This gets parsed as string
557 "#;
558
559 let storage = InitStorageData::from_toml(toml_str).expect("Failed to parse TOML");
560 let key1 = StorageValueName::new("a.b".to_string()).unwrap();
561 let key2 = StorageValueName::new("a.c.d".to_string()).unwrap();
562 let key3 = StorageValueName::new("x.y.z.w".to_string()).unwrap();
563
564 assert_eq!(storage.get(&key1).unwrap(), "0xb");
565 assert_eq!(storage.get(&key2).unwrap(), "0xd");
566 assert_eq!(storage.get(&key3).unwrap(), "42");
567 }
568
569 #[test]
570 fn test_error_on_array() {
571 let toml_str = r#"
572 token_metadata.v = [1, 2, 3]
573 "#;
574
575 let result = InitStorageData::from_toml(toml_str);
576 assert_matches::assert_matches!(
577 result.unwrap_err(),
578 InitStorageDataError::ArraysNotSupported
579 );
580 }
581
582 #[test]
583 fn error_on_empty_subtable() {
584 let toml_str = r#"
585 [a]
586 b = {}
587 "#;
588
589 let result = InitStorageData::from_toml(toml_str);
590 assert_matches::assert_matches!(result.unwrap_err(), InitStorageDataError::EmptyTable(_));
591 }
592
593 #[test]
594 fn error_on_duplicate_keys() {
595 let toml_str = r#"
596 token_metadata.max_supply = "1000000000"
597 token_metadata.max_supply = "500000000"
598 "#;
599
600 let result = InitStorageData::from_toml(toml_str).unwrap_err();
601 assert_matches::assert_matches!(result, InitStorageDataError::InvalidToml(_));
603 assert!(result.source().unwrap().to_string().contains("duplicate"));
604 }
605}