miden_protocol/account/component/metadata/mod.rs
1use alloc::collections::{BTreeMap, BTreeSet};
2use alloc::string::{String, ToString};
3use core::str::FromStr;
4
5use miden_mast_package::{Package, SectionId};
6use semver::Version;
7
8use super::{AccountType, SchemaRequirement, StorageSchema, StorageValueName};
9use crate::errors::AccountError;
10use crate::utils::serde::{
11 ByteReader,
12 ByteWriter,
13 Deserializable,
14 DeserializationError,
15 Serializable,
16};
17
18// ACCOUNT COMPONENT METADATA
19// ================================================================================================
20
21/// Represents the full component metadata configuration.
22///
23/// An account component metadata describes the component alongside its storage layout.
24/// The storage layout can declare typed values which must be provided at instantiation time via
25/// [InitStorageData](`super::storage::InitStorageData`). These can appear either at the slot level
26/// (a singular word slot) or inside composed words as typed fields.
27///
28/// When the `std` feature is enabled, this struct allows for serialization and deserialization to
29/// and from a TOML file.
30///
31/// # Guarantees
32///
33/// - The metadata's storage schema does not contain duplicate slot names.
34/// - Each init-time value name uniquely identifies a single value. The expected init-time metadata
35/// can be retrieved with [AccountComponentMetadata::schema_requirements()], which returns a map
36/// from keys to [SchemaRequirement] (which indicates the expected value type and optional
37/// defaults).
38///
39/// # Example
40///
41/// ```
42/// use std::collections::BTreeMap;
43///
44/// use miden_protocol::account::component::{
45/// AccountComponentMetadata,
46/// FeltSchema,
47/// InitStorageData,
48/// SchemaType,
49/// StorageSchema,
50/// StorageSlotSchema,
51/// StorageValueName,
52/// ValueSlotSchema,
53/// WordSchema,
54/// WordValue,
55/// };
56/// use miden_protocol::account::{AccountType, StorageSlotName};
57///
58/// let slot_name = StorageSlotName::new("demo::test_value")?;
59///
60/// let word = WordSchema::new_value([
61/// FeltSchema::new_void(),
62/// FeltSchema::new_void(),
63/// FeltSchema::new_void(),
64/// FeltSchema::felt("foo"),
65/// ]);
66///
67/// let storage_schema = StorageSchema::new([(
68/// slot_name.clone(),
69/// StorageSlotSchema::Value(ValueSlotSchema::new(Some("demo slot".into()), word)),
70/// )])?;
71///
72/// let metadata = AccountComponentMetadata::new("test name", AccountType::all())
73/// .with_description("description of the component")
74/// .with_storage_schema(storage_schema);
75///
76/// // Init value keys are derived from slot name: `demo::test_value.foo`.
77/// let value_name = StorageValueName::from_slot_name_with_suffix(&slot_name, "foo")?;
78/// let mut init_storage_data = InitStorageData::default();
79/// init_storage_data.set_value(value_name, WordValue::Atomic("300".into()))?;
80///
81/// let storage_slots = metadata.storage_schema().build_storage_slots(&init_storage_data)?;
82/// assert_eq!(storage_slots.len(), 1);
83/// # Ok::<(), Box<dyn std::error::Error>>(())
84/// ```
85#[derive(Debug, Clone, PartialEq, Eq)]
86#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))]
87#[cfg_attr(feature = "std", serde(rename_all = "kebab-case"))]
88pub struct AccountComponentMetadata {
89 /// The human-readable name of the component.
90 name: String,
91
92 /// A brief description of what this component is and how it works.
93 description: String,
94
95 /// The version of the component using semantic versioning.
96 /// This can be used to track and manage component upgrades.
97 version: Version,
98
99 /// A set of supported target account types for this component.
100 supported_types: BTreeSet<AccountType>,
101
102 /// Storage schema defining the component's storage layout, defaults, and init-supplied values.
103 #[cfg_attr(feature = "std", serde(rename = "storage"))]
104 storage_schema: StorageSchema,
105}
106
107impl AccountComponentMetadata {
108 /// Create a new [AccountComponentMetadata] with the given name and supported account types.
109 ///
110 /// Other fields are initialized to sensible defaults:
111 /// - `description`: empty string
112 /// - `version`: 1.0.0
113 /// - `storage_schema`: default (empty)
114 ///
115 /// Use the `with_*` mutator methods to customize these fields.
116 pub fn new(
117 name: impl Into<String>,
118 supported_types: impl IntoIterator<Item = AccountType>,
119 ) -> Self {
120 Self {
121 name: name.into(),
122 description: String::new(),
123 version: Version::new(1, 0, 0),
124 supported_types: supported_types.into_iter().collect(),
125 storage_schema: StorageSchema::default(),
126 }
127 }
128
129 /// Sets the description of the component.
130 pub fn with_description(mut self, description: impl Into<String>) -> Self {
131 self.description = description.into();
132 self
133 }
134
135 /// Sets the version of the component.
136 pub fn with_version(mut self, version: Version) -> Self {
137 self.version = version;
138 self
139 }
140
141 /// Sets the storage schema of the component.
142 pub fn with_storage_schema(mut self, schema: StorageSchema) -> Self {
143 self.storage_schema = schema;
144 self
145 }
146
147 /// Returns the init-time values requirements for this schema.
148 ///
149 /// These values are used for initializing storage slot values or storage map entries. For a
150 /// full example, refer to the docs for [AccountComponentMetadata].
151 ///
152 /// Types for returned init values are inferred based on their location in the storage layout.
153 pub fn schema_requirements(&self) -> BTreeMap<StorageValueName, SchemaRequirement> {
154 self.storage_schema.schema_requirements().expect("storage schema is validated")
155 }
156
157 /// Returns the name of the account component.
158 pub fn name(&self) -> &str {
159 &self.name
160 }
161
162 /// Returns the description of the account component.
163 pub fn description(&self) -> &str {
164 &self.description
165 }
166
167 /// Returns the semantic version of the account component.
168 pub fn version(&self) -> &Version {
169 &self.version
170 }
171
172 /// Returns the account types supported by the component.
173 pub fn supported_types(&self) -> &BTreeSet<AccountType> {
174 &self.supported_types
175 }
176
177 /// Returns the storage schema of the component.
178 pub fn storage_schema(&self) -> &StorageSchema {
179 &self.storage_schema
180 }
181}
182
183impl TryFrom<&Package> for AccountComponentMetadata {
184 type Error = AccountError;
185
186 fn try_from(package: &Package) -> Result<Self, Self::Error> {
187 package
188 .sections
189 .iter()
190 .find_map(|section| {
191 (section.id == SectionId::ACCOUNT_COMPONENT_METADATA).then(|| {
192 AccountComponentMetadata::read_from_bytes(§ion.data).map_err(|err| {
193 AccountError::other_with_source(
194 "failed to deserialize account component metadata",
195 err,
196 )
197 })
198 })
199 })
200 .transpose()?
201 .ok_or_else(|| {
202 AccountError::other(
203 "package does not contain account component metadata section - packages without explicit metadata may be intended for other purposes (e.g., note scripts, transaction scripts)",
204 )
205 })
206 }
207}
208
209// SERIALIZATION
210// ================================================================================================
211
212impl Serializable for AccountComponentMetadata {
213 fn write_into<W: ByteWriter>(&self, target: &mut W) {
214 self.name.write_into(target);
215 self.description.write_into(target);
216 self.version.to_string().write_into(target);
217 self.supported_types.write_into(target);
218 self.storage_schema.write_into(target);
219 }
220}
221
222impl Deserializable for AccountComponentMetadata {
223 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
224 let name = String::read_from(source)?;
225 let description = String::read_from(source)?;
226 if !description.is_ascii() {
227 return Err(DeserializationError::InvalidValue(
228 "description must contain only ASCII characters".to_string(),
229 ));
230 }
231 let version = semver::Version::from_str(&String::read_from(source)?)
232 .map_err(|err: semver::Error| DeserializationError::InvalidValue(err.to_string()))?;
233 let supported_types = BTreeSet::<AccountType>::read_from(source)?;
234 let storage_schema = StorageSchema::read_from(source)?;
235
236 Ok(Self {
237 name,
238 description,
239 version,
240 supported_types,
241 storage_schema,
242 })
243 }
244}