miden_objects/account/component/
mod.rs

1use alloc::collections::BTreeSet;
2use alloc::vec::Vec;
3
4use miden_assembly::ast::QualifiedProcedureName;
5use miden_assembly::{Assembler, Library, Parse};
6use miden_core::utils::Deserializable;
7use miden_mast_package::{Package, SectionId};
8use miden_processor::MastForest;
9
10mod template;
11pub use template::*;
12
13use crate::account::{AccountType, StorageSlot};
14use crate::{AccountError, Word};
15
16// IMPLEMENTATIONS
17// ================================================================================================
18
19impl TryFrom<Package> for AccountComponentTemplate {
20    type Error = AccountError;
21
22    fn try_from(package: Package) -> Result<Self, Self::Error> {
23        let library = package.unwrap_library().as_ref().clone();
24
25        // Look for account component metadata in sections
26        let metadata = package
27            .sections
28            .iter()
29            .find_map(|section| {
30                (section.id == SectionId::ACCOUNT_COMPONENT_METADATA).then(|| {
31                        AccountComponentMetadata::read_from_bytes(&section.data)
32                            .map_err(|err| {
33                                AccountError::other_with_source(
34                                    "failed to deserialize account component metadata",
35                                    err,
36                                )
37                            })
38                })
39            })
40            .transpose()?
41            .ok_or_else(|| {
42                AccountError::other(
43                    "package does not contain account component metadata section - packages without explicit metadata may be intended for other purposes (e.g., note scripts, transaction scripts)",
44                )
45            })?;
46
47        Ok(AccountComponentTemplate::new(metadata, library))
48    }
49}
50
51/// An [`AccountComponent`] defines a [`Library`] of code and the initial value and types of
52/// the [`StorageSlot`]s it accesses.
53///
54/// One or more components can be used to built [`AccountCode`](crate::account::AccountCode) and
55/// [`AccountStorage`](crate::account::AccountStorage).
56///
57/// Each component is independent of other components and can only access its own storage slots.
58/// Each component defines its own storage layout starting at index 0 up to the length of the
59/// storage slots vector.
60///
61/// Components define the [`AccountType`]s they support, meaning whether the component can be used
62/// to instantiate an account of that type. For example, a component implementing a fungible faucet
63/// would only specify support for [`AccountType::FungibleFaucet`]. Using it to instantiate a
64/// regular account would fail. By default, the set of supported types is empty, so each component
65/// is forced to explicitly define what it supports.
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct AccountComponent {
68    pub(super) library: Library,
69    pub(super) storage_slots: Vec<StorageSlot>,
70    pub(super) supported_types: BTreeSet<AccountType>,
71}
72
73impl AccountComponent {
74    // CONSTRUCTORS
75    // --------------------------------------------------------------------------------------------
76
77    /// Returns a new [`AccountComponent`] constructed from the provided `library` and
78    /// `storage_slots`.
79    ///
80    /// All procedures exported from the provided code will become members of the account's public
81    /// interface when added to an [`AccountCode`](crate::account::AccountCode).
82    ///
83    /// # Errors
84    ///
85    /// The following list of errors is exhaustive and can be relied upon for `expect`ing the call
86    /// to this function. It is recommended that custom components ensure these conditions by design
87    /// or in their fallible constructors.
88    ///
89    /// Returns an error if:
90    /// - The number of given [`StorageSlot`]s exceeds 255.
91    pub fn new(code: Library, storage_slots: Vec<StorageSlot>) -> Result<Self, AccountError> {
92        // Check that we have less than 256 storage slots.
93        u8::try_from(storage_slots.len())
94            .map_err(|_| AccountError::StorageTooManySlots(storage_slots.len() as u64))?;
95
96        Ok(Self {
97            library: code,
98            storage_slots,
99            supported_types: BTreeSet::new(),
100        })
101    }
102
103    /// Returns a new [`AccountComponent`] whose library is compiled from the provided `source_code`
104    /// using the specified `assembler` and with the given `storage_slots`.
105    ///
106    /// All procedures exported from the provided code will become members of the account's public
107    /// interface when added to an [`AccountCode`](crate::account::AccountCode).
108    ///
109    /// # Errors
110    ///
111    /// Returns an error if:
112    /// - the compilation of the provided source code fails.
113    /// - The number of storage slots exceeds 255.
114    pub fn compile(
115        source_code: impl Parse,
116        assembler: Assembler,
117        storage_slots: Vec<StorageSlot>,
118    ) -> Result<Self, AccountError> {
119        let library = assembler
120            .assemble_library([source_code])
121            .map_err(AccountError::AccountComponentAssemblyError)?;
122
123        Self::new(library, storage_slots)
124    }
125
126    /// Instantiates an [AccountComponent] from the [AccountComponentTemplate].
127    ///
128    /// The template's component metadata might contain placeholders, which can be replaced by
129    /// mapping storage placeholders to values through the `init_storage_data` parameter.
130    ///
131    /// # Errors
132    ///
133    /// - If any of the component's storage entries cannot be transformed into a valid storage slot.
134    ///   This could be because the metadata is invalid, or storage values were not provided (or
135    ///   they are not of a valid type)
136    pub fn from_template(
137        template: &AccountComponentTemplate,
138        init_storage_data: &InitStorageData,
139    ) -> Result<AccountComponent, AccountError> {
140        let mut storage_slots = vec![];
141        for storage_entry in template.metadata().storage_entries() {
142            let entry_storage_slots = storage_entry
143                .try_build_storage_slots(init_storage_data)
144                .map_err(AccountError::AccountComponentTemplateInstantiationError)?;
145            storage_slots.extend(entry_storage_slots);
146        }
147
148        Ok(AccountComponent::new(template.library().clone(), storage_slots)?
149            .with_supported_types(template.metadata().supported_types().clone()))
150    }
151
152    /// Creates an [`AccountComponent`] from a [`Package`] using [`InitStorageData`].
153    ///
154    /// This method provides type safety by leveraging the component's metadata to validate
155    /// storage initialization data. The package must contain explicit account component metadata.
156    ///
157    /// # Arguments
158    ///
159    /// * `package` - The package containing the library and account component metadata
160    /// * `init_storage_data` - The initialization data for storage slots
161    ///
162    /// # Errors
163    ///
164    /// Returns an error if:
165    /// - The package does not contain account component metadata
166    /// - The package cannot be converted to an [`AccountComponentTemplate`]
167    /// - The storage initialization fails due to invalid or missing data
168    /// - The component creation fails
169    pub fn from_package_with_init_data(
170        package: &Package,
171        init_storage_data: &InitStorageData,
172    ) -> Result<Self, AccountError> {
173        let template = AccountComponentTemplate::try_from(package.clone())?;
174        Self::from_template(&template, init_storage_data)
175    }
176
177    // ACCESSORS
178    // --------------------------------------------------------------------------------------------
179
180    /// Returns the number of storage slots accessible from this component.
181    pub fn storage_size(&self) -> u8 {
182        u8::try_from(self.storage_slots.len())
183            .expect("storage slots len should fit in u8 per the constructor")
184    }
185
186    /// Returns a reference to the underlying [`Library`] of this component.
187    pub fn library(&self) -> &Library {
188        &self.library
189    }
190
191    /// Returns a reference to the underlying [`MastForest`] of this component.
192    pub fn mast_forest(&self) -> &MastForest {
193        self.library.mast_forest().as_ref()
194    }
195
196    /// Returns a slice of the underlying [`StorageSlot`]s of this component.
197    pub fn storage_slots(&self) -> &[StorageSlot] {
198        self.storage_slots.as_slice()
199    }
200
201    /// Returns a reference to the supported [`AccountType`]s.
202    pub fn supported_types(&self) -> &BTreeSet<AccountType> {
203        &self.supported_types
204    }
205
206    /// Returns `true` if this component supports the given `account_type`, `false` otherwise.
207    pub fn supports_type(&self, account_type: AccountType) -> bool {
208        self.supported_types.contains(&account_type)
209    }
210
211    /// Returns a vector of tuples (digest, is_auth) for all procedures in this component.
212    pub fn get_procedures(&self) -> Vec<(Word, bool)> {
213        let mut procedures = Vec::new();
214        for module in self.library.module_infos() {
215            for (_, procedure_info) in module.procedures() {
216                let is_auth = procedure_info.name.starts_with("auth_");
217                procedures.push((procedure_info.digest, is_auth));
218            }
219        }
220        procedures
221    }
222
223    /// Returns the digest of the procedure with the specified name, or `None` if it was not found
224    /// in this component's library or its library path is malformed.
225    pub fn get_procedure_root_by_name(
226        &self,
227        proc_name: impl TryInto<QualifiedProcedureName>,
228    ) -> Option<Word> {
229        self.library.get_procedure_root_by_name(proc_name)
230    }
231
232    // MUTATORS
233    // --------------------------------------------------------------------------------------------
234
235    /// Adds `supported_type` to the set of [`AccountType`]s supported by this component.
236    ///
237    /// This function has the semantics of [`BTreeSet::insert`], i.e. adding a type twice is fine
238    /// and it can be called multiple times with different account types.
239    pub fn with_supported_type(mut self, supported_type: AccountType) -> Self {
240        self.supported_types.insert(supported_type);
241        self
242    }
243
244    /// Overwrites any previously set supported types with the given set.
245    ///
246    /// This can be used to reset the supported types of a component to a chosen set, which may be
247    /// useful after cloning an existing component.
248    pub fn with_supported_types(mut self, supported_types: BTreeSet<AccountType>) -> Self {
249        self.supported_types = supported_types;
250        self
251    }
252
253    /// Sets the [`AccountType`]s supported by this component to all account types.
254    pub fn with_supports_all_types(mut self) -> Self {
255        self.supported_types.extend([
256            AccountType::FungibleFaucet,
257            AccountType::NonFungibleFaucet,
258            AccountType::RegularAccountImmutableCode,
259            AccountType::RegularAccountUpdatableCode,
260        ]);
261        self
262    }
263}
264
265impl From<AccountComponent> for Library {
266    fn from(component: AccountComponent) -> Self {
267        component.library
268    }
269}
270
271#[cfg(test)]
272mod tests {
273    use alloc::collections::BTreeSet;
274    use alloc::string::ToString;
275    use alloc::sync::Arc;
276
277    use miden_assembly::Assembler;
278    use miden_core::utils::Serializable;
279    use miden_mast_package::{MastArtifact, Package, PackageManifest, Section};
280    use semver::Version;
281
282    use super::*;
283    use crate::testing::account_code::CODE;
284
285    #[test]
286    fn test_try_from_package_for_template() {
287        // Create a simple library for testing
288        let library = Assembler::default().assemble_library([CODE]).unwrap();
289
290        // Test with metadata
291        let metadata = AccountComponentMetadata::new(
292            "test_component".to_string(),
293            "A test component".to_string(),
294            Version::new(1, 0, 0),
295            BTreeSet::from_iter([AccountType::RegularAccountImmutableCode]),
296            vec![],
297        )
298        .unwrap();
299
300        let metadata_bytes = metadata.to_bytes();
301        let package_with_metadata = Package {
302            name: "test_package".to_string(),
303            mast: MastArtifact::Library(Arc::new(library.clone())),
304            manifest: PackageManifest::new(None),
305
306            sections: vec![Section::new(
307                SectionId::ACCOUNT_COMPONENT_METADATA,
308                metadata_bytes.clone(),
309            )],
310            version: Default::default(),
311            description: None,
312        };
313
314        let template = AccountComponentTemplate::try_from(package_with_metadata).unwrap();
315        assert_eq!(template.metadata().name(), "test_component");
316        assert!(
317            template
318                .metadata()
319                .supported_types()
320                .contains(&AccountType::RegularAccountImmutableCode)
321        );
322
323        // Test without metadata - should fail
324        let package_without_metadata = Package {
325            name: "test_package_no_metadata".to_string(),
326            mast: MastArtifact::Library(Arc::new(library)),
327            manifest: PackageManifest::new(None),
328            sections: vec![], // No metadata section
329            version: Default::default(),
330            description: None,
331        };
332
333        let result = AccountComponentTemplate::try_from(package_without_metadata);
334        assert!(result.is_err());
335        let error_msg = result.unwrap_err().to_string();
336        assert!(error_msg.contains("package does not contain account component metadata"));
337    }
338
339    #[test]
340    fn test_from_package_with_init_data() {
341        // Create a simple library for testing
342        let library = Assembler::default().assemble_library([CODE]).unwrap();
343
344        // Create metadata for the component
345        let metadata = AccountComponentMetadata::new(
346            "test_component".to_string(),
347            "A test component".to_string(),
348            Version::new(1, 0, 0),
349            BTreeSet::from_iter([
350                AccountType::RegularAccountImmutableCode,
351                AccountType::RegularAccountUpdatableCode,
352            ]),
353            vec![],
354        )
355        .unwrap();
356
357        // Create a package with metadata
358        let package = Package {
359            name: "test_package_init_data".to_string(),
360            mast: MastArtifact::Library(Arc::new(library.clone())),
361            manifest: PackageManifest::new(None),
362            sections: vec![Section::new(
363                SectionId::ACCOUNT_COMPONENT_METADATA,
364                metadata.to_bytes(),
365            )],
366            version: Default::default(),
367            description: None,
368        };
369
370        // Test with empty init data - this tests the complete workflow:
371        // Package -> AccountComponentTemplate -> AccountComponent
372        let init_data = InitStorageData::default();
373        let component =
374            AccountComponent::from_package_with_init_data(&package, &init_data).unwrap();
375
376        // Verify the component was created correctly
377        assert_eq!(component.storage_size(), 0);
378        assert!(component.supports_type(AccountType::RegularAccountImmutableCode));
379        assert!(component.supports_type(AccountType::RegularAccountUpdatableCode));
380        assert!(!component.supports_type(AccountType::FungibleFaucet));
381
382        // Test without metadata - should fail
383        let package_without_metadata = Package {
384            name: "test_package_no_metadata".to_string(),
385            mast: MastArtifact::Library(Arc::new(library)),
386            manifest: PackageManifest::new(None),
387            sections: vec![], // No metadata section
388            version: Default::default(),
389            description: None,
390        };
391
392        let result =
393            AccountComponent::from_package_with_init_data(&package_without_metadata, &init_data);
394        assert!(result.is_err());
395        let error_msg = result.unwrap_err().to_string();
396        assert!(error_msg.contains("package does not contain account component metadata"));
397    }
398}