Skip to main content

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

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