Skip to main content

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

1use alloc::collections::BTreeMap;
2use alloc::string::{String, ToString};
3
4use super::super::type_registry::{SCHEMA_TYPE_REGISTRY, SchemaRequirement, SchemaType};
5use super::super::{InitStorageData, StorageValueName};
6use super::FeltSchema;
7use crate::account::StorageSlotName;
8use crate::errors::ComponentMetadataError;
9use crate::utils::serde::{
10    ByteReader,
11    ByteWriter,
12    Deserializable,
13    DeserializationError,
14    Serializable,
15};
16use crate::{Felt, Word};
17
18// WORD SCHEMA
19// ================================================================================================
20
21/// Defines how a word slot is described within the component's storage schema.
22///
23/// Each word schema can either describe a whole-word typed value supplied at instantiation time
24/// (`Simple`) or a composite word that explicitly defines each felt element (`Composite`).
25#[derive(Debug, Clone, PartialEq, Eq)]
26#[allow(clippy::large_enum_variant)]
27pub enum WordSchema {
28    /// A whole-word typed value supplied at instantiation time.
29    Simple {
30        r#type: SchemaType,
31        default_value: Option<Word>,
32    },
33    /// A composed word that may mix defaults and typed fields.
34    Composite { value: [FeltSchema; 4] },
35}
36
37impl WordSchema {
38    pub fn new_simple(r#type: SchemaType) -> Self {
39        WordSchema::Simple { r#type, default_value: None }
40    }
41
42    pub fn new_simple_with_default(r#type: SchemaType, default_value: Word) -> Self {
43        WordSchema::Simple {
44            r#type,
45            default_value: Some(default_value),
46        }
47    }
48
49    pub fn new_value(value: impl Into<[FeltSchema; 4]>) -> Self {
50        WordSchema::Composite { value: value.into() }
51    }
52
53    pub fn value(&self) -> Option<&[FeltSchema; 4]> {
54        match self {
55            WordSchema::Composite { value } => Some(value),
56            WordSchema::Simple { .. } => None,
57        }
58    }
59
60    /// Returns the schema type associated with whole-word init-supplied values.
61    pub fn word_type(&self) -> SchemaType {
62        match self {
63            WordSchema::Simple { r#type, .. } => r#type.clone(),
64            WordSchema::Composite { .. } => SchemaType::native_word(),
65        }
66    }
67
68    pub(super) fn collect_init_value_requirements(
69        &self,
70        value_name: StorageValueName,
71        description: Option<String>,
72        requirements: &mut BTreeMap<StorageValueName, SchemaRequirement>,
73    ) -> Result<(), ComponentMetadataError> {
74        match self {
75            WordSchema::Simple { r#type, default_value } => {
76                if *r#type == SchemaType::void() {
77                    return Ok(());
78                }
79
80                let default_value = default_value.map(|word| {
81                    SCHEMA_TYPE_REGISTRY.display_word(r#type, word).value().to_string()
82                });
83
84                if requirements
85                    .insert(
86                        value_name.clone(),
87                        SchemaRequirement {
88                            description,
89                            r#type: r#type.clone(),
90                            default_value,
91                        },
92                    )
93                    .is_some()
94                {
95                    return Err(ComponentMetadataError::DuplicateInitValueName(value_name));
96                }
97
98                Ok(())
99            },
100            WordSchema::Composite { value } => {
101                for felt in value.iter() {
102                    felt.collect_init_value_requirements(value_name.clone(), requirements)?;
103                }
104                Ok(())
105            },
106        }
107    }
108
109    /// Validates that the defined word type exists and its inner felts (if any) are valid.
110    pub(super) fn validate(&self) -> Result<(), ComponentMetadataError> {
111        let type_exists = SCHEMA_TYPE_REGISTRY.contains_word_type(&self.word_type());
112        if !type_exists {
113            return Err(ComponentMetadataError::InvalidType(
114                self.word_type().to_string(),
115                "Word".into(),
116            ));
117        }
118
119        if let WordSchema::Simple {
120            r#type,
121            default_value: Some(default_value),
122        } = self
123        {
124            SCHEMA_TYPE_REGISTRY
125                .validate_word_value(r#type, *default_value)
126                .map_err(ComponentMetadataError::StorageValueParsingError)?;
127        }
128
129        if let Some(felts) = self.value() {
130            for felt in felts {
131                felt.validate()?;
132            }
133        }
134
135        Ok(())
136    }
137
138    /// Builds a [`Word`] from the provided initialization data according to this schema.
139    ///
140    /// For simple schemas, expects a direct slot value (not map or field entries).
141    /// For composite schemas, either parses a single value or builds the word from individual
142    /// felt entries.
143    pub(crate) fn try_build_word(
144        &self,
145        init_storage_data: &InitStorageData,
146        slot_name: &StorageSlotName,
147    ) -> Result<Word, ComponentMetadataError> {
148        let slot_prefix = StorageValueName::from_slot_name(slot_name);
149        let slot_value = init_storage_data.slot_value_entry(slot_name);
150        let has_fields = init_storage_data.has_field_entries_for_slot(slot_name);
151
152        if init_storage_data.map_entries(slot_name).is_some() {
153            return Err(ComponentMetadataError::InvalidInitStorageValue(
154                slot_prefix,
155                "expected a value, got a map".into(),
156            ));
157        }
158
159        match self {
160            WordSchema::Simple { r#type, default_value } => {
161                if has_fields {
162                    return Err(ComponentMetadataError::InvalidInitStorageValue(
163                        slot_prefix,
164                        "expected a value, got field entries".into(),
165                    ));
166                }
167                match slot_value {
168                    Some(value) => {
169                        super::parse_storage_value_with_schema(self, value, &slot_prefix)
170                    },
171                    None => {
172                        if *r#type == SchemaType::void() {
173                            Ok(Word::empty())
174                        } else {
175                            default_value.as_ref().copied().ok_or_else(|| {
176                                ComponentMetadataError::InitValueNotProvided(slot_prefix)
177                            })
178                        }
179                    },
180                }
181            },
182            WordSchema::Composite { value } => {
183                if let Some(value) = slot_value {
184                    if has_fields {
185                        return Err(ComponentMetadataError::InvalidInitStorageValue(
186                            slot_prefix,
187                            "expected a single value, got both value and field entries".into(),
188                        ));
189                    }
190                    return super::parse_storage_value_with_schema(self, value, &slot_prefix);
191                }
192
193                let mut result = [Felt::ZERO; 4];
194                for (index, felt_schema) in value.iter().enumerate() {
195                    result[index] = felt_schema.try_build_felt(init_storage_data, slot_name)?;
196                }
197                Ok(Word::from(result))
198            },
199        }
200    }
201
202    pub(crate) fn validate_word_value(
203        &self,
204        slot_prefix: &StorageValueName,
205        label: &str,
206        word: Word,
207    ) -> Result<(), ComponentMetadataError> {
208        match self {
209            WordSchema::Simple { r#type, .. } => {
210                SCHEMA_TYPE_REGISTRY.validate_word_value(r#type, word).map_err(|err| {
211                    ComponentMetadataError::InvalidInitStorageValue(
212                        slot_prefix.clone(),
213                        format!("{label} does not match `{}`: {err}", r#type),
214                    )
215                })
216            },
217            WordSchema::Composite { value } => {
218                for (index, felt_schema) in value.iter().enumerate() {
219                    let felt_type = felt_schema.felt_type();
220                    SCHEMA_TYPE_REGISTRY.validate_felt_value(&felt_type, word[index]).map_err(
221                        |err| {
222                            ComponentMetadataError::InvalidInitStorageValue(
223                                slot_prefix.clone(),
224                                format!("{label}[{index}] does not match `{felt_type}`: {err}"),
225                            )
226                        },
227                    )?;
228                }
229
230                Ok(())
231            },
232        }
233    }
234
235    pub(super) fn write_into_with_optional_defaults<W: ByteWriter>(
236        &self,
237        target: &mut W,
238        include_defaults: bool,
239    ) {
240        match self {
241            WordSchema::Simple { r#type, default_value } => {
242                target.write_u8(0);
243                target.write(r#type);
244                let default_value = if include_defaults { *default_value } else { None };
245                target.write(default_value);
246            },
247            WordSchema::Composite { value } => {
248                target.write_u8(1);
249                for felt in value.iter() {
250                    felt.write_into_with_optional_defaults(target, include_defaults);
251                }
252            },
253        }
254    }
255}
256
257impl Serializable for WordSchema {
258    fn write_into<W: ByteWriter>(&self, target: &mut W) {
259        self.write_into_with_optional_defaults(target, true);
260    }
261}
262
263impl Deserializable for WordSchema {
264    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
265        let tag = source.read_u8()?;
266        match tag {
267            0 => {
268                let r#type = SchemaType::read_from(source)?;
269                let default_value = Option::<Word>::read_from(source)?;
270                Ok(WordSchema::Simple { r#type, default_value })
271            },
272            1 => {
273                let value = <[FeltSchema; 4]>::read_from(source)?;
274                Ok(WordSchema::Composite { value })
275            },
276            other => Err(DeserializationError::InvalidValue(format!(
277                "unknown tag '{other}' for WordSchema"
278            ))),
279        }
280    }
281}
282
283impl From<SchemaType> for WordSchema {
284    fn from(r#type: SchemaType) -> Self {
285        WordSchema::new_simple(r#type)
286    }
287}
288
289impl From<[FeltSchema; 4]> for WordSchema {
290    fn from(value: [FeltSchema; 4]) -> Self {
291        WordSchema::new_value(value)
292    }
293}
294
295impl From<[Felt; 4]> for WordSchema {
296    fn from(value: [Felt; 4]) -> Self {
297        WordSchema::new_simple_with_default(SchemaType::native_word(), Word::from(value))
298    }
299}