Skip to main content

miden_protocol/account/component/storage/schema/
felt.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, WordValue};
9use super::validate_description_ascii;
10use crate::account::StorageSlotName;
11use crate::errors::ComponentMetadataError;
12use crate::{Felt, FieldElement};
13
14// FELT SCHEMA
15// ================================================================================================
16
17/// Supported element schema descriptors for a component's storage entries.
18///
19/// Each felt element in a composed word slot is typed, can have an optional default value, and can
20/// optionally be named to allow overriding at instantiation time.
21///
22/// To avoid non-overridable constants, unnamed elements are allowed only when `type = "void"`,
23/// which always evaluates to `0` and does not require init data.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct FeltSchema {
26    name: Option<String>,
27    description: Option<String>,
28    r#type: SchemaType,
29    default_value: Option<Felt>,
30}
31
32impl FeltSchema {
33    /// Creates a new required typed felt field.
34    pub fn new_typed(r#type: SchemaType, name: impl Into<String>) -> Self {
35        FeltSchema {
36            name: Some(name.into()),
37            description: None,
38            r#type,
39            default_value: None,
40        }
41    }
42
43    /// Creates a new typed felt field with a default value.
44    pub fn new_typed_with_default(
45        r#type: SchemaType,
46        name: impl Into<String>,
47        default_value: Felt,
48    ) -> Self {
49        FeltSchema {
50            name: Some(name.into()),
51            description: None,
52            r#type,
53            default_value: Some(default_value),
54        }
55    }
56
57    /// Creates an unnamed `void` felt element.
58    pub fn new_void() -> Self {
59        FeltSchema {
60            name: None,
61            description: None,
62            r#type: SchemaType::void(),
63            default_value: None,
64        }
65    }
66
67    /// Creates a new required felt field typed as [`SchemaType::native_felt()`].
68    pub fn felt(name: impl Into<String>) -> Self {
69        Self::new_typed(SchemaType::native_felt(), name)
70    }
71
72    /// Creates a new required felt field typed as [`SchemaType::native_word()`].
73    pub fn word(name: impl Into<String>) -> Self {
74        Self::new_typed(SchemaType::native_word(), name)
75    }
76
77    /// Creates a new required felt field typed as [`SchemaType::u8()`].
78    pub fn u8(name: impl Into<String>) -> Self {
79        Self::new_typed(SchemaType::u8(), name)
80    }
81
82    /// Creates a new required felt field typed as [`SchemaType::u16()`].
83    pub fn u16(name: impl Into<String>) -> Self {
84        Self::new_typed(SchemaType::u16(), name)
85    }
86
87    /// Creates a new required felt field typed as [`SchemaType::u32()`].
88    pub fn u32(name: impl Into<String>) -> Self {
89        Self::new_typed(SchemaType::u32(), name)
90    }
91
92    /// Sets the default value of the [`FeltSchema`] and returns `self`.
93    pub fn with_default(self, default_value: Felt) -> Self {
94        FeltSchema {
95            default_value: Some(default_value),
96            ..self
97        }
98    }
99
100    /// Sets the description of the [`FeltSchema`] and returns `self`.
101    pub fn with_description(self, description: impl Into<String>) -> Self {
102        FeltSchema {
103            description: Some(description.into()),
104            ..self
105        }
106    }
107
108    /// Returns the felt type.
109    pub fn felt_type(&self) -> SchemaType {
110        self.r#type.clone()
111    }
112
113    pub fn name(&self) -> Option<&str> {
114        self.name.as_deref()
115    }
116
117    pub fn description(&self) -> Option<&String> {
118        self.description.as_ref()
119    }
120
121    pub fn default_value(&self) -> Option<Felt> {
122        self.default_value
123    }
124
125    pub(super) fn collect_init_value_requirements(
126        &self,
127        slot_prefix: StorageValueName,
128        requirements: &mut BTreeMap<StorageValueName, SchemaRequirement>,
129    ) -> Result<(), ComponentMetadataError> {
130        if self.r#type == SchemaType::void() {
131            return Ok(());
132        }
133
134        let Some(name) = self.name.as_deref() else {
135            return Err(ComponentMetadataError::InvalidSchema(
136                "non-void felt elements must be named".into(),
137            ));
138        };
139        let value_name =
140            StorageValueName::from_slot_name_with_suffix(slot_prefix.slot_name(), name)
141                .map_err(|err| ComponentMetadataError::InvalidSchema(err.to_string()))?;
142
143        let default_value = self
144            .default_value
145            .map(|felt| SCHEMA_TYPE_REGISTRY.display_felt(&self.r#type, felt));
146
147        if requirements
148            .insert(
149                value_name.clone(),
150                SchemaRequirement {
151                    description: self.description.clone(),
152                    r#type: self.r#type.clone(),
153                    default_value,
154                },
155            )
156            .is_some()
157        {
158            return Err(ComponentMetadataError::DuplicateInitValueName(value_name));
159        }
160
161        Ok(())
162    }
163
164    /// Attempts to convert the [`FeltSchema`] into a [`Felt`].
165    ///
166    /// If the schema variant is typed, the value is retrieved from `init_storage_data`,
167    /// identified by its key. Otherwise, the returned value is just the inner element.
168    pub(crate) fn try_build_felt(
169        &self,
170        init_storage_data: &InitStorageData,
171        slot_name: &StorageSlotName,
172    ) -> Result<Felt, ComponentMetadataError> {
173        let value_name = match self.name.as_deref() {
174            Some(name) => Some(
175                StorageValueName::from_slot_name_with_suffix(slot_name, name)
176                    .map_err(|err| ComponentMetadataError::InvalidSchema(err.to_string()))?,
177            ),
178            None => None,
179        };
180
181        if let Some(value_name) = value_name.clone()
182            && let Some(raw_value) = init_storage_data.value_entry(&value_name)
183        {
184            match raw_value {
185                WordValue::Atomic(raw) => {
186                    let felt = SCHEMA_TYPE_REGISTRY
187                        .try_parse_felt(&self.r#type, raw)
188                        .map_err(ComponentMetadataError::StorageValueParsingError)?;
189                    return Ok(felt);
190                },
191                WordValue::Elements(_) => {
192                    return Err(ComponentMetadataError::InvalidInitStorageValue(
193                        value_name,
194                        "expected an atomic value, got a 4-element array".into(),
195                    ));
196                },
197                WordValue::FullyTyped(_) => {
198                    return Err(ComponentMetadataError::InvalidInitStorageValue(
199                        value_name,
200                        "expected an atomic value, got a word".into(),
201                    ));
202                },
203            }
204        }
205
206        if self.r#type == SchemaType::void() {
207            return Ok(Felt::ZERO);
208        }
209
210        if let Some(default_value) = self.default_value {
211            return Ok(default_value);
212        }
213
214        let Some(value_name) = value_name else {
215            return Err(ComponentMetadataError::InvalidSchema(
216                "non-void felt elements must be named".into(),
217            ));
218        };
219
220        Err(ComponentMetadataError::InitValueNotProvided(value_name))
221    }
222
223    /// Validates that the defined felt type exists.
224    pub(super) fn validate(&self) -> Result<(), ComponentMetadataError> {
225        if let Some(description) = self.description.as_deref() {
226            validate_description_ascii(description)?;
227        }
228
229        let type_exists = SCHEMA_TYPE_REGISTRY.contains_felt_type(&self.felt_type());
230        if !type_exists {
231            return Err(ComponentMetadataError::InvalidType(
232                self.felt_type().to_string(),
233                "Felt".into(),
234            ));
235        }
236
237        if self.r#type == SchemaType::void() {
238            if self.name.is_some() {
239                return Err(ComponentMetadataError::InvalidSchema(
240                    "void felt elements must be unnamed".into(),
241                ));
242            }
243            if self.default_value.is_some() {
244                return Err(ComponentMetadataError::InvalidSchema(
245                    "void felt elements cannot define `default-value`".into(),
246                ));
247            }
248            return Ok(());
249        }
250
251        if self.name.is_none() {
252            return Err(ComponentMetadataError::InvalidSchema(
253                "non-void felt elements must be named".into(),
254            ));
255        }
256
257        if let Some(value) = self.default_value {
258            SCHEMA_TYPE_REGISTRY
259                .validate_felt_value(&self.felt_type(), value)
260                .map_err(ComponentMetadataError::StorageValueParsingError)?;
261        }
262        Ok(())
263    }
264
265    pub(super) fn write_into_with_optional_defaults<W: ByteWriter>(
266        &self,
267        target: &mut W,
268        include_defaults: bool,
269    ) {
270        target.write(&self.name);
271        target.write(&self.description);
272        target.write(&self.r#type);
273        let default_value = if include_defaults { self.default_value } else { None };
274        target.write(default_value);
275    }
276}
277
278impl Serializable for FeltSchema {
279    fn write_into<W: ByteWriter>(&self, target: &mut W) {
280        self.write_into_with_optional_defaults(target, true);
281    }
282}
283
284impl Deserializable for FeltSchema {
285    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
286        let name = Option::<String>::read_from(source)?;
287        let description = Option::<String>::read_from(source)?;
288        let r#type = SchemaType::read_from(source)?;
289        let default_value = Option::<Felt>::read_from(source)?;
290        Ok(FeltSchema { name, description, r#type, default_value })
291    }
292}