Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
- [BREAKING] Refactored `AccountStorageDelta` to use a new `StorageSlotDelta` type ([#2182](https://github.com/0xMiden/miden-base/pull/2182)).
- [BREAKING] Removed OLD_MAP_ROOT from being returned when calling [`native_account::set_map_item`](crates/miden-lib/asm/miden/native_account.masm) ([#2194](https://github.com/0xMiden/miden-base/pull/2194)).
- [BREAKING] Refactored account component templates into `AccountStorageSchema` ([#2193](https://github.com/0xMiden/miden-base/pull/2193)).
- [BREAKING] Refactored `InitStorageData` to support native types ([#2230](https://github.com/0xMiden/miden-base/pull/2230)).

## 0.12.4 (2025-11-26)

Expand Down
20 changes: 11 additions & 9 deletions crates/miden-protocol/src/account/component/metadata/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ use crate::AccountError;
///
/// - The metadata's storage schema does not contain duplicate slot names.
/// - The schema cannot contain protocol-reserved slot names.
/// - Each init-time value name uniquely identifies a single value. The expected init-time
/// requirements can be retrieved with [AccountComponentMetadata::schema_requirements()], which
/// returns a map from keys to [SchemaRequirement] (which indicates the expected value type and
/// optional defaults).
/// - Each init-time value name uniquely identifies a single value. The expected init-time metadata
/// can be retrieved with [AccountComponentMetadata::schema_requirements()], which returns a map
/// from keys to [SchemaRequirement] (which indicates the expected value type and optional
/// defaults).
///
/// # Example
///
/// ```
/// use std::collections::BTreeSet;
/// use std::collections::{BTreeMap, BTreeSet};
///
/// use miden_protocol::account::StorageSlotName;
/// use miden_protocol::account::component::{
Expand All @@ -45,6 +45,7 @@ use crate::AccountError;
/// InitStorageData,
/// SchemaTypeId,
/// StorageSlotSchema,
/// StorageValue,
/// StorageValueName,
/// ValueSlotSchema,
/// WordSchema,
Expand Down Expand Up @@ -74,10 +75,11 @@ use crate::AccountError;
/// );
///
/// // Init value keys are derived from slot name: `demo::test_value.foo`.
/// let value_name = StorageValueName::from_slot_name_with_suffix(&slot_name, "foo")?;
/// let init_storage_data = InitStorageData::new(
/// [(StorageValueName::from_slot_name(&slot_name).with_suffix("foo")?, "300".into())],
/// [],
/// );
/// BTreeMap::from([(value_name, StorageValue::Parseable("300".into()))]),
/// BTreeMap::new(),
/// )?;
///
/// let storage_slots = metadata.storage_schema().build_storage_slots(&init_storage_data)?;
/// assert_eq!(storage_slots.len(), 1);
Expand Down Expand Up @@ -123,7 +125,7 @@ impl AccountComponentMetadata {
}
}

/// Returns the init-time value requirements for this schema.
/// Returns the init-time values's requirements for this schema.
///
/// These values are used for initializing storage slot values or storage map entries. For a
/// full example, refer to the docs for [AccountComponentMetadata].
Expand Down
224 changes: 202 additions & 22 deletions crates/miden-protocol/src/account/component/storage/init_storage_data.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
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`].
///
/// This is used for defining specific values in relation to a component's schema, where each values
/// This is used for defining specific values in relation to a component's schema, where each value
/// is supplied as either an atomic string (e.g. `"0x1234"`, `"16"`, `"BTC"`) or an array of 4 field
/// elements.
#[derive(Clone, Debug, PartialEq, Eq)]
Expand All @@ -31,47 +35,223 @@ impl From<&str> for WordValue {
}
}

// STORAGE VALUE
// ====================================================================================================

/// Represents a storage value supplied at initialization time.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StorageValue {
/// A fully-typed word value.
Word(Word),
/// A raw value which will be parsed into a word using the slot schema.
Parseable(WordValue),
}

// CONVERSIONS
// ====================================================================================================

impl From<Word> for StorageValue {
fn from(value: Word) -> Self {
StorageValue::Word(value)
}
}

impl From<WordValue> for StorageValue {
fn from(value: WordValue) -> Self {
StorageValue::Parseable(value)
}
}

impl From<String> for StorageValue {
fn from(value: String) -> Self {
StorageValue::Parseable(WordValue::Atomic(value))
}
}

impl From<&str> for StorageValue {
fn from(value: &str) -> Self {
StorageValue::Parseable(WordValue::Atomic(String::from(value)))
}
}

impl From<Felt> for StorageValue {
/// Converts a [`Felt`] to a [`StorageValue`] as a Word in the form `[0, 0, 0, felt]`.
fn from(value: Felt) -> Self {
StorageValue::Word(Word::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, value]))
}
}

impl From<[Felt; 4]> for StorageValue {
fn from(value: [Felt; 4]) -> Self {
StorageValue::Word(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.
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 value names to their init values.
value_entries: BTreeMap<StorageValueName, StorageValue>,
/// A mapping of storage map slot names to their init key/value entries.
map_entries: BTreeMap<StorageSlotName, Vec<(StorageValue, StorageValue)>>,
}

impl InitStorageData {
/// Creates a new instance of [InitStorageData].
/// Creates a new instance of [InitStorageData], validating that there are no conflicting
/// entries.
///
/// # Errors
///
/// A [`BTreeMap`] is constructed from the passed iterator, so duplicate keys will cause
/// overridden values.
/// 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, StorageValue>,
map_entries: BTreeMap<StorageSlotName, Vec<(StorageValue, StorageValue)>>,
) -> 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> {
pub fn values(&self) -> &BTreeMap<StorageValueName, StorageValue> {
&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<(StorageValue, StorageValue)>> {
&self.map_entries
}

/// Returns a reference to the stored init value for the given name.
pub fn value_entry(&self, name: &StorageValueName) -> Option<&StorageValue> {
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<&StorageValue> {
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<(StorageValue, StorageValue)>> {
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);
}
}

/// 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<StorageValue>`, 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 word value (atomic or elements)
pub fn insert_value(
&mut self,
name: StorageValueName,
value: impl Into<StorageValue>,
) -> Result<(), InitStorageDataError> {
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<(StorageValue, StorageValue)>,
) -> Result<(), InitStorageDataError> {
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<StorageValue>,
value: impl Into<StorageValue>,
) {
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),
}
2 changes: 1 addition & 1 deletion crates/miden-protocol/src/account/component/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ mod type_registry;
pub use type_registry::{SchemaRequirement, SchemaTypeError, SchemaTypeId};

mod init_storage_data;
pub use init_storage_data::{InitStorageData, WordValue};
pub use init_storage_data::{InitStorageData, InitStorageDataError, StorageValue, WordValue};

#[cfg(feature = "std")]
pub mod toml;
Loading