miden_protocol/account/component/
mod.rs1use 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
21const AUTH_SCRIPT_ATTRIBUTE: &str = "auth_script";
23
24#[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 pub fn new(
68 code: impl Into<AccountComponentCode>,
69 storage_slots: Vec<StorageSlot>,
70 metadata: AccountComponentMetadata,
71 ) -> Result<Self, AccountError> {
72 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 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 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 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 pub fn component_code(&self) -> &AccountComponentCode {
166 &self.code
167 }
168
169 pub fn mast_forest(&self) -> &MastForest {
171 self.code.mast_forest()
172 }
173
174 pub fn storage_slots(&self) -> &[StorageSlot] {
176 self.storage_slots.as_slice()
177 }
178
179 pub fn metadata(&self) -> &AccountComponentMetadata {
181 &self.metadata
182 }
183
184 pub fn storage_schema(&self) -> &StorageSchema {
186 self.metadata.storage_schema()
187 }
188
189 pub fn supported_types(&self) -> &BTreeSet<AccountType> {
191 self.metadata.supported_types()
192 }
193
194 pub fn supports_type(&self, account_type: AccountType) -> bool {
196 self.metadata.supported_types().contains(&account_type)
197 }
198
199 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 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 let library = Assembler::default().assemble_library([CODE]).unwrap();
257
258 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 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![], 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 let library = Assembler::default().assemble_library([CODE]).unwrap();
308 let component_code = AccountComponentCode::from(library.clone());
309
310 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 let init_data = InitStorageData::default();
319 let component =
320 AccountComponent::from_library(&component_code, &metadata, &init_data).unwrap();
321
322 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 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![], 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}