Skip to main content

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