1use alloc::collections::{BTreeMap, BTreeSet};
2use alloc::string::{String, ToString};
3use alloc::vec::Vec;
4
5use miden_core::{Felt, Word};
6use semver::Version;
7use serde::de::Error as _;
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9
10use super::super::{
11 FeltSchema,
12 MapSlotSchema,
13 StorageSchema,
14 StorageSlotSchema,
15 StorageValueName,
16 ValueSlotSchema,
17 WordSchema,
18 WordValue,
19};
20use crate::account::component::storage::type_registry::SCHEMA_TYPE_REGISTRY;
21use crate::account::component::{AccountComponentMetadata, SchemaType};
22use crate::account::{AccountType, StorageSlotName};
23use crate::errors::ComponentMetadataError;
24
25mod init_storage_data;
26mod serde_impls;
27
28#[cfg(test)]
29mod tests;
30
31#[derive(Debug, Deserialize)]
35#[serde(rename_all = "kebab-case", deny_unknown_fields)]
36struct RawAccountComponentMetadata {
37 name: String,
38 description: String,
39 version: Version,
40 supported_types: BTreeSet<AccountType>,
41 #[serde(rename = "storage")]
42 #[serde(default)]
43 storage: RawStorageSchema,
44}
45
46impl AccountComponentMetadata {
47 pub fn from_toml(toml_string: &str) -> Result<Self, ComponentMetadataError> {
55 let raw: RawAccountComponentMetadata = toml::from_str(toml_string)
56 .map_err(ComponentMetadataError::TomlDeserializationError)?;
57
58 if !raw.description.is_ascii() {
59 return Err(ComponentMetadataError::InvalidSchema(
60 "description must contain only ASCII characters".to_string(),
61 ));
62 }
63
64 let RawStorageSchema { slots } = raw.storage;
65 let mut fields = Vec::with_capacity(slots.len());
66
67 for slot in slots {
68 fields.push(slot.try_into_slot_schema()?);
69 }
70
71 let storage_schema = StorageSchema::new(fields)?;
72 Ok(Self::new(raw.name, raw.supported_types)
73 .with_description(raw.description)
74 .with_version(raw.version)
75 .with_storage_schema(storage_schema))
76 }
77
78 pub fn to_toml(&self) -> Result<String, ComponentMetadataError> {
80 let toml = toml::to_string(self).map_err(ComponentMetadataError::TomlSerializationError)?;
81 Ok(toml)
82 }
83}
84
85#[derive(Debug, Default, Deserialize, Serialize)]
96#[serde(rename_all = "kebab-case", deny_unknown_fields)]
97struct RawStorageSchema {
98 #[serde(default)]
99 slots: Vec<RawStorageSlotSchema>,
100}
101
102#[derive(Debug, Clone, Deserialize, Serialize)]
110#[serde(untagged)]
111enum RawSlotType {
112 Word(RawWordType),
113 Map(RawMapType),
114}
115
116#[derive(Debug, Clone, Deserialize, Serialize)]
118#[serde(untagged)]
119enum RawWordType {
120 TypeIdentifier(SchemaType),
121 FeltSchemaArray(Vec<FeltSchema>),
122}
123
124#[derive(Debug, Clone, Deserialize, Serialize)]
126#[serde(rename_all = "kebab-case", deny_unknown_fields)]
127struct RawMapType {
128 key: RawWordType,
129 value: RawWordType,
130}
131
132impl Serialize for StorageSchema {
136 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
137 where
138 S: Serializer,
139 {
140 let slots = self
141 .slots()
142 .iter()
143 .map(|(slot_name, schema)| RawStorageSlotSchema::from_slot(slot_name, schema))
144 .collect();
145
146 RawStorageSchema { slots }.serialize(serializer)
147 }
148}
149
150impl<'de> Deserialize<'de> for StorageSchema {
151 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
152 where
153 D: Deserializer<'de>,
154 {
155 let raw = RawStorageSchema::deserialize(deserializer)?;
157 let mut fields = Vec::with_capacity(raw.slots.len());
158
159 for slot in raw.slots {
160 let (slot_name, schema) = slot.try_into_slot_schema().map_err(D::Error::custom)?;
161 fields.push((slot_name, schema));
162 }
163
164 StorageSchema::new(fields).map_err(D::Error::custom)
165 }
166}
167
168#[derive(Debug, Deserialize, Serialize)]
175#[serde(rename_all = "kebab-case", deny_unknown_fields)]
176struct RawStorageSlotSchema {
177 name: String,
180 #[serde(default)]
181 description: Option<String>,
182 #[serde(rename = "type")]
190 r#type: RawSlotType,
191 #[serde(default)]
193 default_value: Option<WordValue>,
194 #[serde(default)]
199 default_values: Option<Vec<RawMapEntrySchema>>,
200}
201
202#[derive(Debug, Deserialize, Serialize)]
203#[serde(deny_unknown_fields)]
204struct RawMapEntrySchema {
205 key: WordValue,
206 value: WordValue,
207}
208
209impl RawStorageSlotSchema {
210 fn from_slot(slot_name: &StorageSlotName, schema: &StorageSlotSchema) -> Self {
214 match schema {
215 StorageSlotSchema::Value(schema) => Self::from_value_slot(slot_name, schema),
216 StorageSlotSchema::Map(schema) => Self::from_map_slot(slot_name, schema),
217 }
218 }
219
220 fn from_value_slot(slot_name: &StorageSlotName, schema: &ValueSlotSchema) -> Self {
221 let word = schema.word();
222 let (r#type, default_value) = match word {
223 WordSchema::Simple { r#type, default_value } => (
224 RawSlotType::Word(RawWordType::TypeIdentifier(r#type.clone())),
225 default_value.map(|word| WordValue::from_word(r#type, word)),
226 ),
227 WordSchema::Composite { value } => {
228 (RawSlotType::Word(RawWordType::FeltSchemaArray(value.to_vec())), None)
229 },
230 };
231
232 Self {
233 name: slot_name.as_str().to_string(),
234 description: schema.description().cloned(),
235 r#type,
236 default_value,
237 default_values: None,
238 }
239 }
240
241 fn from_map_slot(slot_name: &StorageSlotName, schema: &MapSlotSchema) -> Self {
242 let default_values = schema.default_values().map(|default_values| {
243 default_values
244 .into_iter()
245 .map(|(key, value)| RawMapEntrySchema {
246 key: WordValue::from_word(&schema.key_schema().word_type(), key),
247 value: WordValue::from_word(&schema.value_schema().word_type(), value),
248 })
249 .collect()
250 });
251
252 let key_type = match schema.key_schema() {
253 WordSchema::Simple { r#type, .. } => RawWordType::TypeIdentifier(r#type.clone()),
254 WordSchema::Composite { value } => RawWordType::FeltSchemaArray(value.to_vec()),
255 };
256
257 let value_type = match schema.value_schema() {
258 WordSchema::Simple { r#type, .. } => RawWordType::TypeIdentifier(r#type.clone()),
259 WordSchema::Composite { value } => RawWordType::FeltSchemaArray(value.to_vec()),
260 };
261
262 Self {
263 name: slot_name.as_str().to_string(),
264 description: schema.description().cloned(),
265 r#type: RawSlotType::Map(RawMapType { key: key_type, value: value_type }),
266 default_value: None,
267 default_values,
268 }
269 }
270
271 fn try_into_slot_schema(
276 self,
277 ) -> Result<(StorageSlotName, StorageSlotSchema), ComponentMetadataError> {
278 let RawStorageSlotSchema {
279 name,
280 description,
281 r#type,
282 default_value,
283 default_values,
284 } = self;
285
286 let slot_name_raw = name;
287 let slot_name = StorageSlotName::new(slot_name_raw.clone()).map_err(|err| {
288 ComponentMetadataError::InvalidSchema(format!(
289 "invalid storage slot name `{slot_name_raw}`: {err}"
290 ))
291 })?;
292
293 let description =
294 description.and_then(|d| if d.trim().is_empty() { None } else { Some(d) });
295
296 let slot_prefix = StorageValueName::from_slot_name(&slot_name);
297
298 if default_value.is_some() && default_values.is_some() {
299 return Err(ComponentMetadataError::InvalidSchema(
300 "storage slot schema cannot define both `default-value` and `default-values`"
301 .into(),
302 ));
303 }
304
305 match r#type {
306 RawSlotType::Map(map_type) => {
307 if default_value.is_some() {
308 return Err(ComponentMetadataError::InvalidSchema(
309 "map slots cannot define `default-value`".into(),
310 ));
311 }
312
313 let RawMapType { key: key_type, value: value_type } = map_type;
314 let key_schema = Self::parse_word_schema(key_type, "`type.key`")?;
315 let value_schema = Self::parse_word_schema(value_type, "`type.value`")?;
316
317 let default_values = default_values
318 .map(|entries| {
319 Self::parse_default_map_entries(
320 entries,
321 &key_schema,
322 &value_schema,
323 &slot_prefix,
324 )
325 })
326 .transpose()?;
327
328 Ok((
329 slot_name,
330 StorageSlotSchema::Map(MapSlotSchema::new(
331 description,
332 default_values,
333 key_schema,
334 value_schema,
335 )),
336 ))
337 },
338
339 RawSlotType::Word(word_type) => {
340 if default_values.is_some() {
341 return Err(ComponentMetadataError::InvalidSchema(
342 "`default-values` can be specified only for map slots (use `type = { ... }`)"
343 .into(),
344 ));
345 }
346
347 match word_type {
348 RawWordType::TypeIdentifier(r#type) => {
349 if r#type.as_str() == "map" {
350 return Err(ComponentMetadataError::InvalidSchema(
351 "value slots cannot use `type = \"map\"`; use `type = { key = <key-type>, value = <value-type>}` instead"
352 .into(),
353 ));
354 }
355
356 let word = default_value
357 .as_ref()
358 .map(|default_value| {
359 default_value.try_parse_as_typed_word(
360 &r#type,
361 &slot_prefix,
362 "default value",
363 )
364 })
365 .transpose()?;
366
367 let word_schema = match word {
368 Some(word) => WordSchema::new_simple_with_default(r#type, word),
369 None => WordSchema::new_simple(r#type),
370 };
371
372 Ok((
373 slot_name,
374 StorageSlotSchema::Value(ValueSlotSchema::new(
375 description,
376 word_schema,
377 )),
378 ))
379 },
380
381 RawWordType::FeltSchemaArray(elements) => {
382 if default_value.is_some() {
383 return Err(ComponentMetadataError::InvalidSchema(
384 "composite word slots cannot define `default-value`".into(),
385 ));
386 }
387
388 let elements = Self::parse_felt_schema_array(elements, "word slot `type`")?;
389 Ok((
390 slot_name,
391 StorageSlotSchema::Value(ValueSlotSchema::new(
392 description,
393 WordSchema::new_value(elements),
394 )),
395 ))
396 },
397 }
398 },
399 }
400 }
401
402 fn parse_word_schema(
403 raw: RawWordType,
404 label: &str,
405 ) -> Result<WordSchema, ComponentMetadataError> {
406 match raw {
407 RawWordType::TypeIdentifier(r#type) => Ok(WordSchema::new_simple(r#type)),
408 RawWordType::FeltSchemaArray(elements) => {
409 let elements = Self::parse_felt_schema_array(elements, label)?;
410 Ok(WordSchema::new_value(elements))
411 },
412 }
413 }
414
415 fn parse_felt_schema_array(
416 elements: Vec<FeltSchema>,
417 label: &str,
418 ) -> Result<[FeltSchema; 4], ComponentMetadataError> {
419 if elements.len() != 4 {
420 return Err(ComponentMetadataError::InvalidSchema(format!(
421 "{label} must be an array of 4 elements, got {}",
422 elements.len()
423 )));
424 }
425 Ok(elements.try_into().expect("length is 4"))
426 }
427
428 fn parse_default_map_entries(
429 entries: Vec<RawMapEntrySchema>,
430 key_schema: &WordSchema,
431 value_schema: &WordSchema,
432 slot_prefix: &StorageValueName,
433 ) -> Result<BTreeMap<Word, Word>, ComponentMetadataError> {
434 let mut map = BTreeMap::new();
435
436 let parse = |schema: &WordSchema, raw: &WordValue, label: &str| {
437 super::schema::parse_storage_value_with_schema(schema, raw, slot_prefix).map_err(
438 |err| {
439 ComponentMetadataError::InvalidSchema(format!("invalid map `{label}`: {err}"))
440 },
441 )
442 };
443
444 for (index, entry) in entries.into_iter().enumerate() {
445 let key_label = format!("default-values[{index}].key");
446 let value_label = format!("default-values[{index}].value");
447
448 let key = parse(key_schema, &entry.key, &key_label)?;
449 let value = parse(value_schema, &entry.value, &value_label)?;
450
451 if map.insert(key, value).is_some() {
452 return Err(ComponentMetadataError::InvalidSchema(format!(
453 "map storage slot `default-values[{index}]` contains a duplicate key"
454 )));
455 }
456 }
457
458 Ok(map)
459 }
460}
461
462impl WordValue {
463 pub(super) fn try_parse_as_typed_word(
464 &self,
465 schema_type: &SchemaType,
466 slot_prefix: &StorageValueName,
467 label: &str,
468 ) -> Result<Word, ComponentMetadataError> {
469 let word = match self {
470 WordValue::FullyTyped(word) => *word,
471 WordValue::Atomic(value) => SCHEMA_TYPE_REGISTRY
472 .try_parse_word(schema_type, value)
473 .map_err(ComponentMetadataError::StorageValueParsingError)?,
474 WordValue::Elements(elements) => {
475 let felts = elements
476 .iter()
477 .map(|element| {
478 SCHEMA_TYPE_REGISTRY.try_parse_felt(&SchemaType::native_felt(), element)
479 })
480 .collect::<Result<Vec<Felt>, _>>()
481 .map_err(ComponentMetadataError::StorageValueParsingError)?;
482 let felts: [Felt; 4] = felts.try_into().expect("length is 4");
483 Word::from(felts)
484 },
485 };
486
487 WordSchema::new_simple(schema_type.clone()).validate_word_value(
488 slot_prefix,
489 label,
490 word,
491 )?;
492 Ok(word)
493 }
494
495 pub(super) fn from_word(schema_type: &SchemaType, word: Word) -> Self {
496 WordValue::Atomic(SCHEMA_TYPE_REGISTRY.display_word(schema_type, word).value().to_string())
497 }
498}