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