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 crate::account::StorageSlotName;
9use crate::{Felt, FieldElement, Word};
10
11/// A word value provided via [`InitStorageData`].
12///
13/// This is used for defining specific values in relation to a component's schema, where each value
14/// is supplied as either a fully-typed word, an atomic string (e.g. `"0x1234"`, `"16"`, `"BTC"`),
15/// or an array of 4 field elements.
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub enum WordValue {
18    /// A fully-typed word value.
19    FullyTyped(Word),
20    /// Represents a single word value, given by a single string input.
21    Atomic(String),
22    /// Represents a word through four string-encoded field elements.
23    Elements([String; 4]),
24}
25
26impl From<Word> for WordValue {
27    fn from(value: Word) -> Self {
28        WordValue::FullyTyped(value)
29    }
30}
31
32impl From<String> for WordValue {
33    fn from(value: String) -> Self {
34        WordValue::Atomic(value)
35    }
36}
37
38impl From<&str> for WordValue {
39    fn from(value: &str) -> Self {
40        WordValue::Atomic(String::from(value))
41    }
42}
43
44// CONVERSIONS
45// ====================================================================================================
46
47impl From<Felt> for WordValue {
48    /// Converts a [`Felt`] to a [`WordValue`] as a Word in the form `[0, 0, 0, felt]`.
49    fn from(value: Felt) -> Self {
50        WordValue::FullyTyped(Word::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, value]))
51    }
52}
53
54impl From<[Felt; 4]> for WordValue {
55    fn from(value: [Felt; 4]) -> Self {
56        WordValue::FullyTyped(Word::from(value))
57    }
58}
59
60// INIT STORAGE DATA
61// ====================================================================================================
62
63/// Represents the data required to initialize storage entries when instantiating an
64/// [AccountComponent](crate::account::AccountComponent) from component metadata (either provided
65/// directly or extracted from a package).
66///
67/// An [`InitStorageData`] can be created from a TOML string when the `std` feature flag is set.
68#[derive(Clone, Debug, Default)]
69pub struct InitStorageData {
70    /// A mapping of storage value names to their init values.
71    value_entries: BTreeMap<StorageValueName, WordValue>,
72    /// A mapping of storage map slot names to their init key/value entries.
73    map_entries: BTreeMap<StorageSlotName, Vec<(WordValue, WordValue)>>,
74}
75
76impl InitStorageData {
77    /// Creates a new instance of [InitStorageData], validating that there are no conflicting
78    /// entries.
79    ///
80    /// # Errors
81    ///
82    /// Returns an error if:
83    /// - A slot has both value entries and map entries
84    /// - A slot has both a slot-level value and field values
85    pub fn new(
86        value_entries: BTreeMap<StorageValueName, WordValue>,
87        map_entries: BTreeMap<StorageSlotName, Vec<(WordValue, WordValue)>>,
88    ) -> Result<Self, InitStorageDataError> {
89        // Check for conflicts between value entries and map entries
90        for slot_name in map_entries.keys() {
91            if value_entries.keys().any(|v| v.slot_name() == slot_name) {
92                return Err(InitStorageDataError::ConflictingEntries(slot_name.as_str().into()));
93            }
94        }
95
96        // Check for conflicts between slot-level values and field values
97        for value_name in value_entries.keys() {
98            if value_name.field_name().is_none() {
99                // This is a slot-level value; check if there are field entries for this slot
100                let has_field_entries = value_entries.keys().any(|other| {
101                    other.slot_name() == value_name.slot_name() && other.field_name().is_some()
102                });
103                if has_field_entries {
104                    return Err(InitStorageDataError::ConflictingEntries(
105                        value_name.slot_name().as_str().into(),
106                    ));
107                }
108            }
109        }
110
111        Ok(InitStorageData { value_entries, map_entries })
112    }
113
114    /// Returns a reference to the underlying init values map.
115    pub fn values(&self) -> &BTreeMap<StorageValueName, WordValue> {
116        &self.value_entries
117    }
118
119    /// Returns a reference to the underlying init map entries.
120    pub fn maps(&self) -> &BTreeMap<StorageSlotName, Vec<(WordValue, WordValue)>> {
121        &self.map_entries
122    }
123
124    /// Returns a reference to the stored init value for the given name.
125    pub fn value_entry(&self, name: &StorageValueName) -> Option<&WordValue> {
126        self.value_entries.get(name)
127    }
128
129    /// Returns a reference to the stored init value for a full slot name.
130    pub fn slot_value_entry(&self, slot_name: &StorageSlotName) -> Option<&WordValue> {
131        let name = StorageValueName::from_slot_name(slot_name);
132        self.value_entries.get(&name)
133    }
134
135    /// Returns the map entries associated with the given storage map slot name, if any.
136    pub fn map_entries(&self, slot_name: &StorageSlotName) -> Option<&Vec<(WordValue, WordValue)>> {
137        self.map_entries.get(slot_name)
138    }
139
140    /// Returns true if any init value entry targets the given slot name.
141    pub fn has_value_entries_for_slot(&self, slot_name: &StorageSlotName) -> bool {
142        self.value_entries.keys().any(|name| name.slot_name() == slot_name)
143    }
144
145    /// Returns true if any init value entry targets a field of the given slot name.
146    pub fn has_field_entries_for_slot(&self, slot_name: &StorageSlotName) -> bool {
147        self.value_entries
148            .keys()
149            .any(|name| name.slot_name() == slot_name && name.field_name().is_some())
150    }
151
152    // MUTATORS
153    // --------------------------------------------------------------------------------------------
154
155    /// Inserts a value entry, returning an error on duplicate or conflicting keys.
156    ///
157    /// The value can be any type that implements `Into<WordValue>`, e.g.:
158    ///
159    /// - `Word`: a fully-typed word value
160    /// - `[Felt; 4]`: converted to a Word
161    /// - `Felt`: converted to `[0, 0, 0, felt]`
162    /// - `String` or `&str`: a parseable string value
163    /// - `WordValue`: a word value (fully typed, atomic, or elements)
164    pub fn insert_value(
165        &mut self,
166        name: StorageValueName,
167        value: impl Into<WordValue>,
168    ) -> Result<(), InitStorageDataError> {
169        if self.value_entries.contains_key(&name) {
170            return Err(InitStorageDataError::DuplicateKey(name.to_string()));
171        }
172        if self.map_entries.contains_key(name.slot_name()) {
173            return Err(InitStorageDataError::ConflictingEntries(name.slot_name().as_str().into()));
174        }
175        self.value_entries.insert(name, value.into());
176        Ok(())
177    }
178
179    /// Sets a value entry, overriding any existing entry for the name.
180    ///
181    /// Returns an error if the [`StorageValueName`] has been used for a map slot.
182    pub fn set_value(
183        &mut self,
184        name: StorageValueName,
185        value: impl Into<WordValue>,
186    ) -> Result<(), InitStorageDataError> {
187        if self.map_entries.contains_key(name.slot_name()) {
188            return Err(InitStorageDataError::ConflictingEntries(name.slot_name().as_str().into()));
189        }
190        self.value_entries.insert(name, value.into());
191        Ok(())
192    }
193
194    /// Inserts a single map entry, returning an error on duplicate or conflicting keys.
195    ///
196    /// See [`Self::insert_value`] for examples of supported types for `key` and `value`.
197    pub fn insert_map_entry(
198        &mut self,
199        slot_name: StorageSlotName,
200        key: impl Into<WordValue>,
201        value: impl Into<WordValue>,
202    ) -> Result<(), InitStorageDataError> {
203        if self.has_value_entries_for_slot(&slot_name) {
204            return Err(InitStorageDataError::ConflictingEntries(slot_name.as_str().into()));
205        }
206
207        let key = key.into();
208        if let Some(entries) = self.map_entries.get(&slot_name)
209            && entries.iter().any(|(existing_key, _)| existing_key == &key)
210        {
211            return Err(InitStorageDataError::DuplicateKey(format!(
212                "{}[{key:?}]",
213                slot_name.as_str()
214            )));
215        }
216
217        self.map_entries.entry(slot_name).or_default().push((key, value.into()));
218        Ok(())
219    }
220
221    /// Sets map entries for the slot, replacing any existing entries.
222    ///
223    /// Returns an error if there are conflicting value entries.
224    pub fn set_map_values(
225        &mut self,
226        slot_name: StorageSlotName,
227        entries: Vec<(WordValue, WordValue)>,
228    ) -> Result<(), InitStorageDataError> {
229        if self.has_value_entries_for_slot(&slot_name) {
230            return Err(InitStorageDataError::ConflictingEntries(slot_name.as_str().into()));
231        }
232        self.map_entries.insert(slot_name, entries);
233        Ok(())
234    }
235
236    /// Merges another [`InitStorageData`] into this one, overwriting value entries and appending
237    /// map entries.
238    pub fn merge_with(&mut self, other: InitStorageData) {
239        self.value_entries.extend(other.value_entries);
240        for (slot_name, entries) in other.map_entries {
241            self.map_entries.entry(slot_name).or_default().extend(entries);
242        }
243    }
244
245    /// Merges another [`InitStorageData`] into this one, overwriting value entries and appending
246    /// map entries.
247    pub fn merge_from(&mut self, other: InitStorageData) {
248        self.merge_with(other);
249    }
250}
251
252// ERRORS
253// ====================================================================================================
254
255/// Error returned when creating [`InitStorageData`] with invalid entries.
256#[derive(Debug, Error, PartialEq, Eq)]
257pub enum InitStorageDataError {
258    #[error("duplicate init key `{0}`")]
259    DuplicateKey(String),
260    #[error("conflicting init entries for `{0}`")]
261    ConflictingEntries(String),
262}