miden_protocol/account/component/storage/schema/
word.rs1use 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#[derive(Debug, Clone, PartialEq, Eq)]
22#[allow(clippy::large_enum_variant)]
23pub enum WordSchema {
24 Simple {
26 r#type: SchemaType,
27 default_value: Option<Word>,
28 },
29 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 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 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 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}