Skip to main content

miden_protocol/account/component/storage/schema/
mod.rs

1use alloc::collections::BTreeMap;
2use alloc::string::ToString;
3use alloc::vec::Vec;
4
5use miden_core::utils::{ByteReader, ByteWriter, Deserializable, Serializable};
6use miden_processor::DeserializationError;
7
8use super::type_registry::SchemaRequirement;
9use super::{InitStorageData, StorageValueName};
10use crate::account::{StorageSlot, StorageSlotName};
11use crate::crypto::utils::bytes_to_elements_with_padding;
12use crate::errors::ComponentMetadataError;
13use crate::{Hasher, Word};
14
15mod felt;
16pub use felt::FeltSchema;
17
18mod map_slot;
19pub use map_slot::MapSlotSchema;
20
21mod parse;
22pub(crate) use parse::parse_storage_value_with_schema;
23
24mod slot;
25pub use slot::StorageSlotSchema;
26
27mod value_slot;
28pub use value_slot::ValueSlotSchema;
29
30mod word;
31pub use word::WordSchema;
32
33#[cfg(test)]
34mod tests;
35
36// STORAGE SCHEMA
37// ================================================================================================
38
39/// Describes the storage schema of an account component in terms of its named storage slots.
40#[derive(Debug, Clone, Default, PartialEq, Eq)]
41pub struct StorageSchema {
42    slots: BTreeMap<StorageSlotName, StorageSlotSchema>,
43}
44
45impl StorageSchema {
46    /// Creates a new [`StorageSchema`].
47    ///
48    /// # Errors
49    /// - If `fields` contains duplicate slot names.
50    /// - If any slot schema is invalid.
51    /// - If multiple schema fields map to the same init value name.
52    pub fn new(
53        slots: impl IntoIterator<Item = (StorageSlotName, StorageSlotSchema)>,
54    ) -> Result<Self, ComponentMetadataError> {
55        let mut map = BTreeMap::new();
56        for (slot_name, schema) in slots {
57            if map.insert(slot_name.clone(), schema).is_some() {
58                return Err(ComponentMetadataError::DuplicateSlotName(slot_name));
59            }
60        }
61
62        let schema = Self { slots: map };
63        schema.validate()?;
64        Ok(schema)
65    }
66
67    /// Returns an iterator over `(slot_name, schema)` pairs in slot-id order.
68    pub fn iter(&self) -> impl Iterator<Item = (&StorageSlotName, &StorageSlotSchema)> {
69        self.slots.iter()
70    }
71
72    /// Returns a reference to the underlying slots map.
73    pub fn slots(&self) -> &BTreeMap<StorageSlotName, StorageSlotSchema> {
74        &self.slots
75    }
76
77    /// Builds the initial [`StorageSlot`]s for this schema using the provided initialization data.
78    pub fn build_storage_slots(
79        &self,
80        init_storage_data: &InitStorageData,
81    ) -> Result<Vec<StorageSlot>, ComponentMetadataError> {
82        self.slots
83            .iter()
84            .map(|(slot_name, schema)| schema.try_build_storage_slot(slot_name, init_storage_data))
85            .collect()
86    }
87
88    /// Returns a commitment to this storage schema definition.
89    ///
90    /// The commitment is computed over the serialized schema and does not include defaults.
91    pub fn commitment(&self) -> Word {
92        let mut bytes = Vec::new();
93        self.write_into_with_optional_defaults(&mut bytes, false);
94        let elements = bytes_to_elements_with_padding(&bytes);
95        Hasher::hash_elements(&elements)
96    }
97
98    /// Returns init-value requirements for the entire schema.
99    ///
100    /// The returned map includes both required values (no `default_value`) and optional values
101    /// (with `default_value`), and excludes map entries.
102    pub fn schema_requirements(
103        &self,
104    ) -> Result<BTreeMap<StorageValueName, SchemaRequirement>, ComponentMetadataError> {
105        let mut requirements = BTreeMap::new();
106        for (slot_name, schema) in self.slots.iter() {
107            schema.collect_init_value_requirements(slot_name, &mut requirements)?;
108        }
109        Ok(requirements)
110    }
111
112    /// Serializes the schema, optionally ignoring the default values (used for committing to a
113    /// schema definition).
114    fn write_into_with_optional_defaults<W: ByteWriter>(
115        &self,
116        target: &mut W,
117        include_defaults: bool,
118    ) {
119        target.write_u16(self.slots.len() as u16);
120        for (slot_name, schema) in self.slots.iter() {
121            target.write(slot_name);
122            schema.write_into_with_optional_defaults(target, include_defaults);
123        }
124    }
125
126    /// Validates schema-level invariants across all slots.
127    fn validate(&self) -> Result<(), ComponentMetadataError> {
128        let mut init_values = BTreeMap::new();
129
130        for (slot_name, schema) in self.slots.iter() {
131            schema.validate()?;
132            schema.collect_init_value_requirements(slot_name, &mut init_values)?;
133        }
134
135        Ok(())
136    }
137}
138
139impl Serializable for StorageSchema {
140    fn write_into<W: ByteWriter>(&self, target: &mut W) {
141        self.write_into_with_optional_defaults(target, true);
142    }
143}
144
145impl Deserializable for StorageSchema {
146    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
147        let num_entries = source.read_u16()? as usize;
148        let mut fields = BTreeMap::new();
149
150        for _ in 0..num_entries {
151            let slot_name = StorageSlotName::read_from(source)?;
152            let schema = StorageSlotSchema::read_from(source)?;
153
154            if fields.insert(slot_name.clone(), schema).is_some() {
155                return Err(DeserializationError::InvalidValue(format!(
156                    "duplicate slot name in storage schema: {slot_name}",
157                )));
158            }
159        }
160
161        let schema = StorageSchema::new(fields)
162            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
163        Ok(schema)
164    }
165}
166
167pub(super) fn validate_description_ascii(description: &str) -> Result<(), ComponentMetadataError> {
168    if description.is_ascii() {
169        Ok(())
170    } else {
171        Err(ComponentMetadataError::InvalidSchema(
172            "description must contain only ASCII characters".to_string(),
173        ))
174    }
175}