Skip to main content

miden_protocol/account/component/storage/
init_storage_data.rs

1use alloc::collections::BTreeMap;
2use alloc::string::{String, ToString};
3use alloc::vec::Vec;
4
5use thiserror::Error;
6
7use super::StorageValueName;
8use super::value_name::StorageValueNameError;
9use crate::account::StorageSlotName;
10use crate::errors::StorageSlotNameError;
11use crate::{Felt, FieldElement, Word};
12
13/// A word value provided via [`InitStorageData`].
14///
15/// This is used for defining specific values in relation to a component's schema, where each value
16/// is supplied as either a fully-typed word, an atomic string (e.g. `"0x1234"`, `"16"`, `"BTC"`),
17/// or an array of 4 field elements.
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub enum WordValue {
20    /// A fully-typed word value.
21    FullyTyped(Word),
22    /// Represents a single word value, given by a single string input.
23    Atomic(String),
24    /// Represents a word through four string-encoded field elements.
25    Elements([String; 4]),
26}
27
28// CONVERSIONS
29// ====================================================================================================
30
31impl From<Word> for WordValue {
32    fn from(value: Word) -> Self {
33        WordValue::FullyTyped(value)
34    }
35}
36
37impl From<String> for WordValue {
38    fn from(value: String) -> Self {
39        WordValue::Atomic(value)
40    }
41}
42
43impl From<&str> for WordValue {
44    fn from(value: &str) -> Self {
45        WordValue::Atomic(String::from(value))
46    }
47}
48
49impl From<Felt> for WordValue {
50    /// Converts a [`Felt`] to a [`WordValue`] as a Word in the form `[0, 0, 0, felt]`.
51    fn from(value: Felt) -> Self {
52        WordValue::FullyTyped(Word::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, value]))
53    }
54}
55
56impl From<[Felt; 4]> for WordValue {
57    fn from(value: [Felt; 4]) -> Self {
58        WordValue::FullyTyped(Word::from(value))
59    }
60}
61
62impl From<u8> for WordValue {
63    /// Converts a [`u8`] to a [`WordValue::Atomic`] string representation.
64    fn from(value: u8) -> Self {
65        WordValue::Atomic(value.to_string())
66    }
67}
68
69impl From<u16> for WordValue {
70    /// Converts a [`u16`] to a [`WordValue::Atomic`] string representation.
71    fn from(value: u16) -> Self {
72        WordValue::Atomic(value.to_string())
73    }
74}
75
76impl From<u32> for WordValue {
77    /// Converts a [`u32`] to a [`WordValue::Atomic`] string representation.
78    fn from(value: u32) -> Self {
79        WordValue::Atomic(value.to_string())
80    }
81}
82
83impl From<u64> for WordValue {
84    /// Converts a [`u64`] to a [`WordValue::Atomic`] string representation.
85    fn from(value: u64) -> Self {
86        WordValue::Atomic(value.to_string())
87    }
88}
89
90impl From<[u32; 4]> for WordValue {
91    /// Converts a `[u32; 4]` to a [`WordValue`] by converting each element to a [`Felt`].
92    fn from(value: [u32; 4]) -> Self {
93        WordValue::FullyTyped(Word::from([
94            Felt::from(value[0]),
95            Felt::from(value[1]),
96            Felt::from(value[2]),
97            Felt::from(value[3]),
98        ]))
99    }
100}
101
102// INIT STORAGE DATA
103// ====================================================================================================
104
105/// Represents the data required to initialize storage entries when instantiating an
106/// [AccountComponent](crate::account::AccountComponent) from component metadata (either provided
107/// directly or extracted from a package).
108///
109/// An [`InitStorageData`] can be created from a TOML string when the `std` feature flag is set.
110#[derive(Clone, Debug, Default)]
111pub struct InitStorageData {
112    /// A mapping of storage value names to their init values.
113    value_entries: BTreeMap<StorageValueName, WordValue>,
114    /// A mapping of storage map slot names to their init key/value entries.
115    map_entries: BTreeMap<StorageSlotName, Vec<(WordValue, WordValue)>>,
116}
117
118impl InitStorageData {
119    /// Creates a new instance of [InitStorageData], validating that there are no conflicting
120    /// entries.
121    ///
122    /// # Errors
123    ///
124    /// Returns an error if:
125    /// - A slot has both value entries and map entries
126    /// - A slot has both a slot-level value and field values
127    pub fn new(
128        value_entries: BTreeMap<StorageValueName, WordValue>,
129        map_entries: BTreeMap<StorageSlotName, Vec<(WordValue, WordValue)>>,
130    ) -> Result<Self, InitStorageDataError> {
131        // Check for conflicts between value entries and map entries
132        for slot_name in map_entries.keys() {
133            if value_entries.keys().any(|v| v.slot_name() == slot_name) {
134                return Err(InitStorageDataError::ConflictingEntries(slot_name.as_str().into()));
135            }
136        }
137
138        // Check for conflicts between slot-level values and field values
139        for value_name in value_entries.keys() {
140            if value_name.field_name().is_none() {
141                // This is a slot-level value; check if there are field entries for this slot
142                let has_field_entries = value_entries.keys().any(|other| {
143                    other.slot_name() == value_name.slot_name() && other.field_name().is_some()
144                });
145                if has_field_entries {
146                    return Err(InitStorageDataError::ConflictingEntries(
147                        value_name.slot_name().as_str().into(),
148                    ));
149                }
150            }
151        }
152
153        Ok(InitStorageData { value_entries, map_entries })
154    }
155
156    /// Returns a reference to the underlying init values map.
157    pub fn values(&self) -> &BTreeMap<StorageValueName, WordValue> {
158        &self.value_entries
159    }
160
161    /// Returns a reference to the underlying init map entries.
162    pub fn maps(&self) -> &BTreeMap<StorageSlotName, Vec<(WordValue, WordValue)>> {
163        &self.map_entries
164    }
165
166    /// Returns a reference to the stored init value for the given name.
167    pub fn value_entry(&self, name: &StorageValueName) -> Option<&WordValue> {
168        self.value_entries.get(name)
169    }
170
171    /// Returns a reference to the stored init value for a full slot name.
172    pub fn slot_value_entry(&self, slot_name: &StorageSlotName) -> Option<&WordValue> {
173        let name = StorageValueName::from_slot_name(slot_name);
174        self.value_entries.get(&name)
175    }
176
177    /// Returns the map entries associated with the given storage map slot name, if any.
178    pub fn map_entries(&self, slot_name: &StorageSlotName) -> Option<&Vec<(WordValue, WordValue)>> {
179        self.map_entries.get(slot_name)
180    }
181
182    /// Returns true if any init value entry targets the given slot name.
183    pub fn has_value_entries_for_slot(&self, slot_name: &StorageSlotName) -> bool {
184        self.value_entries.keys().any(|name| name.slot_name() == slot_name)
185    }
186
187    /// Returns true if any init value entry targets a field of the given slot name.
188    pub fn has_field_entries_for_slot(&self, slot_name: &StorageSlotName) -> bool {
189        self.value_entries
190            .keys()
191            .any(|name| name.slot_name() == slot_name && name.field_name().is_some())
192    }
193
194    // MUTATORS
195    // --------------------------------------------------------------------------------------------
196
197    /// Inserts a value entry, returning an error on duplicate or conflicting keys.
198    ///
199    /// The name can be any type that implements `TryInto<StorageValueName>`, e.g.:
200    ///
201    /// - `StorageValueName`: used directly
202    /// - `&str` or `String`: parsed into a `StorageValueName`
203    ///
204    /// The value can be any type that implements `Into<WordValue>`, e.g.:
205    ///
206    /// - `Word`: a fully-typed word value
207    /// - `[Felt; 4]`: converted to a Word
208    /// - `Felt`: converted to `[0, 0, 0, felt]`
209    /// - `u32`, `u64`, `u8`, `u16`: converted to a Felt, then to `[0, 0, 0, felt]`
210    /// - `String` or `&str`: a parseable string value
211    /// - `WordValue`: a word value (fully typed, atomic, or elements)
212    pub fn insert_value<N, E>(
213        &mut self,
214        name: N,
215        value: impl Into<WordValue>,
216    ) -> Result<(), InitStorageDataError>
217    where
218        N: TryInto<StorageValueName, Error = E>,
219        InitStorageDataError: From<E>,
220    {
221        let name = name.try_into().map_err(InitStorageDataError::from)?;
222        if self.value_entries.contains_key(&name) {
223            return Err(InitStorageDataError::DuplicateKey(name.to_string()));
224        }
225        if self.map_entries.contains_key(name.slot_name()) {
226            return Err(InitStorageDataError::ConflictingEntries(name.slot_name().as_str().into()));
227        }
228        self.value_entries.insert(name, value.into());
229        Ok(())
230    }
231
232    /// Sets a value entry, overriding any existing entry for the name.
233    ///
234    /// Returns an error if the [`StorageValueName`] has been used for a map slot.
235    ///
236    /// See [`Self::insert_value`] for accepted types for `name` and `value`.
237    pub fn set_value<N, E>(
238        &mut self,
239        name: N,
240        value: impl Into<WordValue>,
241    ) -> Result<(), InitStorageDataError>
242    where
243        N: TryInto<StorageValueName, Error = E>,
244        InitStorageDataError: From<E>,
245    {
246        let name = name.try_into().map_err(InitStorageDataError::from)?;
247        if self.map_entries.contains_key(name.slot_name()) {
248            return Err(InitStorageDataError::ConflictingEntries(name.slot_name().as_str().into()));
249        }
250        self.value_entries.insert(name, value.into());
251        Ok(())
252    }
253
254    /// Inserts a single map entry, returning an error on duplicate or conflicting keys.
255    ///
256    /// The slot_name can be any type that implements `TryInto<StorageSlotName>`, e.g.:
257    ///
258    /// - `StorageSlotName`: used directly
259    /// - `&str` or `String`: parsed into a `StorageSlotName`
260    ///
261    /// See [`Self::insert_value`] for examples of supported types for `key` and `value`.
262    pub fn insert_map_entry<S, E>(
263        &mut self,
264        slot_name: S,
265        key: impl Into<WordValue>,
266        value: impl Into<WordValue>,
267    ) -> Result<(), InitStorageDataError>
268    where
269        S: TryInto<StorageSlotName, Error = E>,
270        InitStorageDataError: From<E>,
271    {
272        let slot_name = slot_name.try_into().map_err(InitStorageDataError::from)?;
273        if self.has_value_entries_for_slot(&slot_name) {
274            return Err(InitStorageDataError::ConflictingEntries(slot_name.as_str().into()));
275        }
276
277        let key = key.into();
278        if let Some(entries) = self.map_entries.get(&slot_name)
279            && entries.iter().any(|(existing_key, _)| existing_key == &key)
280        {
281            return Err(InitStorageDataError::DuplicateKey(format!(
282                "{}[{key:?}]",
283                slot_name.as_str()
284            )));
285        }
286
287        self.map_entries.entry(slot_name).or_default().push((key, value.into()));
288        Ok(())
289    }
290
291    /// Sets map entries for the slot, replacing any existing entries.
292    ///
293    /// Returns an error if there are conflicting value entries.
294    ///
295    /// See [`Self::insert_map_entry`] for accepted types for `slot_name`.
296    pub fn set_map_values<S, E>(
297        &mut self,
298        slot_name: S,
299        entries: Vec<(WordValue, WordValue)>,
300    ) -> Result<(), InitStorageDataError>
301    where
302        S: TryInto<StorageSlotName, Error = E>,
303        InitStorageDataError: From<E>,
304    {
305        let slot_name = slot_name.try_into().map_err(InitStorageDataError::from)?;
306        if self.has_value_entries_for_slot(&slot_name) {
307            return Err(InitStorageDataError::ConflictingEntries(slot_name.as_str().into()));
308        }
309        self.map_entries.insert(slot_name, entries);
310        Ok(())
311    }
312
313    /// Merges another [`InitStorageData`] into this one, overwriting value entries and appending
314    /// map entries.
315    pub fn merge_with(&mut self, other: InitStorageData) {
316        self.value_entries.extend(other.value_entries);
317        for (slot_name, entries) in other.map_entries {
318            self.map_entries.entry(slot_name).or_default().extend(entries);
319        }
320    }
321
322    /// Merges another [`InitStorageData`] into this one, overwriting value entries and appending
323    /// map entries.
324    pub fn merge_from(&mut self, other: InitStorageData) {
325        self.merge_with(other);
326    }
327}
328
329// ERRORS
330// ====================================================================================================
331
332/// Error returned when creating [`InitStorageData`] with invalid entries.
333#[derive(Debug, Error)]
334pub enum InitStorageDataError {
335    #[error("duplicate init key `{0}`")]
336    DuplicateKey(String),
337    #[error("conflicting init entries for `{0}`")]
338    ConflictingEntries(String),
339    #[error("invalid storage value name")]
340    InvalidValueName(#[from] StorageValueNameError),
341    #[error("invalid storage slot name")]
342    InvalidSlotName(#[from] StorageSlotNameError),
343}
344
345impl From<core::convert::Infallible> for InitStorageDataError {
346    fn from(err: core::convert::Infallible) -> Self {
347        match err {}
348    }
349}