-
Notifications
You must be signed in to change notification settings - Fork 107
feat: extend InitStorageData and allow passing native structs
#2230
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
98c040f
636f727
c1c7f8f
a4250a7
935e521
012de90
8de6882
b6180d2
5b1f9ed
e1a3fd3
3438219
a4844ab
2a145c1
ac6e3c0
fde5efd
8d47859
743fcb2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,22 @@ | ||
| use alloc::collections::BTreeMap; | ||
| use alloc::string::String; | ||
| use alloc::string::{String, ToString}; | ||
| use alloc::vec::Vec; | ||
|
|
||
| use thiserror::Error; | ||
|
|
||
| use super::StorageValueName; | ||
| use crate::account::StorageSlotName; | ||
| use crate::{Felt, FieldElement, Word}; | ||
|
|
||
| /// A raw word value provided via [`InitStorageData`]. | ||
| /// A word value provided via [`InitStorageData`]. | ||
| /// | ||
| /// This is used for defining specific values in relation to a component's schema, where each values | ||
| /// is supplied as either an atomic string (e.g. `"0x1234"`, `"16"`, `"BTC"`) or an array of 4 field | ||
| /// elements. | ||
| /// This is used for defining specific values in relation to a component's schema, where each value | ||
| /// is supplied as either a fully-typed word, an atomic string (e.g. `"0x1234"`, `"16"`, `"BTC"`), | ||
| /// or an array of 4 field elements. | ||
| #[derive(Clone, Debug, PartialEq, Eq)] | ||
| #[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))] | ||
| #[cfg_attr(feature = "std", serde(untagged))] | ||
| pub enum WordValue { | ||
| /// A fully-typed word value. | ||
| FullyTyped(Word), | ||
| /// Represents a single word value, given by a single string input. | ||
| Atomic(String), | ||
| /// Represents a word through four string-encoded field elements. | ||
|
|
@@ -31,47 +35,187 @@ impl From<&str> for WordValue { | |
| } | ||
| } | ||
|
|
||
| impl From<Word> for WordValue { | ||
| fn from(value: Word) -> Self { | ||
| WordValue::FullyTyped(value) | ||
| } | ||
| } | ||
|
|
||
| impl From<Felt> for WordValue { | ||
| /// Converts a [`Felt`] to a [`WordValue`] as a Word in the form `[0, 0, 0, felt]`. | ||
| fn from(value: Felt) -> Self { | ||
| WordValue::FullyTyped(Word::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, value])) | ||
| } | ||
| } | ||
|
|
||
| impl From<[Felt; 4]> for WordValue { | ||
| fn from(value: [Felt; 4]) -> Self { | ||
| WordValue::FullyTyped(Word::from(value)) | ||
| } | ||
| } | ||
|
|
||
| // INIT STORAGE DATA | ||
| // ==================================================================================================== | ||
|
|
||
| /// Represents the data required to initialize storage entries when instantiating an | ||
| /// [AccountComponent](crate::account::AccountComponent) from component metadata (either provided | ||
| /// directly or extracted from a package). | ||
| /// | ||
| /// An [`InitStorageData`] can be created from a TOML string when the `std` feature flag is set. | ||
| #[derive(Clone, Debug, Default)] | ||
| pub struct InitStorageData { | ||
| /// A mapping of init value names to their raw values. | ||
| /// A mapping of storage value names to their init values. | ||
| value_entries: BTreeMap<StorageValueName, WordValue>, | ||
| /// A mapping of storage map slot names to their raw key/value entries. | ||
| map_entries: BTreeMap<StorageValueName, Vec<(WordValue, WordValue)>>, | ||
| /// A mapping of storage map slot names to their init key/value entries. | ||
| map_entries: BTreeMap<StorageSlotName, Vec<(WordValue, WordValue)>>, | ||
| } | ||
|
|
||
| impl InitStorageData { | ||
| /// Creates a new instance of [InitStorageData]. | ||
| /// Creates a new instance of [InitStorageData], validating that there are no conflicting | ||
| /// entries. | ||
| /// | ||
| /// A [`BTreeMap`] is constructed from the passed iterator, so duplicate keys will cause | ||
| /// overridden values. | ||
| /// # Errors | ||
| /// | ||
| /// Returns an error if: | ||
| /// - A slot has both value entries and map entries | ||
| /// - A slot has both a slot-level value and field values | ||
| pub fn new( | ||
| entries: impl IntoIterator<Item = (StorageValueName, WordValue)>, | ||
| map_entries: impl IntoIterator<Item = (StorageValueName, Vec<(WordValue, WordValue)>)>, | ||
| ) -> Self { | ||
| InitStorageData { | ||
| value_entries: entries.into_iter().collect(), | ||
| map_entries: map_entries.into_iter().collect(), | ||
| value_entries: BTreeMap<StorageValueName, WordValue>, | ||
| map_entries: BTreeMap<StorageSlotName, Vec<(WordValue, WordValue)>>, | ||
| ) -> Result<Self, InitStorageDataError> { | ||
| // Check for conflicts between value entries and map entries | ||
| for slot_name in map_entries.keys() { | ||
| if value_entries.keys().any(|v| v.slot_name() == slot_name) { | ||
| return Err(InitStorageDataError::ConflictingEntries(slot_name.as_str().into())); | ||
| } | ||
| } | ||
|
|
||
| // Check for conflicts between slot-level values and field values | ||
| for value_name in value_entries.keys() { | ||
| if value_name.field_name().is_none() { | ||
| // This is a slot-level value; check if there are field entries for this slot | ||
| let has_field_entries = value_entries.keys().any(|other| { | ||
| other.slot_name() == value_name.slot_name() && other.field_name().is_some() | ||
| }); | ||
| if has_field_entries { | ||
| return Err(InitStorageDataError::ConflictingEntries( | ||
| value_name.slot_name().as_str().into(), | ||
| )); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Ok(InitStorageData { value_entries, map_entries }) | ||
| } | ||
|
|
||
| /// Returns a reference to the underlying init values map. | ||
| pub fn values(&self) -> &BTreeMap<StorageValueName, WordValue> { | ||
| &self.value_entries | ||
| } | ||
|
|
||
| /// Returns a reference to the stored init value, or [`Option::None`] if the key is not | ||
| /// present. | ||
| pub fn get(&self, key: &StorageValueName) -> Option<&WordValue> { | ||
| self.value_entries.get(key) | ||
| /// Returns a reference to the underlying init map entries. | ||
| pub fn maps(&self) -> &BTreeMap<StorageSlotName, Vec<(WordValue, WordValue)>> { | ||
| &self.map_entries | ||
| } | ||
|
|
||
| /// Returns a reference to the stored init value for the given name. | ||
| pub fn value_entry(&self, name: &StorageValueName) -> Option<&WordValue> { | ||
| self.value_entries.get(name) | ||
| } | ||
|
|
||
| /// Returns a reference to the stored init value for a full slot name. | ||
| pub fn slot_value_entry(&self, slot_name: &StorageSlotName) -> Option<&WordValue> { | ||
| let name = StorageValueName::from_slot_name(slot_name); | ||
| self.value_entries.get(&name) | ||
| } | ||
|
|
||
| /// Returns the map entries associated with the given storage map slot name, if any. | ||
| pub fn map_entries(&self, key: &StorageValueName) -> Option<&Vec<(WordValue, WordValue)>> { | ||
| self.map_entries.get(key) | ||
| pub fn map_entries(&self, slot_name: &StorageSlotName) -> Option<&Vec<(WordValue, WordValue)>> { | ||
| self.map_entries.get(slot_name) | ||
| } | ||
|
|
||
| /// Merges another [`InitStorageData`] into this one, overwriting value entries and appending | ||
| /// map entries. | ||
| pub fn merge_from(&mut self, other: InitStorageData) { | ||
| self.value_entries.extend(other.value_entries); | ||
| for (slot_name, entries) in other.map_entries { | ||
| self.map_entries.entry(slot_name).or_default().extend(entries); | ||
| } | ||
| } | ||
igamigo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /// Returns true if any init value entry targets the given slot name. | ||
| pub fn has_value_entries_for_slot(&self, slot_name: &StorageSlotName) -> bool { | ||
| self.value_entries.keys().any(|name| name.slot_name() == slot_name) | ||
| } | ||
|
|
||
| /// Returns true if any init value entry targets a field of the given slot name. | ||
| pub fn has_field_entries_for_slot(&self, slot_name: &StorageSlotName) -> bool { | ||
| self.value_entries | ||
| .keys() | ||
| .any(|name| name.slot_name() == slot_name && name.field_name().is_some()) | ||
| } | ||
|
|
||
| // MUTATORS | ||
| // -------------------------------------------------------------------------------------------- | ||
|
|
||
| /// Inserts a value entry, returning an error on duplicate or conflicting keys. | ||
| /// | ||
| /// The value can be any type that implements `Into<WordValue>`, e.g.: | ||
| /// | ||
| /// - `Word`: a fully-typed word value | ||
| /// - `[Felt; 4]`: converted to a Word | ||
| /// - `Felt`: converted to `[0, 0, 0, felt]` | ||
| /// - `String` or `&str`: a parseable string value | ||
| /// - `WordValue`: a raw or fully-typed word value | ||
| pub fn insert_value( | ||
| &mut self, | ||
| name: StorageValueName, | ||
| value: impl Into<WordValue>, | ||
| ) -> Result<(), InitStorageDataError> { | ||
|
Comment on lines
+124
to
+128
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe not for this PR, but would it make sense to also define the type for the data.insert_value("foo::bar.baz", Felt::from(42_u32));Also, maybe we should implement more conversions for |
||
| if self.value_entries.contains_key(&name) { | ||
| return Err(InitStorageDataError::DuplicateKey(name.to_string())); | ||
| } | ||
| if self.map_entries.contains_key(name.slot_name()) { | ||
| return Err(InitStorageDataError::ConflictingEntries(name.slot_name().as_str().into())); | ||
| } | ||
| self.value_entries.insert(name, value.into()); | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Inserts map entries, returning an error if there are conflicting value entries. | ||
| pub fn set_map_values( | ||
| &mut self, | ||
| slot_name: StorageSlotName, | ||
| entries: Vec<(WordValue, WordValue)>, | ||
| ) -> Result<(), InitStorageDataError> { | ||
|
Comment on lines
+184
to
+188
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment re |
||
| if self.has_value_entries_for_slot(&slot_name) { | ||
| return Err(InitStorageDataError::ConflictingEntries(slot_name.as_str().into())); | ||
| } | ||
| self.map_entries.entry(slot_name).or_default().extend(entries); | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Inserts a single map entry. | ||
| /// | ||
| /// See [`Self::insert_value`] for examples of supported types for `key` and `value`. | ||
| pub fn insert_map_entry( | ||
| &mut self, | ||
| slot_name: StorageSlotName, | ||
| key: impl Into<WordValue>, | ||
| value: impl Into<WordValue>, | ||
| ) { | ||
| self.map_entries.entry(slot_name).or_default().push((key.into(), value.into())); | ||
|
||
| } | ||
| } | ||
|
|
||
| // ERRORS | ||
| // ==================================================================================================== | ||
|
|
||
| /// Error returned when creating [`InitStorageData`] with invalid entries. | ||
| #[derive(Debug, Error, PartialEq, Eq)] | ||
| pub enum InitStorageDataError { | ||
| #[error("duplicate init key `{0}`")] | ||
| DuplicateKey(String), | ||
| #[error("conflicting init entries for `{0}`")] | ||
| ConflictingEntries(String), | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.