Skip to main content

miden_protocol/account/component/
mod.rs

1use alloc::vec::Vec;
2
3use miden_mast_package::Package;
4use miden_processor::mast::MastNodeExt;
5
6mod metadata;
7pub use metadata::*;
8
9pub mod storage;
10pub use storage::*;
11
12mod code;
13pub use code::AccountComponentCode;
14
15use crate::MastForest;
16use crate::account::{AccountProcedureRoot, StorageSlot};
17use crate::assembly::Path;
18use crate::errors::AccountError;
19
20/// The attribute name used to mark the authentication procedure in an account component.
21const AUTH_SCRIPT_ATTRIBUTE: &str = "auth_script";
22
23// ACCOUNT COMPONENT
24// ================================================================================================
25
26/// An [`AccountComponent`] defines a [`Library`](miden_assembly::Library) of code and the initial
27/// value and types of the [`StorageSlot`]s it accesses.
28///
29/// One or more components can be used to built [`AccountCode`](crate::account::AccountCode) and
30/// [`AccountStorage`](crate::account::AccountStorage).
31///
32/// Each component is independent of other components and can only access its own storage slots.
33/// Each component defines its own storage layout starting at index 0 up to the length of the
34/// storage slots vector.
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct AccountComponent {
37    pub(super) code: AccountComponentCode,
38    pub(super) storage_slots: Vec<StorageSlot>,
39    pub(super) metadata: AccountComponentMetadata,
40}
41
42impl AccountComponent {
43    // CONSTRUCTORS
44    // --------------------------------------------------------------------------------------------
45
46    /// Returns a new [`AccountComponent`] constructed from the provided `library`,
47    /// `storage_slots`, and `metadata`.
48    ///
49    /// All procedures exported from the provided code will become members of the account's public
50    /// interface when added to an [`AccountCode`](crate::account::AccountCode).
51    ///
52    /// # Errors
53    ///
54    /// The following list of errors is exhaustive and can be relied upon for `expect`ing the call
55    /// to this function. It is recommended that custom components ensure these conditions by design
56    /// or in their fallible constructors.
57    ///
58    /// Returns an error if:
59    /// - The number of given [`StorageSlot`]s exceeds 255.
60    pub fn new(
61        code: impl Into<AccountComponentCode>,
62        storage_slots: Vec<StorageSlot>,
63        metadata: AccountComponentMetadata,
64    ) -> Result<Self, AccountError> {
65        // Check that we have less than 256 storage slots.
66        u8::try_from(storage_slots.len())
67            .map_err(|_| AccountError::StorageTooManySlots(storage_slots.len() as u64))?;
68
69        Ok(Self {
70            code: code.into(),
71            storage_slots,
72            metadata,
73        })
74    }
75
76    /// Creates an [`AccountComponent`] from a [`Package`] using [`InitStorageData`].
77    ///
78    /// This method provides type safety by leveraging the component's metadata to validate
79    /// storage initialization data. The package must contain explicit account component metadata.
80    ///
81    /// # Arguments
82    ///
83    /// * `package` - The package containing the [`Library`](miden_assembly::Library) and account
84    ///   component metadata
85    /// * `init_storage_data` - The initialization data for storage slots
86    ///
87    /// # Errors
88    ///
89    /// Returns an error if:
90    /// - The package does not contain a library artifact
91    /// - The package does not contain account component metadata
92    /// - The metadata cannot be deserialized from the package
93    /// - The storage initialization fails due to invalid or missing data
94    /// - The component creation fails
95    pub fn from_package(
96        package: &Package,
97        init_storage_data: &InitStorageData,
98    ) -> Result<Self, AccountError> {
99        let metadata = AccountComponentMetadata::try_from(package)?;
100        let library = package.mast.as_ref().clone();
101
102        let component_code = AccountComponentCode::from(library);
103        Self::from_library(&component_code, &metadata, init_storage_data)
104    }
105
106    /// Creates an [`AccountComponent`] from an [`AccountComponentCode`] and
107    /// [`AccountComponentMetadata`].
108    ///
109    /// This method provides type safety by leveraging the component's metadata to validate
110    /// the passed storage initialization data ([`InitStorageData`]).
111    ///
112    /// # Arguments
113    ///
114    /// * `library` - The component's assembled code
115    /// * `metadata` - The component's metadata, which describes the storage layout
116    /// * `init_storage_data` - The initialization data for storage slots
117    ///
118    /// # Errors
119    ///
120    /// Returns an error if:
121    /// - The package does not contain a library artifact
122    /// - The package does not contain account component metadata
123    /// - The metadata cannot be deserialized from the package
124    /// - The storage initialization fails due to invalid or missing data
125    /// - The component creation fails
126    pub fn from_library(
127        library: &AccountComponentCode,
128        metadata: &AccountComponentMetadata,
129        init_storage_data: &InitStorageData,
130    ) -> Result<Self, AccountError> {
131        let storage_slots = metadata
132            .storage_schema()
133            .build_storage_slots(init_storage_data)
134            .map_err(|err| {
135                AccountError::other_with_source("failed to instantiate account component", err)
136            })?;
137
138        AccountComponent::new(library.clone(), storage_slots, metadata.clone())
139    }
140
141    // ACCESSORS
142    // --------------------------------------------------------------------------------------------
143
144    /// Returns the number of storage slots accessible from this component.
145    pub fn storage_size(&self) -> u8 {
146        u8::try_from(self.storage_slots.len())
147            .expect("storage slots len should fit in u8 per the constructor")
148    }
149
150    /// Returns a reference to the underlying [`AccountComponentCode`] of this component.
151    pub fn component_code(&self) -> &AccountComponentCode {
152        &self.code
153    }
154
155    /// Returns a reference to the underlying [`MastForest`] of this component.
156    pub fn mast_forest(&self) -> &MastForest {
157        self.code.mast_forest()
158    }
159
160    /// Returns a slice of the underlying [`StorageSlot`]s of this component.
161    pub fn storage_slots(&self) -> &[StorageSlot] {
162        self.storage_slots.as_slice()
163    }
164
165    /// Returns the component metadata.
166    pub fn metadata(&self) -> &AccountComponentMetadata {
167        &self.metadata
168    }
169
170    /// Returns the storage schema associated with this component.
171    pub fn storage_schema(&self) -> &StorageSchema {
172        self.metadata.storage_schema()
173    }
174
175    /// Returns an iterator over ([`AccountProcedureRoot`], is_auth) for all procedures in this
176    /// component.
177    ///
178    /// A procedure is considered an authentication procedure if it has the `@auth_script`
179    /// attribute.
180    pub fn procedures(&self) -> impl Iterator<Item = (AccountProcedureRoot, bool)> + '_ {
181        let library = self.code.as_library();
182        library.exports().filter_map(|export| {
183            export.as_procedure().map(|proc_export| {
184                let digest = library
185                    .mast_forest()
186                    .get_node_by_id(proc_export.node)
187                    .expect("export node not in the forest")
188                    .digest();
189                let is_auth = proc_export.attributes.has(AUTH_SCRIPT_ATTRIBUTE);
190                (AccountProcedureRoot::from_raw(digest), is_auth)
191            })
192        })
193    }
194
195    /// Returns the [`AccountProcedureRoot`] of the procedure with the specified path, or `None`
196    /// if it was not found in this component's library.
197    pub fn get_procedure_root_by_path(
198        &self,
199        proc_name: impl AsRef<Path>,
200    ) -> Option<AccountProcedureRoot> {
201        self.code.get_procedure_root_by_path(proc_name)
202    }
203}
204
205impl From<AccountComponent> for AccountComponentCode {
206    fn from(component: AccountComponent) -> Self {
207        component.code
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use alloc::string::ToString;
214    use alloc::sync::Arc;
215
216    use miden_assembly::Assembler;
217    use miden_mast_package::{Package, PackageManifest, Section, SectionId, TargetType};
218    use semver::Version;
219
220    use super::*;
221    use crate::testing::account_code::CODE;
222    use crate::utils::serde::Serializable;
223
224    #[test]
225    fn test_extract_metadata_from_package() {
226        // Create a simple library for testing
227        let library = Assembler::default().assemble_library([CODE]).unwrap();
228
229        // Test with metadata
230        let metadata = AccountComponentMetadata::new("test_component")
231            .with_description("A test component")
232            .with_version(Version::new(1, 0, 0));
233
234        let metadata_bytes = metadata.to_bytes();
235        let package_with_metadata = Package {
236            name: "test_package".into(),
237            mast: library.clone(),
238            manifest: PackageManifest::new(core::iter::empty()).unwrap(),
239            kind: TargetType::AccountComponent,
240            sections: vec![Section::new(
241                SectionId::ACCOUNT_COMPONENT_METADATA,
242                metadata_bytes.clone(),
243            )],
244            version: Version::new(0, 0, 0),
245            description: None,
246        };
247
248        let extracted_metadata =
249            AccountComponentMetadata::try_from(&package_with_metadata).unwrap();
250        assert_eq!(extracted_metadata.name(), "test_component");
251
252        // Test without metadata - should fail
253        let package_without_metadata = Package {
254            name: "test_package_no_metadata".into(),
255            mast: library,
256            manifest: PackageManifest::new(core::iter::empty()).unwrap(),
257            kind: TargetType::AccountComponent,
258            sections: vec![], // No metadata section
259            version: Version::new(0, 0, 0),
260            description: None,
261        };
262
263        let result = AccountComponentMetadata::try_from(&package_without_metadata);
264        assert!(result.is_err());
265        let error_msg = result.unwrap_err().to_string();
266        assert!(error_msg.contains("package does not contain account component metadata"));
267    }
268
269    #[test]
270    fn test_from_library_with_init_data() {
271        // Create a simple library for testing
272        let library = Assembler::default().assemble_library([CODE]).unwrap();
273        let component_code = AccountComponentCode::from(Arc::unwrap_or_clone(library.clone()));
274
275        // Create metadata for the component
276        let metadata = AccountComponentMetadata::new("test_component")
277            .with_description("A test component")
278            .with_version(Version::new(1, 0, 0));
279
280        // Test with empty init data - this tests the complete workflow:
281        // Library + Metadata -> AccountComponent
282        let init_data = InitStorageData::default();
283        let component =
284            AccountComponent::from_library(&component_code, &metadata, &init_data).unwrap();
285
286        // Verify the component was created correctly
287        assert_eq!(component.storage_size(), 0);
288
289        // Test without metadata - should fail
290        let package_without_metadata = Package {
291            name: "test_package_no_metadata".into(),
292            mast: library,
293            kind: TargetType::AccountComponent,
294            manifest: PackageManifest::new(core::iter::empty()).unwrap(),
295            sections: vec![], // No metadata section
296            version: Version::new(0, 0, 0),
297            description: None,
298        };
299
300        let result = AccountComponent::from_package(&package_without_metadata, &init_data);
301        assert!(result.is_err());
302        let error_msg = result.unwrap_err().to_string();
303        assert!(error_msg.contains("package does not contain account component metadata"));
304    }
305}