Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c3db35d
refactor: replace templates with component schemas
igamigo Dec 17, 2025
98d0bed
chore: overrideable -> overridable
igamigo Dec 17, 2025
63fb0b3
chore: more docs
igamigo Dec 17, 2025
fc08696
feat: avoid overloading type word and define slot type with the dotte…
igamigo Dec 17, 2025
3ab023d
chore: CHANGELOG
igamigo Dec 17, 2025
0a52051
chore: update docs
igamigo Dec 17, 2025
b1cde82
chore: update docs
igamigo Dec 17, 2025
3471268
chore: display
igamigo Dec 17, 2025
a77a61a
chore: spellcheck
igamigo Dec 18, 2025
959bc51
reviews: address most of the first review's smaller comments
igamigo Dec 18, 2025
a7a909f
reviews: infer type based o type structure
igamigo Dec 18, 2025
a66477e
reviews: re-enable tests
igamigo Dec 18, 2025
b79f1e2
reviews: docs, typeregstry renames, doc comment rewrites
igamigo Dec 18, 2025
8348f43
chore: lints
igamigo Dec 18, 2025
52f47ce
reviews: give context to errors, simplify validations, revert felt pa…
igamigo Dec 18, 2025
68c39c0
reviews: simplify further
igamigo Dec 18, 2025
0c0cd44
reviews: initvaluerequirements -> schemarequirements; now collected i…
igamigo Dec 18, 2025
2cca66d
reviews: more doc suggestions applied; validate schema
igamigo Dec 18, 2025
d349a11
reviews: singular->simple, scalar->atomic, docs reviews, nits
igamigo Dec 19, 2025
b5942e1
reviews: initstoragedata duplicate detection
igamigo Dec 19, 2025
6d16850
feat: scope storagevaluename
igamigo Dec 19, 2025
258520d
chore: docs fix
igamigo Dec 19, 2025
bea5bf1
chore: fix indentaion
mmagician Dec 19, 2025
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 @@ -27,6 +27,7 @@
- [BREAKING] Made `AccountProcedureIndexMap` construction infallible ([#2163](https://github.com/0xMiden/miden-base/pull/2163)).
- [BREAKING] Renamed `tracked_procedure_roots_slot` to `trigger_procedure_roots_slot` in ACL auth components for naming consistency ([#2166](https://github.com/0xMiden/miden-base/pull/2166)).
- [BREAKING] Refactored `AccountStorageDelta` to use a new `StorageSlotDelta` type ([#2182](https://github.com/0xMiden/miden-base/pull/2182)).
- [BREAKING] Refactored account component templates into `AccountStorageSchema` ([#2193](https://github.com/0xMiden/miden-base/pull/2193)).

## 0.12.4 (2025-11-26)

Expand Down
2 changes: 1 addition & 1 deletion crates/miden-lib/src/account/faucets/network_fungible.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ impl From<NetworkFungibleFaucet> for AccountComponent {
Felt::ZERO,
]);

// Convert AccountId to Word representation for storage
// Convert AccountId into its Word encoding for storage.
let owner_account_id_word: Word = [
Felt::new(0),
Felt::new(0),
Expand Down
213 changes: 213 additions & 0 deletions crates/miden-objects/src/account/component/metadata/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::string::{String, ToString};
use core::str::FromStr;

use miden_core::utils::{ByteReader, ByteWriter, Deserializable, Serializable};
use miden_mast_package::{Package, SectionId};
use miden_processor::DeserializationError;
use semver::Version;

use super::{AccountStorageSchema, AccountType, SchemaRequirement, StorageValueName};
use crate::AccountError;

// ACCOUNT COMPONENT METADATA
// ================================================================================================

/// Represents the full component metadata configuration.
///
/// An account component metadata describes the component alongside its storage layout.
/// The storage layout can declare typed values which must be provided at instantiation time via
/// [InitStorageData](`super::storage::InitStorageData`). These can appear either at the slot level
/// (a singular word slot) or inside composed words as typed fields.
///
/// When the `std` feature is enabled, this struct allows for serialization and deserialization to
/// and from a TOML file.
///
/// # Guarantees
///
/// - The metadata's storage schema does not contain duplicate slot names.
/// - The schema cannot contain protocol-reserved slot names.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not for this PR, but currently protocol-reserved slot names are hardcoded to a single check:

if component_slot.name() == Self::faucet_sysdata_slot() {
    return Err(AccountError::StorageSlotNameMustNotBeFaucetSysdata);
}

One idea would be to encode this in some helper enum ReservedSlotNames (or similar), which we could then reference from doc strings like this one here. On the other hand, this sounds like a bit of an overkill for the single reserved slot name that we have. But I admit, it's a little hard to find what protocol reserved slots are in the codebase.

cc @PhilippGackstatter

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally agree this would be nice, but I think we want to get rid of the faucet sysdata slot and make issuance tracking the responsibility of the faucet implementation, in which case we'd no longer have any protocol-reserved slots at all, which would be even better.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - though for now we could probably go with the RESERVED_SLOT_NAMES as already implemented in #2207

It will be equally easy to change if/once we remove the faucet's reserved slot.

/// - 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 miden_objects::account::StorageSlotName;
/// use miden_objects::account::component::{
/// AccountComponentMetadata,
/// AccountStorageSchema,
/// FeltSchema,
/// InitStorageData,
/// SchemaTypeId,
/// StorageSlotSchema,
/// StorageValueName,
/// ValueSlotSchema,
/// WordSchema,
/// };
/// use semver::Version;
///
/// let slot_name = StorageSlotName::new("demo::test_value")?;
///
/// let word = WordSchema::new_value([
/// FeltSchema::new_void(),
/// FeltSchema::new_void(),
/// FeltSchema::new_void(),
/// FeltSchema::new_typed(SchemaTypeId::native_felt(), "foo"),
/// ]);
///
/// let storage_schema = AccountStorageSchema::new([(
/// slot_name.clone(),
/// StorageSlotSchema::Value(ValueSlotSchema::new(Some("demo slot".into()), word)),
/// )])?;
///
/// let metadata = AccountComponentMetadata::new(
/// "test name".into(),
/// "description of the component".into(),
/// Version::parse("0.1.0")?,
/// BTreeSet::new(),
/// storage_schema,
/// );
///
/// // Init value keys are derived from slot name: `demo::test_value.foo`.
/// let init_storage_data = InitStorageData::new(
/// [(StorageValueName::from_slot_name(&slot_name).with_suffix("foo")?, "300".into())],
/// [],
/// );
///
/// let storage_slots = metadata.storage_schema().build_storage_slots(&init_storage_data)?;
/// assert_eq!(storage_slots.len(), 1);
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "std", serde(rename_all = "kebab-case"))]
pub struct AccountComponentMetadata {
/// The human-readable name of the component.
name: String,

/// A brief description of what this component is and how it works.
description: String,

/// The version of the component using semantic versioning.
/// This can be used to track and manage component upgrades.
version: Version,

/// A set of supported target account types for this component.
supported_types: BTreeSet<AccountType>,

/// Storage schema defining the component's storage layout, defaults, and init-supplied values.
#[cfg_attr(feature = "std", serde(rename = "storage"))]
storage_schema: AccountStorageSchema,
}

impl AccountComponentMetadata {
/// Create a new [AccountComponentMetadata].
pub fn new(
name: String,
description: String,
version: Version,
targets: BTreeSet<AccountType>,
storage_schema: AccountStorageSchema,
) -> Self {
Self {
name,
description,
version,
supported_types: targets,
storage_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].
///
/// Types for returned init values are inferred based on their location in the storage layout.
pub fn schema_requirements(&self) -> BTreeMap<StorageValueName, SchemaRequirement> {
self.storage_schema.schema_requirements().expect("storage schema is validated")
}

/// Returns the name of the account component.
pub fn name(&self) -> &str {
&self.name
}

/// Returns the description of the account component.
pub fn description(&self) -> &str {
&self.description
}

/// Returns the semantic version of the account component.
pub fn version(&self) -> &Version {
&self.version
}

/// Returns the account types supported by the component.
pub fn supported_types(&self) -> &BTreeSet<AccountType> {
&self.supported_types
}

/// Returns the storage schema of the component.
pub fn storage_schema(&self) -> &AccountStorageSchema {
&self.storage_schema
}
}

impl TryFrom<&Package> for AccountComponentMetadata {
type Error = AccountError;

fn try_from(package: &Package) -> Result<Self, Self::Error> {
package
.sections
.iter()
.find_map(|section| {
(section.id == SectionId::ACCOUNT_COMPONENT_METADATA).then(|| {
AccountComponentMetadata::read_from_bytes(&section.data).map_err(|err| {
AccountError::other_with_source(
"failed to deserialize account component metadata",
err,
)
})
})
})
.transpose()?
.ok_or_else(|| {
AccountError::other(
"package does not contain account component metadata section - packages without explicit metadata may be intended for other purposes (e.g., note scripts, transaction scripts)",
)
})
}
}

// SERIALIZATION
// ================================================================================================

impl Serializable for AccountComponentMetadata {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.name.write_into(target);
self.description.write_into(target);
self.version.to_string().write_into(target);
self.supported_types.write_into(target);
self.storage_schema.write_into(target);
}
}

impl Deserializable for AccountComponentMetadata {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
Ok(Self {
name: String::read_from(source)?,
description: String::read_from(source)?,
version: semver::Version::from_str(&String::read_from(source)?).map_err(
|err: semver::Error| DeserializationError::InvalidValue(err.to_string()),
)?,
supported_types: BTreeSet::<AccountType>::read_from(source)?,
storage_schema: AccountStorageSchema::read_from(source)?,
})
}
}
42 changes: 19 additions & 23 deletions crates/miden-objects/src/account/component/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
use alloc::collections::BTreeSet;
use alloc::vec::Vec;

// TODO(named_slots): Refactor templates.
// mod template;
// pub use template::*;
use miden_mast_package::{MastArtifact, Package};

mod metadata;
pub use metadata::*;

pub mod storage;
pub use storage::*;

mod code;
pub use code::AccountComponentCode;

Expand Down Expand Up @@ -69,7 +74,6 @@ impl AccountComponent {
})
}

/*
/// Creates an [`AccountComponent`] from a [`Package`] using [`InitStorageData`].
///
/// This method provides type safety by leveraging the component's metadata to validate
Expand Down Expand Up @@ -104,8 +108,7 @@ impl AccountComponent {
};

let component_code = AccountComponentCode::from(library);

AccountComponent::from_library(&component_code, &metadata, init_storage_data)
Self::from_library(&component_code, &metadata, init_storage_data)
}

/// Creates an [`AccountComponent`] from an [`AccountComponentCode`] and
Expand Down Expand Up @@ -134,18 +137,16 @@ impl AccountComponent {
account_component_metadata: &AccountComponentMetadata,
init_storage_data: &InitStorageData,
) -> Result<Self, AccountError> {
let mut storage_slots = vec![];
for storage_entry in account_component_metadata.storage_entries() {
let entry_storage_slots = storage_entry
.try_build_storage_slots(init_storage_data)
.map_err(AccountError::AccountComponentTemplateInstantiationError)?;
storage_slots.extend(entry_storage_slots);
}
let storage_slots = account_component_metadata
.storage_schema()
.build_storage_slots(init_storage_data)
.map_err(|err| {
AccountError::other_with_source("failed to instantiate account component", err)
})?;

Ok(AccountComponent::new(library.clone(), storage_slots)?
.with_supported_types(account_component_metadata.supported_types().clone()))
}
*/

// ACCESSORS
// --------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -241,8 +242,6 @@ impl From<AccountComponent> for AccountComponentCode {
}
}

// TODO(named_slots): Reactivate tests once template is refactored.
/*
#[cfg(test)]
mod tests {
use alloc::collections::BTreeSet;
Expand All @@ -268,9 +267,8 @@ mod tests {
"A test component".to_string(),
Version::new(1, 0, 0),
BTreeSet::from_iter([AccountType::RegularAccountImmutableCode]),
vec![],
)
.unwrap();
AccountStorageSchema::default(),
);

let metadata_bytes = metadata.to_bytes();
let package_with_metadata = Package {
Expand Down Expand Up @@ -326,9 +324,8 @@ mod tests {
AccountType::RegularAccountImmutableCode,
AccountType::RegularAccountUpdatableCode,
]),
vec![],
)
.unwrap();
AccountStorageSchema::default(),
);

// Test with empty init data - this tests the complete workflow:
// Library + Metadata -> AccountComponent
Expand Down Expand Up @@ -358,4 +355,3 @@ mod tests {
assert!(error_msg.contains("package does not contain account component metadata"));
}
}
*/
Loading