1use alloc::boxed::Box;
2use alloc::collections::BTreeMap;
3use alloc::string::{String, ToString};
4use alloc::vec::Vec;
5
6use miden_core::utils::{ByteReader, ByteWriter, Deserializable, Serializable};
7use miden_processor::DeserializationError;
8
9use super::type_registry::{SCHEMA_TYPE_REGISTRY, SchemaRequirement, SchemaTypeId};
10use super::{InitStorageData, StorageValueName, WordValue};
11use crate::account::storage::is_reserved_slot_name;
12use crate::account::{StorageMap, StorageSlot, StorageSlotName};
13use crate::crypto::utils::bytes_to_elements_with_padding;
14use crate::errors::AccountComponentTemplateError;
15use crate::{Felt, FieldElement, Hasher, Word};
16
17#[derive(Debug, Clone, Default, PartialEq, Eq)]
22pub struct StorageSchema {
23 slots: BTreeMap<StorageSlotName, StorageSlotSchema>,
24}
25
26impl StorageSchema {
27 pub fn new(
35 slots: impl IntoIterator<Item = (StorageSlotName, StorageSlotSchema)>,
36 ) -> Result<Self, AccountComponentTemplateError> {
37 let mut map = BTreeMap::new();
38 for (slot_name, schema) in slots {
39 if map.insert(slot_name.clone(), schema).is_some() {
40 return Err(AccountComponentTemplateError::DuplicateSlotName(slot_name));
41 }
42 }
43
44 let schema = Self { slots: map };
45 schema.validate()?;
46 Ok(schema)
47 }
48
49 pub fn iter(&self) -> impl Iterator<Item = (&StorageSlotName, &StorageSlotSchema)> {
51 self.slots.iter()
52 }
53
54 pub fn slots(&self) -> &BTreeMap<StorageSlotName, StorageSlotSchema> {
56 &self.slots
57 }
58
59 pub fn build_storage_slots(
61 &self,
62 init_storage_data: &InitStorageData,
63 ) -> Result<Vec<StorageSlot>, AccountComponentTemplateError> {
64 self.slots
65 .iter()
66 .map(|(slot_name, schema)| schema.try_build_storage_slot(slot_name, init_storage_data))
67 .collect()
68 }
69
70 pub fn commitment(&self) -> Word {
74 let mut bytes = Vec::new();
75 self.write_into_with_optional_defaults(&mut bytes, false);
76 let elements = bytes_to_elements_with_padding(&bytes);
77 Hasher::hash_elements(&elements)
78 }
79
80 pub fn schema_requirements(
85 &self,
86 ) -> Result<BTreeMap<StorageValueName, SchemaRequirement>, AccountComponentTemplateError> {
87 let mut requirements = BTreeMap::new();
88 for (slot_name, schema) in self.slots.iter() {
89 schema.collect_init_value_requirements(slot_name, &mut requirements)?;
90 }
91 Ok(requirements)
92 }
93
94 fn write_into_with_optional_defaults<W: ByteWriter>(
97 &self,
98 target: &mut W,
99 include_defaults: bool,
100 ) {
101 target.write_u16(self.slots.len() as u16);
102 for (slot_name, schema) in self.slots.iter() {
103 target.write(slot_name);
104 schema.write_into_with_optional_defaults(target, include_defaults);
105 }
106 }
107
108 fn validate(&self) -> Result<(), AccountComponentTemplateError> {
110 let mut init_values = BTreeMap::new();
111
112 for (slot_name, schema) in self.slots.iter() {
113 if is_reserved_slot_name(slot_name) {
114 return Err(AccountComponentTemplateError::ReservedSlotName(slot_name.clone()));
115 }
116
117 schema.validate()?;
118 schema.collect_init_value_requirements(slot_name, &mut init_values)?;
119 }
120
121 Ok(())
122 }
123}
124
125impl Serializable for StorageSchema {
126 fn write_into<W: ByteWriter>(&self, target: &mut W) {
127 self.write_into_with_optional_defaults(target, true);
128 }
129}
130
131impl Deserializable for StorageSchema {
132 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
133 let num_entries = source.read_u16()? as usize;
134 let mut fields = BTreeMap::new();
135
136 for _ in 0..num_entries {
137 let slot_name = StorageSlotName::read_from(source)?;
138 let schema = StorageSlotSchema::read_from(source)?;
139
140 if fields.insert(slot_name.clone(), schema).is_some() {
141 return Err(DeserializationError::InvalidValue(format!(
142 "duplicate slot name in storage schema: {slot_name}",
143 )));
144 }
145 }
146
147 let schema = StorageSchema::new(fields)
148 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
149 Ok(schema)
150 }
151}
152
153fn validate_description_ascii(description: &str) -> Result<(), AccountComponentTemplateError> {
154 if description.is_ascii() {
155 Ok(())
156 } else {
157 Err(AccountComponentTemplateError::InvalidSchema(
158 "description must contain only ASCII characters".to_string(),
159 ))
160 }
161}
162
163#[allow(clippy::large_enum_variant)]
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub enum StorageSlotSchema {
171 Value(ValueSlotSchema),
172 Map(MapSlotSchema),
173}
174
175impl StorageSlotSchema {
176 fn collect_init_value_requirements(
177 &self,
178 slot_name: &StorageSlotName,
179 requirements: &mut BTreeMap<StorageValueName, SchemaRequirement>,
180 ) -> Result<(), AccountComponentTemplateError> {
181 let slot_name = StorageValueName::from_slot_name(slot_name);
182 match self {
183 StorageSlotSchema::Value(slot) => {
184 slot.collect_init_value_requirements(slot_name, requirements)
185 },
186 StorageSlotSchema::Map(_) => Ok(()),
187 }
188 }
189
190 pub fn try_build_storage_slot(
193 &self,
194 slot_name: &StorageSlotName,
195 init_storage_data: &InitStorageData,
196 ) -> Result<StorageSlot, AccountComponentTemplateError> {
197 match self {
198 StorageSlotSchema::Value(slot) => {
199 let word = slot.try_build_word(init_storage_data, slot_name)?;
200 Ok(StorageSlot::with_value(slot_name.clone(), word))
201 },
202 StorageSlotSchema::Map(slot) => {
203 let storage_map = slot.try_build_map(init_storage_data, slot_name)?;
204 Ok(StorageSlot::with_map(slot_name.clone(), storage_map))
205 },
206 }
207 }
208
209 pub(crate) fn validate(&self) -> Result<(), AccountComponentTemplateError> {
211 match self {
212 StorageSlotSchema::Value(slot) => slot.validate()?,
213 StorageSlotSchema::Map(slot) => slot.validate()?,
214 }
215
216 Ok(())
217 }
218
219 fn write_into_with_optional_defaults<W: ByteWriter>(
222 &self,
223 target: &mut W,
224 include_defaults: bool,
225 ) {
226 match self {
227 StorageSlotSchema::Value(slot) => {
228 target.write_u8(0u8);
229 slot.write_into_with_optional_defaults(target, include_defaults);
230 },
231 StorageSlotSchema::Map(slot) => {
232 target.write_u8(1u8);
233 slot.write_into_with_optional_defaults(target, include_defaults);
234 },
235 }
236 }
237}
238
239impl Serializable for StorageSlotSchema {
240 fn write_into<W: ByteWriter>(&self, target: &mut W) {
241 self.write_into_with_optional_defaults(target, true);
242 }
243}
244
245impl Deserializable for StorageSlotSchema {
246 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
247 let variant_tag = source.read_u8()?;
248 match variant_tag {
249 0 => Ok(StorageSlotSchema::Value(ValueSlotSchema::read_from(source)?)),
250 1 => Ok(StorageSlotSchema::Map(MapSlotSchema::read_from(source)?)),
251 _ => Err(DeserializationError::InvalidValue(format!(
252 "unknown variant tag '{variant_tag}' for StorageSlotSchema"
253 ))),
254 }
255 }
256}
257
258#[derive(Debug, Clone, PartialEq, Eq)]
266#[allow(clippy::large_enum_variant)]
267pub enum WordSchema {
268 Simple {
270 r#type: SchemaTypeId,
271 default_value: Option<Word>,
272 },
273 Composite { value: [FeltSchema; 4] },
275}
276
277impl WordSchema {
278 pub fn new_simple(r#type: SchemaTypeId) -> Self {
279 WordSchema::Simple { r#type, default_value: None }
280 }
281
282 pub fn new_simple_with_default(r#type: SchemaTypeId, default_value: Word) -> Self {
283 WordSchema::Simple {
284 r#type,
285 default_value: Some(default_value),
286 }
287 }
288
289 pub fn new_value(value: impl Into<[FeltSchema; 4]>) -> Self {
290 WordSchema::Composite { value: value.into() }
291 }
292
293 pub fn value(&self) -> Option<&[FeltSchema; 4]> {
294 match self {
295 WordSchema::Composite { value } => Some(value),
296 WordSchema::Simple { .. } => None,
297 }
298 }
299
300 pub fn word_type(&self) -> SchemaTypeId {
302 match self {
303 WordSchema::Simple { r#type, .. } => r#type.clone(),
304 WordSchema::Composite { .. } => SchemaTypeId::native_word(),
305 }
306 }
307
308 fn collect_init_value_requirements(
309 &self,
310 value_name: StorageValueName,
311 description: Option<String>,
312 requirements: &mut BTreeMap<StorageValueName, SchemaRequirement>,
313 ) -> Result<(), AccountComponentTemplateError> {
314 match self {
315 WordSchema::Simple { r#type, default_value } => {
316 if *r#type == SchemaTypeId::void() {
317 return Ok(());
318 }
319
320 let default_value = default_value.map(|word| {
321 SCHEMA_TYPE_REGISTRY.display_word(r#type, word).value().to_string()
322 });
323
324 if requirements
325 .insert(
326 value_name.clone(),
327 SchemaRequirement {
328 description,
329 r#type: r#type.clone(),
330 default_value,
331 },
332 )
333 .is_some()
334 {
335 return Err(AccountComponentTemplateError::DuplicateInitValueName(value_name));
336 }
337
338 Ok(())
339 },
340 WordSchema::Composite { value } => {
341 for felt in value.iter() {
342 felt.collect_init_value_requirements(value_name.clone(), requirements)?;
343 }
344 Ok(())
345 },
346 }
347 }
348
349 fn validate(&self) -> Result<(), AccountComponentTemplateError> {
351 let type_exists = SCHEMA_TYPE_REGISTRY.contains_word_type(&self.word_type());
352 if !type_exists {
353 return Err(AccountComponentTemplateError::InvalidType(
354 self.word_type().to_string(),
355 "Word".into(),
356 ));
357 }
358
359 if let WordSchema::Simple {
360 r#type,
361 default_value: Some(default_value),
362 } = self
363 {
364 SCHEMA_TYPE_REGISTRY
365 .validate_word_value(r#type, *default_value)
366 .map_err(AccountComponentTemplateError::StorageValueParsingError)?;
367 }
368
369 if let Some(felts) = self.value() {
370 for felt in felts {
371 felt.validate()?;
372 }
373 }
374
375 Ok(())
376 }
377
378 pub(crate) fn try_build_word(
384 &self,
385 init_storage_data: &InitStorageData,
386 slot_name: &StorageSlotName,
387 ) -> Result<Word, AccountComponentTemplateError> {
388 let slot_prefix = StorageValueName::from_slot_name(slot_name);
389 let slot_value = init_storage_data.slot_value_entry(slot_name);
390 let has_fields = init_storage_data.has_field_entries_for_slot(slot_name);
391
392 if init_storage_data.map_entries(slot_name).is_some() {
393 return Err(AccountComponentTemplateError::InvalidInitStorageValue(
394 slot_prefix,
395 "expected a value, got a map".into(),
396 ));
397 }
398
399 match self {
400 WordSchema::Simple { r#type, default_value } => {
401 if has_fields {
402 return Err(AccountComponentTemplateError::InvalidInitStorageValue(
403 slot_prefix,
404 "expected a value, got field entries".into(),
405 ));
406 }
407 match slot_value {
408 Some(value) => parse_storage_value_with_schema(self, value, &slot_prefix),
409 None => {
410 if *r#type == SchemaTypeId::void() {
411 Ok(Word::empty())
412 } else {
413 default_value.as_ref().copied().ok_or_else(|| {
414 AccountComponentTemplateError::InitValueNotProvided(slot_prefix)
415 })
416 }
417 },
418 }
419 },
420 WordSchema::Composite { value } => {
421 if let Some(value) = slot_value {
422 if has_fields {
423 return Err(AccountComponentTemplateError::InvalidInitStorageValue(
424 slot_prefix,
425 "expected a single value, got both value and field entries".into(),
426 ));
427 }
428 return parse_storage_value_with_schema(self, value, &slot_prefix);
429 }
430
431 let mut result = [Felt::ZERO; 4];
432 for (index, felt_schema) in value.iter().enumerate() {
433 result[index] = felt_schema.try_build_felt(init_storage_data, slot_name)?;
434 }
435 Ok(Word::from(result))
436 },
437 }
438 }
439
440 pub(crate) fn validate_word_value(
441 &self,
442 slot_prefix: &StorageValueName,
443 label: &str,
444 word: Word,
445 ) -> Result<(), AccountComponentTemplateError> {
446 match self {
447 WordSchema::Simple { r#type, .. } => {
448 SCHEMA_TYPE_REGISTRY.validate_word_value(r#type, word).map_err(|err| {
449 AccountComponentTemplateError::InvalidInitStorageValue(
450 slot_prefix.clone(),
451 format!("{label} does not match `{}`: {err}", r#type),
452 )
453 })
454 },
455 WordSchema::Composite { value } => {
456 for (index, felt_schema) in value.iter().enumerate() {
457 let felt_type = felt_schema.felt_type();
458 SCHEMA_TYPE_REGISTRY.validate_felt_value(&felt_type, word[index]).map_err(
459 |err| {
460 AccountComponentTemplateError::InvalidInitStorageValue(
461 slot_prefix.clone(),
462 format!("{label}[{index}] does not match `{felt_type}`: {err}"),
463 )
464 },
465 )?;
466 }
467
468 Ok(())
469 },
470 }
471 }
472
473 fn write_into_with_optional_defaults<W: ByteWriter>(
476 &self,
477 target: &mut W,
478 include_defaults: bool,
479 ) {
480 match self {
481 WordSchema::Simple { r#type, default_value } => {
482 target.write_u8(0);
483 target.write(r#type);
484 let default_value = if include_defaults { *default_value } else { None };
485 target.write(default_value);
486 },
487 WordSchema::Composite { value } => {
488 target.write_u8(1);
489 for felt in value.iter() {
490 felt.write_into_with_optional_defaults(target, include_defaults);
491 }
492 },
493 }
494 }
495}
496
497impl Serializable for WordSchema {
498 fn write_into<W: ByteWriter>(&self, target: &mut W) {
499 self.write_into_with_optional_defaults(target, true);
500 }
501}
502
503impl Deserializable for WordSchema {
504 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
505 let tag = source.read_u8()?;
506 match tag {
507 0 => {
508 let r#type = SchemaTypeId::read_from(source)?;
509 let default_value = Option::<Word>::read_from(source)?;
510 Ok(WordSchema::Simple { r#type, default_value })
511 },
512 1 => {
513 let value = <[FeltSchema; 4]>::read_from(source)?;
514 Ok(WordSchema::Composite { value })
515 },
516 other => Err(DeserializationError::InvalidValue(format!(
517 "unknown tag '{other}' for WordSchema"
518 ))),
519 }
520 }
521}
522
523impl From<[FeltSchema; 4]> for WordSchema {
524 fn from(value: [FeltSchema; 4]) -> Self {
525 WordSchema::new_value(value)
526 }
527}
528
529impl From<[Felt; 4]> for WordSchema {
530 fn from(value: [Felt; 4]) -> Self {
531 WordSchema::new_simple_with_default(SchemaTypeId::native_word(), Word::from(value))
532 }
533}
534
535#[derive(Debug, Clone, PartialEq, Eq)]
546pub struct FeltSchema {
547 name: Option<String>,
548 description: Option<String>,
549 r#type: SchemaTypeId,
550 default_value: Option<Felt>,
551}
552
553impl FeltSchema {
554 pub fn new_typed(r#type: SchemaTypeId, name: impl Into<String>) -> Self {
556 FeltSchema {
557 name: Some(name.into()),
558 description: None,
559 r#type,
560 default_value: None,
561 }
562 }
563
564 pub fn new_typed_with_default(
566 r#type: SchemaTypeId,
567 name: impl Into<String>,
568 default_value: Felt,
569 ) -> Self {
570 FeltSchema {
571 name: Some(name.into()),
572 description: None,
573 r#type,
574 default_value: Some(default_value),
575 }
576 }
577
578 pub fn new_void() -> Self {
580 FeltSchema {
581 name: None,
582 description: None,
583 r#type: SchemaTypeId::void(),
584 default_value: None,
585 }
586 }
587
588 pub fn with_description(self, description: impl Into<String>) -> Self {
590 FeltSchema {
591 description: Some(description.into()),
592 ..self
593 }
594 }
595
596 pub fn felt_type(&self) -> SchemaTypeId {
598 self.r#type.clone()
599 }
600
601 pub fn name(&self) -> Option<&str> {
602 self.name.as_deref()
603 }
604
605 pub fn description(&self) -> Option<&String> {
606 self.description.as_ref()
607 }
608
609 pub fn default_value(&self) -> Option<Felt> {
610 self.default_value
611 }
612
613 fn collect_init_value_requirements(
614 &self,
615 slot_prefix: StorageValueName,
616 requirements: &mut BTreeMap<StorageValueName, SchemaRequirement>,
617 ) -> Result<(), AccountComponentTemplateError> {
618 if self.r#type == SchemaTypeId::void() {
619 return Ok(());
620 }
621
622 let Some(name) = self.name.as_deref() else {
623 return Err(AccountComponentTemplateError::InvalidSchema(
624 "non-void felt elements must be named".into(),
625 ));
626 };
627 let value_name =
628 StorageValueName::from_slot_name_with_suffix(slot_prefix.slot_name(), name)
629 .map_err(|err| AccountComponentTemplateError::InvalidSchema(err.to_string()))?;
630
631 let default_value = self
632 .default_value
633 .map(|felt| SCHEMA_TYPE_REGISTRY.display_felt(&self.r#type, felt));
634
635 if requirements
636 .insert(
637 value_name.clone(),
638 SchemaRequirement {
639 description: self.description.clone(),
640 r#type: self.r#type.clone(),
641 default_value,
642 },
643 )
644 .is_some()
645 {
646 return Err(AccountComponentTemplateError::DuplicateInitValueName(value_name));
647 }
648
649 Ok(())
650 }
651
652 pub(crate) fn try_build_felt(
657 &self,
658 init_storage_data: &InitStorageData,
659 slot_name: &StorageSlotName,
660 ) -> Result<Felt, AccountComponentTemplateError> {
661 let value_name = match self.name.as_deref() {
662 Some(name) => Some(
663 StorageValueName::from_slot_name_with_suffix(slot_name, name)
664 .map_err(|err| AccountComponentTemplateError::InvalidSchema(err.to_string()))?,
665 ),
666 None => None,
667 };
668
669 if let Some(value_name) = value_name.clone()
670 && let Some(raw_value) = init_storage_data.value_entry(&value_name)
671 {
672 match raw_value {
673 WordValue::Atomic(raw) => {
674 let felt = SCHEMA_TYPE_REGISTRY
675 .try_parse_felt(&self.r#type, raw)
676 .map_err(AccountComponentTemplateError::StorageValueParsingError)?;
677 return Ok(felt);
678 },
679 WordValue::Elements(_) => {
680 return Err(AccountComponentTemplateError::InvalidInitStorageValue(
681 value_name,
682 "expected an atomic value, got a 4-element array".into(),
683 ));
684 },
685 WordValue::FullyTyped(_) => {
686 return Err(AccountComponentTemplateError::InvalidInitStorageValue(
687 value_name,
688 "expected an atomic value, got a word".into(),
689 ));
690 },
691 }
692 }
693
694 if self.r#type == SchemaTypeId::void() {
695 return Ok(Felt::ZERO);
696 }
697
698 if let Some(default_value) = self.default_value {
699 return Ok(default_value);
700 }
701
702 let Some(value_name) = value_name else {
703 return Err(AccountComponentTemplateError::InvalidSchema(
704 "non-void felt elements must be named".into(),
705 ));
706 };
707
708 Err(AccountComponentTemplateError::InitValueNotProvided(value_name))
709 }
710
711 fn write_into_with_optional_defaults<W: ByteWriter>(
714 &self,
715 target: &mut W,
716 include_defaults: bool,
717 ) {
718 target.write(&self.name);
719 target.write(&self.description);
720 target.write(&self.r#type);
721 let default_value = if include_defaults { self.default_value } else { None };
722 target.write(default_value);
723 }
724
725 fn validate(&self) -> Result<(), AccountComponentTemplateError> {
727 if let Some(description) = self.description.as_deref() {
728 validate_description_ascii(description)?;
729 }
730
731 let type_exists = SCHEMA_TYPE_REGISTRY.contains_felt_type(&self.felt_type());
732 if !type_exists {
733 return Err(AccountComponentTemplateError::InvalidType(
734 self.felt_type().to_string(),
735 "Felt".into(),
736 ));
737 }
738
739 if self.r#type == SchemaTypeId::void() {
740 if self.name.is_some() {
741 return Err(AccountComponentTemplateError::InvalidSchema(
742 "void felt elements must be unnamed".into(),
743 ));
744 }
745 if self.default_value.is_some() {
746 return Err(AccountComponentTemplateError::InvalidSchema(
747 "void felt elements cannot define `default-value`".into(),
748 ));
749 }
750 return Ok(());
751 }
752
753 if self.name.is_none() {
754 return Err(AccountComponentTemplateError::InvalidSchema(
755 "non-void felt elements must be named".into(),
756 ));
757 }
758
759 if let Some(value) = self.default_value {
760 SCHEMA_TYPE_REGISTRY
761 .validate_felt_value(&self.felt_type(), value)
762 .map_err(AccountComponentTemplateError::StorageValueParsingError)?;
763 }
764 Ok(())
765 }
766}
767
768impl Serializable for FeltSchema {
769 fn write_into<W: ByteWriter>(&self, target: &mut W) {
770 self.write_into_with_optional_defaults(target, true);
771 }
772}
773
774impl Deserializable for FeltSchema {
775 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
776 let name = Option::<String>::read_from(source)?;
777 let description = Option::<String>::read_from(source)?;
778 let r#type = SchemaTypeId::read_from(source)?;
779 let default_value = Option::<Felt>::read_from(source)?;
780 Ok(FeltSchema { name, description, r#type, default_value })
781 }
782}
783
784#[derive(Debug, Clone, PartialEq, Eq)]
786pub struct ValueSlotSchema {
787 description: Option<String>,
788 word: WordSchema,
789}
790
791impl ValueSlotSchema {
792 pub fn new(description: Option<String>, word: WordSchema) -> Self {
793 Self { description, word }
794 }
795
796 pub fn description(&self) -> Option<&String> {
797 self.description.as_ref()
798 }
799
800 pub fn word(&self) -> &WordSchema {
801 &self.word
802 }
803
804 fn collect_init_value_requirements(
805 &self,
806 value_name: StorageValueName,
807 requirements: &mut BTreeMap<StorageValueName, SchemaRequirement>,
808 ) -> Result<(), AccountComponentTemplateError> {
809 self.word.collect_init_value_requirements(
810 value_name,
811 self.description.clone(),
812 requirements,
813 )
814 }
815
816 pub fn try_build_word(
818 &self,
819 init_storage_data: &InitStorageData,
820 slot_name: &StorageSlotName,
821 ) -> Result<Word, AccountComponentTemplateError> {
822 self.word.try_build_word(init_storage_data, slot_name)
823 }
824
825 fn write_into_with_optional_defaults<W: ByteWriter>(
828 &self,
829 target: &mut W,
830 include_defaults: bool,
831 ) {
832 target.write(&self.description);
833 self.word.write_into_with_optional_defaults(target, include_defaults);
834 }
835
836 pub(crate) fn validate(&self) -> Result<(), AccountComponentTemplateError> {
838 if let Some(description) = self.description.as_deref() {
839 validate_description_ascii(description)?;
840 }
841 self.word.validate()?;
842 Ok(())
843 }
844}
845
846impl Serializable for ValueSlotSchema {
847 fn write_into<W: ByteWriter>(&self, target: &mut W) {
848 self.write_into_with_optional_defaults(target, true);
849 }
850}
851
852impl Deserializable for ValueSlotSchema {
853 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
854 let description = Option::<String>::read_from(source)?;
855 let word = WordSchema::read_from(source)?;
856 Ok(ValueSlotSchema::new(description, word))
857 }
858}
859
860#[derive(Debug, Clone, PartialEq, Eq)]
862pub struct MapSlotSchema {
863 description: Option<String>,
864 default_values: Option<BTreeMap<Word, Word>>,
865 key_schema: WordSchema,
866 value_schema: WordSchema,
867}
868
869impl MapSlotSchema {
870 pub fn new(
871 description: Option<String>,
872 default_values: Option<BTreeMap<Word, Word>>,
873 key_schema: WordSchema,
874 value_schema: WordSchema,
875 ) -> Self {
876 Self {
877 description,
878 default_values,
879 key_schema,
880 value_schema,
881 }
882 }
883
884 pub fn description(&self) -> Option<&String> {
885 self.description.as_ref()
886 }
887
888 pub fn try_build_map(
893 &self,
894 init_storage_data: &InitStorageData,
895 slot_name: &StorageSlotName,
896 ) -> Result<StorageMap, AccountComponentTemplateError> {
897 let mut entries = self.default_values.clone().unwrap_or_default();
898 let slot_prefix = StorageValueName::from_slot_name(slot_name);
899
900 if init_storage_data.slot_value_entry(slot_name).is_some() {
901 return Err(AccountComponentTemplateError::InvalidInitStorageValue(
902 slot_prefix,
903 "expected a map, got a value".into(),
904 ));
905 }
906 if init_storage_data.has_field_entries_for_slot(slot_name) {
907 return Err(AccountComponentTemplateError::InvalidInitStorageValue(
908 slot_prefix,
909 "expected a map, got field entries".into(),
910 ));
911 }
912 if let Some(init_entries) = init_storage_data.map_entries(slot_name) {
913 let mut parsed_entries = Vec::with_capacity(init_entries.len());
914 for (raw_key, raw_value) in init_entries.iter() {
915 let key = parse_storage_value_with_schema(&self.key_schema, raw_key, &slot_prefix)?;
916 let value =
917 parse_storage_value_with_schema(&self.value_schema, raw_value, &slot_prefix)?;
918
919 parsed_entries.push((key, value));
920 }
921
922 for (key, value) in parsed_entries.iter() {
923 entries.insert(*key, *value);
924 }
925 }
926
927 if entries.is_empty() {
928 return Ok(StorageMap::new());
929 }
930
931 StorageMap::with_entries(entries)
932 .map_err(|err| AccountComponentTemplateError::StorageMapHasDuplicateKeys(Box::new(err)))
933 }
934
935 pub fn key_schema(&self) -> &WordSchema {
936 &self.key_schema
937 }
938
939 pub fn value_schema(&self) -> &WordSchema {
940 &self.value_schema
941 }
942
943 pub fn default_values(&self) -> Option<BTreeMap<Word, Word>> {
944 self.default_values.clone()
945 }
946
947 fn write_into_with_optional_defaults<W: ByteWriter>(
950 &self,
951 target: &mut W,
952 include_defaults: bool,
953 ) {
954 target.write(&self.description);
955 let default_values = if include_defaults {
956 self.default_values.clone()
957 } else {
958 None
959 };
960 target.write(&default_values);
961 self.key_schema.write_into_with_optional_defaults(target, include_defaults);
962 self.value_schema.write_into_with_optional_defaults(target, include_defaults);
963 }
964
965 fn validate(&self) -> Result<(), AccountComponentTemplateError> {
967 if let Some(description) = self.description.as_deref() {
968 validate_description_ascii(description)?;
969 }
970 self.key_schema.validate()?;
971 self.value_schema.validate()?;
972 Ok(())
973 }
974}
975
976pub(super) fn parse_storage_value_with_schema(
977 schema: &WordSchema,
978 raw_value: &WordValue,
979 slot_prefix: &StorageValueName,
980) -> Result<Word, AccountComponentTemplateError> {
981 let word = match (schema, raw_value) {
982 (_, WordValue::FullyTyped(word)) => *word,
983 (WordSchema::Simple { r#type, .. }, raw_value) => {
984 parse_simple_word_value(r#type, raw_value, slot_prefix)?
985 },
986 (WordSchema::Composite { value }, WordValue::Elements(elements)) => {
987 parse_composite_elements(value, elements, slot_prefix)?
988 },
989 (WordSchema::Composite { .. }, WordValue::Atomic(value)) => SCHEMA_TYPE_REGISTRY
990 .try_parse_word(&SchemaTypeId::native_word(), value)
991 .map_err(|err| {
992 AccountComponentTemplateError::InvalidInitStorageValue(
993 slot_prefix.clone(),
994 format!("failed to parse value as `word`: {err}"),
995 )
996 })?,
997 };
998
999 schema.validate_word_value(slot_prefix, "value", word)?;
1000 Ok(word)
1001}
1002
1003fn parse_simple_word_value(
1004 schema_type: &SchemaTypeId,
1005 raw_value: &WordValue,
1006 slot_prefix: &StorageValueName,
1007) -> Result<Word, AccountComponentTemplateError> {
1008 match raw_value {
1009 WordValue::Atomic(value) => {
1010 SCHEMA_TYPE_REGISTRY.try_parse_word(schema_type, value).map_err(|err| {
1011 AccountComponentTemplateError::InvalidInitStorageValue(
1012 slot_prefix.clone(),
1013 format!("failed to parse value as `{}`: {err}", schema_type),
1014 )
1015 })
1016 },
1017 WordValue::Elements(elements) => {
1018 let felts: Vec<Felt> = elements
1019 .iter()
1020 .map(|element| {
1021 SCHEMA_TYPE_REGISTRY.try_parse_felt(&SchemaTypeId::native_felt(), element)
1022 })
1023 .collect::<Result<_, _>>()
1024 .map_err(|err| {
1025 AccountComponentTemplateError::InvalidInitStorageValue(
1026 slot_prefix.clone(),
1027 format!("failed to parse value element as `felt`: {err}"),
1028 )
1029 })?;
1030 let felts: [Felt; 4] = felts.try_into().expect("length is 4");
1031 Ok(Word::from(felts))
1032 },
1033 WordValue::FullyTyped(word) => Ok(*word),
1034 }
1035}
1036
1037fn parse_composite_elements(
1038 schema: &[FeltSchema; 4],
1039 elements: &[String; 4],
1040 slot_prefix: &StorageValueName,
1041) -> Result<Word, AccountComponentTemplateError> {
1042 let mut felts = [Felt::ZERO; 4];
1043 for (index, felt_schema) in schema.iter().enumerate() {
1044 let felt_type = felt_schema.felt_type();
1045 felts[index] =
1046 SCHEMA_TYPE_REGISTRY
1047 .try_parse_felt(&felt_type, &elements[index])
1048 .map_err(|err| {
1049 AccountComponentTemplateError::InvalidInitStorageValue(
1050 slot_prefix.clone(),
1051 format!("failed to parse value[{index}] as `{felt_type}`: {err}"),
1052 )
1053 })?;
1054 }
1055 Ok(Word::from(felts))
1056}
1057
1058impl Serializable for MapSlotSchema {
1059 fn write_into<W: ByteWriter>(&self, target: &mut W) {
1060 self.write_into_with_optional_defaults(target, true);
1061 }
1062}
1063
1064impl Deserializable for MapSlotSchema {
1065 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1066 let description = Option::<String>::read_from(source)?;
1067 let default_values = Option::<BTreeMap<Word, Word>>::read_from(source)?;
1068 let key_schema = WordSchema::read_from(source)?;
1069 let value_schema = WordSchema::read_from(source)?;
1070 Ok(MapSlotSchema::new(description, default_values, key_schema, value_schema))
1071 }
1072}
1073
1074#[cfg(test)]
1078mod tests {
1079 use alloc::collections::BTreeMap;
1080
1081 use super::*;
1082
1083 #[test]
1084 fn map_slot_schema_default_values_returns_map() {
1085 let word_schema = WordSchema::new_simple(SchemaTypeId::native_word());
1086 let mut default_values = BTreeMap::new();
1087 default_values.insert(
1088 Word::from([Felt::new(1), Felt::new(0), Felt::new(0), Felt::new(0)]),
1089 Word::from([Felt::new(10), Felt::new(11), Felt::new(12), Felt::new(13)]),
1090 );
1091 let slot = MapSlotSchema::new(
1092 Some("static map".into()),
1093 Some(default_values),
1094 word_schema.clone(),
1095 word_schema,
1096 );
1097
1098 let mut expected = BTreeMap::new();
1099 expected.insert(
1100 Word::from([Felt::new(1), Felt::new(0), Felt::new(0), Felt::new(0)]),
1101 Word::from([Felt::new(10), Felt::new(11), Felt::new(12), Felt::new(13)]),
1102 );
1103
1104 assert_eq!(slot.default_values(), Some(expected));
1105 }
1106
1107 #[test]
1108 fn value_slot_schema_exposes_felt_schema_types() {
1109 let felt_values = [
1110 FeltSchema::new_typed(SchemaTypeId::u8(), "a"),
1111 FeltSchema::new_typed(SchemaTypeId::u16(), "b"),
1112 FeltSchema::new_typed(SchemaTypeId::u32(), "c"),
1113 FeltSchema::new_typed(SchemaTypeId::new("felt").unwrap(), "d"),
1114 ];
1115
1116 let slot = ValueSlotSchema::new(None, WordSchema::new_value(felt_values));
1117 let WordSchema::Composite { value } = slot.word() else {
1118 panic!("expected composite word schema");
1119 };
1120
1121 assert_eq!(value[0].felt_type(), SchemaTypeId::u8());
1122 assert_eq!(value[1].felt_type(), SchemaTypeId::u16());
1123 assert_eq!(value[2].felt_type(), SchemaTypeId::u32());
1124 assert_eq!(value[3].felt_type(), SchemaTypeId::new("felt").unwrap());
1125 }
1126
1127 #[test]
1128 fn map_slot_schema_key_and_value_types() {
1129 let key_schema = WordSchema::new_simple(SchemaTypeId::new("sampling::Key").unwrap());
1130
1131 let value_schema = WordSchema::new_value([
1132 FeltSchema::new_typed(SchemaTypeId::native_felt(), "a"),
1133 FeltSchema::new_typed(SchemaTypeId::native_felt(), "b"),
1134 FeltSchema::new_typed(SchemaTypeId::native_felt(), "c"),
1135 FeltSchema::new_typed(SchemaTypeId::native_felt(), "d"),
1136 ]);
1137
1138 let slot = MapSlotSchema::new(None, None, key_schema, value_schema);
1139
1140 assert_eq!(
1141 slot.key_schema(),
1142 &WordSchema::new_simple(SchemaTypeId::new("sampling::Key").unwrap())
1143 );
1144
1145 let WordSchema::Composite { value } = slot.value_schema() else {
1146 panic!("expected composite word schema for map values");
1147 };
1148 for felt in value.iter() {
1149 assert_eq!(felt.felt_type(), SchemaTypeId::native_felt());
1150 }
1151 }
1152
1153 #[test]
1154 fn value_slot_schema_accepts_typed_word_init_value() {
1155 let slot = ValueSlotSchema::new(None, WordSchema::new_simple(SchemaTypeId::native_word()));
1156 let slot_name: StorageSlotName = "demo::slot".parse().unwrap();
1157
1158 let expected = Word::from([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]);
1159 let mut init_data = InitStorageData::default();
1160 init_data
1161 .set_value(StorageValueName::from_slot_name(&slot_name), expected)
1162 .unwrap();
1163
1164 let built = slot.try_build_word(&init_data, &slot_name).unwrap();
1165 assert_eq!(built, expected);
1166 }
1167
1168 #[test]
1169 fn value_slot_schema_accepts_felt_typed_word_init_value() {
1170 let slot = ValueSlotSchema::new(None, WordSchema::new_simple(SchemaTypeId::u8()));
1171 let slot_name: StorageSlotName = "demo::u8_word".parse().unwrap();
1172
1173 let mut init_data = InitStorageData::default();
1174 init_data.set_value(StorageValueName::from_slot_name(&slot_name), "6").unwrap();
1175
1176 let built = slot.try_build_word(&init_data, &slot_name).unwrap();
1177 assert_eq!(built, Word::from([Felt::new(0), Felt::new(0), Felt::new(0), Felt::new(6)]));
1178 }
1179
1180 #[test]
1181 fn value_slot_schema_accepts_typed_felt_init_value_in_composed_word() {
1182 let word = WordSchema::new_value([
1183 FeltSchema::new_typed(SchemaTypeId::u8(), "a"),
1184 FeltSchema::new_typed_with_default(SchemaTypeId::native_felt(), "b", Felt::new(2)),
1185 FeltSchema::new_typed_with_default(SchemaTypeId::native_felt(), "c", Felt::new(3)),
1186 FeltSchema::new_typed_with_default(SchemaTypeId::native_felt(), "d", Felt::new(4)),
1187 ]);
1188 let slot = ValueSlotSchema::new(None, word);
1189 let slot_name: StorageSlotName = "demo::slot".parse().unwrap();
1190
1191 let mut init_data = InitStorageData::default();
1192 init_data
1193 .set_value(StorageValueName::from_slot_name_with_suffix(&slot_name, "a").unwrap(), "1")
1194 .unwrap();
1195
1196 let built = slot.try_build_word(&init_data, &slot_name).unwrap();
1197 assert_eq!(built, Word::from([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]));
1198 }
1199
1200 #[test]
1201 fn map_slot_schema_accepts_typed_map_init_value() {
1202 let word_schema = WordSchema::new_simple(SchemaTypeId::native_word());
1203 let slot = MapSlotSchema::new(None, None, word_schema.clone(), word_schema);
1204 let slot_name: StorageSlotName = "demo::map".parse().unwrap();
1205
1206 let entries = vec![(
1207 WordValue::Elements(["1".into(), "0".into(), "0".into(), "0".into()]),
1208 WordValue::Elements(["10".into(), "11".into(), "12".into(), "13".into()]),
1209 )];
1210 let mut init_data = InitStorageData::default();
1211 init_data.set_map_values(slot_name.clone(), entries.clone()).unwrap();
1212
1213 let built = slot.try_build_map(&init_data, &slot_name).unwrap();
1214 let expected = StorageMap::with_entries([(
1215 Word::from([Felt::new(1), Felt::new(0), Felt::new(0), Felt::new(0)]),
1216 Word::from([Felt::new(10), Felt::new(11), Felt::new(12), Felt::new(13)]),
1217 )])
1218 .unwrap();
1219 assert_eq!(built, expected);
1220 }
1221
1222 #[test]
1223 fn map_slot_schema_missing_init_value_defaults_to_empty_map() {
1224 let word_schema = WordSchema::new_simple(SchemaTypeId::native_word());
1225 let slot = MapSlotSchema::new(None, None, word_schema.clone(), word_schema);
1226 let built = slot
1227 .try_build_map(&InitStorageData::default(), &"demo::map".parse().unwrap())
1228 .unwrap();
1229 assert_eq!(built, StorageMap::new());
1230 }
1231}