Skip to content

Commit eeda9e6

Browse files
authored
refactor: move storage schema component into a separate file (0xMiden#2603)
1 parent 88e56f8 commit eeda9e6

5 files changed

Lines changed: 292 additions & 272 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
- [BREAKING] Changed `asset::create_fungible_asset` and `faucet::create_fungible_asset` signature to take `enable_callbacks` flag ([#2571](https://github.com/0xMiden/protocol/pull/2571)).
9696

9797
- [BREAKING] Fixed `TokenSymbol::try_from(Felt)` to reject values below `MIN_ENCODED_VALUE`; implemented `Display` for `TokenSymbol` replacing the fallible `to_string()` method; removed `Default` derive ([#2464](https://github.com/0xMiden/protocol/issues/2464)).
98+
- Moved `AccountSchemaCommitment` component into a sub-module ([#2603](https://github.com/0xMiden/protocol/pull/2603)).
9899

99100
## 0.13.3 (2026-01-27)
100101

crates/miden-standards/asm/standards/metadata/storage_schema.masm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use miden::protocol::active_account
88
# =================================================================================================
99

1010
# The slot in this component's storage layout where the account storage schema commitment is stored
11-
const SCHEMA_COMMITMENT_SLOT = word("miden::standards::metadata::storage_schema")
11+
const SCHEMA_COMMITMENT_SLOT = word("miden::standards::metadata::storage_schema::commitment")
1212

1313
pub proc get_schema_commitment
1414
dropw

crates/miden-standards/src/account/components/mod.rs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,6 @@ static MINT_POLICY_AUTH_CONTROLLED_LIBRARY: LazyLock<Library> = LazyLock::new(||
120120
// METADATA LIBRARIES
121121
// ================================================================================================
122122

123-
// Initialize the Storage Schema library only once.
124-
static STORAGE_SCHEMA_LIBRARY: LazyLock<Library> = LazyLock::new(|| {
125-
let bytes = include_bytes!(concat!(
126-
env!("OUT_DIR"),
127-
"/assets/account_components/metadata/schema_commitment.masl"
128-
));
129-
Library::read_from_bytes(bytes).expect("Shipped Storage Schema library is well-formed")
130-
});
131-
132123
/// Returns the Basic Wallet Library.
133124
pub fn basic_wallet_library() -> Library {
134125
BASIC_WALLET_LIBRARY.clone()
@@ -159,11 +150,6 @@ pub fn auth_controlled_library() -> Library {
159150
MINT_POLICY_AUTH_CONTROLLED_LIBRARY.clone()
160151
}
161152

162-
/// Returns the Storage Schema Library.
163-
pub fn storage_schema_library() -> Library {
164-
STORAGE_SCHEMA_LIBRARY.clone()
165-
}
166-
167153
/// Returns the Singlesig Library.
168154
pub fn singlesig_library() -> Library {
169155
SINGLESIG_LIBRARY.clone()
Lines changed: 2 additions & 257 deletions
Original file line numberDiff line numberDiff line change
@@ -1,258 +1,3 @@
1-
use alloc::collections::BTreeMap;
1+
mod schema_commitment;
22

3-
use miden_protocol::Word;
4-
use miden_protocol::account::component::{AccountComponentMetadata, StorageSchema};
5-
use miden_protocol::account::{
6-
Account,
7-
AccountBuilder,
8-
AccountComponent,
9-
AccountType,
10-
StorageSlot,
11-
StorageSlotName,
12-
};
13-
use miden_protocol::errors::{AccountError, ComponentMetadataError};
14-
use miden_protocol::utils::sync::LazyLock;
15-
16-
use crate::account::components::storage_schema_library;
17-
18-
pub static SCHEMA_COMMITMENT_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
19-
StorageSlotName::new("miden::standards::metadata::storage_schema")
20-
.expect("storage slot name should be valid")
21-
});
22-
23-
/// An [`AccountComponent`] exposing the account storage schema commitment.
24-
///
25-
/// The [`AccountSchemaCommitment`] component can be constructed from a list of [`StorageSchema`],
26-
/// from which a commitment is computed and then inserted into the [`SCHEMA_COMMITMENT_SLOT_NAME`]
27-
/// slot.
28-
///
29-
/// It reexports the `get_schema_commitment` procedure from
30-
/// `miden::standards::metadata::storage_schema`.
31-
///
32-
/// ## Storage Layout
33-
///
34-
/// - [`Self::schema_commitment_slot`]: Storage schema commitment.
35-
pub struct AccountSchemaCommitment {
36-
schema_commitment: Word,
37-
}
38-
39-
impl AccountSchemaCommitment {
40-
/// Creates a new [`AccountSchemaCommitment`] component from storage schemas.
41-
///
42-
/// The input schemas are merged into a single schema before the final commitment is computed.
43-
///
44-
/// # Errors
45-
///
46-
/// Returns an error if the schemas contain conflicting definitions for the same slot name.
47-
pub fn new<'a>(
48-
schemas: impl IntoIterator<Item = &'a StorageSchema>,
49-
) -> Result<Self, ComponentMetadataError> {
50-
Ok(Self {
51-
schema_commitment: compute_schema_commitment(schemas)?,
52-
})
53-
}
54-
55-
/// Creates a new [`AccountSchemaCommitment`] component from a [`StorageSchema`].
56-
pub fn from_schema(storage_schema: &StorageSchema) -> Result<Self, ComponentMetadataError> {
57-
Self::new(core::slice::from_ref(storage_schema))
58-
}
59-
60-
/// Returns the [`StorageSlotName`] where the schema commitment is stored.
61-
pub fn schema_commitment_slot() -> &'static StorageSlotName {
62-
&SCHEMA_COMMITMENT_SLOT_NAME
63-
}
64-
65-
/// Returns the [`AccountComponentMetadata`] for this component.
66-
pub fn component_metadata() -> AccountComponentMetadata {
67-
AccountComponentMetadata::new("miden::metadata::schema_commitment", AccountType::all())
68-
.with_description("Component exposing the account storage schema commitment")
69-
}
70-
}
71-
72-
impl From<AccountSchemaCommitment> for AccountComponent {
73-
fn from(schema_commitment: AccountSchemaCommitment) -> Self {
74-
let metadata = AccountSchemaCommitment::component_metadata();
75-
76-
AccountComponent::new(
77-
storage_schema_library(),
78-
vec![StorageSlot::with_value(
79-
AccountSchemaCommitment::schema_commitment_slot().clone(),
80-
schema_commitment.schema_commitment,
81-
)],
82-
metadata,
83-
)
84-
.expect(
85-
"AccountSchemaCommitment component should satisfy the requirements of a valid account component",
86-
)
87-
}
88-
}
89-
90-
// ACCOUNT BUILDER EXTENSION
91-
// ================================================================================================
92-
93-
/// An extension trait for [`AccountBuilder`] that provides a convenience method for building an
94-
/// account with an [`AccountSchemaCommitment`] component.
95-
pub trait AccountBuilderSchemaCommitmentExt {
96-
/// Builds an [`Account`] out of the configured builder after computing the storage schema
97-
/// commitment from all components currently in the builder and adding an
98-
/// [`AccountSchemaCommitment`] component.
99-
///
100-
/// # Errors
101-
///
102-
/// Returns an error if:
103-
/// - The components' storage schemas contain conflicting definitions for the same slot name.
104-
/// - [`AccountBuilder::build`] fails.
105-
fn build_with_schema_commitment(self) -> Result<Account, AccountError>;
106-
}
107-
108-
impl AccountBuilderSchemaCommitmentExt for AccountBuilder {
109-
fn build_with_schema_commitment(self) -> Result<Account, AccountError> {
110-
let schema_commitment =
111-
AccountSchemaCommitment::new(self.storage_schemas()).map_err(|err| {
112-
AccountError::other_with_source("failed to compute account schema commitment", err)
113-
})?;
114-
115-
self.with_component(schema_commitment).build()
116-
}
117-
}
118-
119-
// HELPERS
120-
// ================================================================================================
121-
122-
/// Computes the schema commitment.
123-
///
124-
/// The account schema commitment is computed from the merged schema commitment.
125-
/// If the passed list of schemas is empty, [`Word::empty()`] is returned.
126-
fn compute_schema_commitment<'a>(
127-
schemas: impl IntoIterator<Item = &'a StorageSchema>,
128-
) -> Result<Word, ComponentMetadataError> {
129-
let mut schemas = schemas.into_iter().peekable();
130-
if schemas.peek().is_none() {
131-
return Ok(Word::empty());
132-
}
133-
134-
let mut merged_slots = BTreeMap::new();
135-
136-
for schema in schemas {
137-
for (slot_name, slot_schema) in schema.iter() {
138-
match merged_slots.get(slot_name) {
139-
None => {
140-
merged_slots.insert(slot_name.clone(), slot_schema.clone());
141-
},
142-
// Slot exists, check if the schema is the same before erroring
143-
Some(existing) => {
144-
if existing != slot_schema {
145-
return Err(ComponentMetadataError::InvalidSchema(format!(
146-
"conflicting definitions for storage slot `{slot_name}`",
147-
)));
148-
}
149-
},
150-
}
151-
}
152-
}
153-
154-
let merged_schema = StorageSchema::new(merged_slots)?;
155-
156-
Ok(merged_schema.commitment())
157-
}
158-
159-
// TESTS
160-
// ================================================================================================
161-
162-
#[cfg(test)]
163-
mod tests {
164-
use miden_protocol::Word;
165-
use miden_protocol::account::AccountBuilder;
166-
use miden_protocol::account::auth::{AuthScheme, PublicKeyCommitment};
167-
use miden_protocol::account::component::AccountComponentMetadata;
168-
169-
use super::{AccountBuilderSchemaCommitmentExt, AccountSchemaCommitment};
170-
use crate::account::auth::{AuthSingleSig, NoAuth};
171-
172-
#[test]
173-
fn storage_schema_commitment_is_order_independent() {
174-
let toml_a = r#"
175-
name = "Component A"
176-
description = "Component A schema"
177-
version = "0.1.0"
178-
supported-types = []
179-
180-
[[storage.slots]]
181-
name = "test::slot_a"
182-
type = "word"
183-
"#;
184-
185-
let toml_b = r#"
186-
name = "Component B"
187-
description = "Component B schema"
188-
version = "0.1.0"
189-
supported-types = []
190-
191-
[[storage.slots]]
192-
name = "test::slot_b"
193-
description = "description is committed to"
194-
type = "word"
195-
"#;
196-
197-
let metadata_a = AccountComponentMetadata::from_toml(toml_a).unwrap();
198-
let metadata_b = AccountComponentMetadata::from_toml(toml_b).unwrap();
199-
200-
let schema_a = metadata_a.storage_schema().clone();
201-
let schema_b = metadata_b.storage_schema().clone();
202-
203-
// Create one component for each of two different accounts, but switch orderings
204-
let component_a =
205-
AccountSchemaCommitment::new(&[schema_a.clone(), schema_b.clone()]).unwrap();
206-
let component_b = AccountSchemaCommitment::new(&[schema_b, schema_a]).unwrap();
207-
208-
let account_a = AccountBuilder::new([1u8; 32])
209-
.with_auth_component(NoAuth)
210-
.with_component(component_a)
211-
.build()
212-
.unwrap();
213-
214-
let account_b = AccountBuilder::new([2u8; 32])
215-
.with_auth_component(NoAuth)
216-
.with_component(component_b)
217-
.build()
218-
.unwrap();
219-
220-
let slot_name = AccountSchemaCommitment::schema_commitment_slot();
221-
let commitment_a = account_a.storage().get_item(slot_name).unwrap();
222-
let commitment_b = account_b.storage().get_item(slot_name).unwrap();
223-
224-
assert_eq!(commitment_a, commitment_b);
225-
}
226-
227-
#[test]
228-
fn storage_schema_commitment_is_empty_for_no_schemas() {
229-
let component = AccountSchemaCommitment::new(&[]).unwrap();
230-
231-
assert_eq!(component.schema_commitment, Word::empty());
232-
}
233-
234-
#[test]
235-
fn build_with_schema_commitment_adds_schema_commitment_component() {
236-
let auth_component = AuthSingleSig::new(
237-
PublicKeyCommitment::from(Word::empty()),
238-
AuthScheme::EcdsaK256Keccak,
239-
);
240-
241-
let account = AccountBuilder::new([1u8; 32])
242-
.with_auth_component(auth_component)
243-
.build_with_schema_commitment()
244-
.unwrap();
245-
246-
// The auth component has 2 slots (public key and scheme ID) and the schema commitment adds
247-
// 1 more.
248-
assert_eq!(account.storage().num_slots(), 3);
249-
250-
// The auth component's public key slot should be accessible.
251-
assert!(account.storage().get_item(AuthSingleSig::public_key_slot()).is_ok());
252-
253-
// The schema commitment slot should be non-empty since we have a component with a schema.
254-
let slot_name = AccountSchemaCommitment::schema_commitment_slot();
255-
let commitment = account.storage().get_item(slot_name).unwrap();
256-
assert_ne!(commitment, Word::empty());
257-
}
258-
}
3+
pub use schema_commitment::{AccountBuilderSchemaCommitmentExt, AccountSchemaCommitment};

0 commit comments

Comments
 (0)