miden_standards/account/metadata/
mod.rs1use alloc::collections::BTreeMap;
2
3use miden_protocol::Word;
4use miden_protocol::account::component::StorageSchema;
5use miden_protocol::account::{AccountComponent, StorageSlot, StorageSlotName};
6use miden_protocol::errors::AccountComponentTemplateError;
7use miden_protocol::utils::sync::LazyLock;
8
9use crate::account::components::storage_schema_library;
10
11pub static SCHEMA_COMMITMENT_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
12 StorageSlotName::new("miden::standards::metadata::storage_schema")
13 .expect("storage slot name should be valid")
14});
15
16pub struct AccountSchemaCommitment {
29 schema_commitment: Word,
30}
31
32impl AccountSchemaCommitment {
33 pub fn new(schemas: &[StorageSchema]) -> Result<Self, AccountComponentTemplateError> {
41 Ok(Self {
42 schema_commitment: compute_schema_commitment(schemas)?,
43 })
44 }
45
46 pub fn from_schema(
48 storage_schema: &StorageSchema,
49 ) -> Result<Self, AccountComponentTemplateError> {
50 Self::new(core::slice::from_ref(storage_schema))
51 }
52
53 pub fn schema_commitment_slot() -> &'static StorageSlotName {
55 &SCHEMA_COMMITMENT_SLOT_NAME
56 }
57}
58
59impl From<AccountSchemaCommitment> for AccountComponent {
60 fn from(schema_commitment: AccountSchemaCommitment) -> Self {
61 AccountComponent::new(
62 storage_schema_library(),
63 vec![StorageSlot::with_value(
64 AccountSchemaCommitment::schema_commitment_slot().clone(),
65 schema_commitment.schema_commitment,
66 )],
67 )
68 .expect(
69 "AccountSchemaCommitment component should satisfy the requirements of a valid account component",
70 )
71 .with_supports_all_types()
72 }
73}
74
75fn compute_schema_commitment(
80 schemas: &[StorageSchema],
81) -> Result<Word, AccountComponentTemplateError> {
82 if schemas.is_empty() {
83 return Ok(Word::empty());
84 }
85
86 let mut merged_slots = BTreeMap::new();
87 for schema in schemas {
88 for (slot_name, slot_schema) in schema.iter() {
89 match merged_slots.get(slot_name) {
90 None => {
91 merged_slots.insert(slot_name.clone(), slot_schema.clone());
92 },
93 Some(existing) => {
95 if existing != slot_schema {
96 return Err(AccountComponentTemplateError::InvalidSchema(format!(
97 "conflicting definitions for storage slot `{slot_name}`",
98 )));
99 }
100 },
101 }
102 }
103 }
104
105 let merged_schema = StorageSchema::new(merged_slots)?;
106
107 Ok(merged_schema.commitment())
108}
109
110#[cfg(test)]
114mod tests {
115 use miden_protocol::Word;
116 use miden_protocol::account::AccountBuilder;
117 use miden_protocol::account::component::AccountComponentMetadata;
118
119 use super::AccountSchemaCommitment;
120 use crate::account::auth::NoAuth;
121
122 #[test]
123 fn storage_schema_commitment_is_order_independent() {
124 let toml_a = r#"
125 name = "Component A"
126 description = "Component A schema"
127 version = "0.1.0"
128 supported-types = []
129
130 [[storage.slots]]
131 name = "test::slot_a"
132 type = "word"
133 "#;
134
135 let toml_b = r#"
136 name = "Component B"
137 description = "Component B schema"
138 version = "0.1.0"
139 supported-types = []
140
141 [[storage.slots]]
142 name = "test::slot_b"
143 description = "description is committed to"
144 type = "word"
145 "#;
146
147 let metadata_a = AccountComponentMetadata::from_toml(toml_a).unwrap();
148 let metadata_b = AccountComponentMetadata::from_toml(toml_b).unwrap();
149
150 let schema_a = metadata_a.storage_schema().clone();
151 let schema_b = metadata_b.storage_schema().clone();
152
153 let component_a =
155 AccountSchemaCommitment::new(&[schema_a.clone(), schema_b.clone()]).unwrap();
156 let component_b = AccountSchemaCommitment::new(&[schema_b, schema_a]).unwrap();
157
158 let account_a = AccountBuilder::new([1u8; 32])
159 .with_auth_component(NoAuth)
160 .with_component(component_a)
161 .build()
162 .unwrap();
163
164 let account_b = AccountBuilder::new([2u8; 32])
165 .with_auth_component(NoAuth)
166 .with_component(component_b)
167 .build()
168 .unwrap();
169
170 let slot_name = AccountSchemaCommitment::schema_commitment_slot();
171 let commitment_a = account_a.storage().get_item(slot_name).unwrap();
172 let commitment_b = account_b.storage().get_item(slot_name).unwrap();
173
174 assert_eq!(commitment_a, commitment_b);
175 }
176
177 #[test]
178 fn storage_schema_commitment_is_empty_for_no_schemas() {
179 let component = AccountSchemaCommitment::new(&[]).unwrap();
180
181 assert_eq!(component.schema_commitment, Word::empty());
182 }
183}