Skip to main content

miden_protocol/account/component/metadata/
mod.rs

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