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