miden_objects/account/component/
mod.rs1use 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
16impl 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 let metadata = package
27 .sections
28 .iter()
29 .find_map(|section| {
30 (section.id == SectionId::ACCOUNT_COMPONENT_METADATA).then(|| {
31 AccountComponentMetadata::read_from_bytes(§ion.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#[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 pub fn new(code: Library, storage_slots: Vec<StorageSlot>) -> Result<Self, AccountError> {
92 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 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 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 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 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 pub fn library(&self) -> &Library {
188 &self.library
189 }
190
191 pub fn mast_forest(&self) -> &MastForest {
193 self.library.mast_forest().as_ref()
194 }
195
196 pub fn storage_slots(&self) -> &[StorageSlot] {
198 self.storage_slots.as_slice()
199 }
200
201 pub fn supported_types(&self) -> &BTreeSet<AccountType> {
203 &self.supported_types
204 }
205
206 pub fn supports_type(&self, account_type: AccountType) -> bool {
208 self.supported_types.contains(&account_type)
209 }
210
211 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 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 pub fn with_supported_type(mut self, supported_type: AccountType) -> Self {
240 self.supported_types.insert(supported_type);
241 self
242 }
243
244 pub fn with_supported_types(mut self, supported_types: BTreeSet<AccountType>) -> Self {
249 self.supported_types = supported_types;
250 self
251 }
252
253 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 let library = Assembler::default().assemble_library([CODE]).unwrap();
289
290 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 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![], 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 let library = Assembler::default().assemble_library([CODE]).unwrap();
343
344 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 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 let init_data = InitStorageData::default();
373 let component =
374 AccountComponent::from_package_with_init_data(&package, &init_data).unwrap();
375
376 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 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![], 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}