Skip to main content

miden_protocol/account/component/
mod.rs

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