miden_protocol/account/component/
mod.rs1use alloc::collections::BTreeSet;
2use alloc::vec::Vec;
3
4use miden_mast_package::{MastArtifact, Package};
5use miden_processor::mast::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::{AccountProcedureRoot, 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 procedures(&self) -> impl Iterator<Item = (AccountProcedureRoot, bool)> + '_ {
205 let library = self.code.as_library();
206 library.exports().filter_map(|export| {
207 export.as_procedure().map(|proc_export| {
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 (AccountProcedureRoot::from_raw(digest), is_auth)
215 })
216 })
217 }
218
219 pub fn get_procedure_root_by_path(&self, proc_name: impl AsRef<Path>) -> Option<Word> {
222 self.code.as_library().get_procedure_root_by_path(proc_name)
223 }
224}
225
226impl From<AccountComponent> for AccountComponentCode {
227 fn from(component: AccountComponent) -> Self {
228 component.code
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use alloc::string::ToString;
235 use alloc::sync::Arc;
236
237 use miden_assembly::Assembler;
238 use miden_mast_package::{
239 MastArtifact,
240 Package,
241 PackageKind,
242 PackageManifest,
243 Section,
244 SectionId,
245 };
246 use semver::Version;
247
248 use super::*;
249 use crate::testing::account_code::CODE;
250 use crate::utils::serde::Serializable;
251
252 #[test]
253 fn test_extract_metadata_from_package() {
254 let library = Assembler::default().assemble_library([CODE]).unwrap();
256
257 let metadata = AccountComponentMetadata::new(
259 "test_component",
260 [AccountType::RegularAccountImmutableCode],
261 )
262 .with_description("A test component")
263 .with_version(Version::new(1, 0, 0));
264
265 let metadata_bytes = metadata.to_bytes();
266 let package_with_metadata = Package {
267 name: "test_package".to_string(),
268 mast: MastArtifact::Library(Arc::new(library.clone())),
269 manifest: PackageManifest::new(None),
270 kind: PackageKind::AccountComponent,
271 sections: vec![Section::new(
272 SectionId::ACCOUNT_COMPONENT_METADATA,
273 metadata_bytes.clone(),
274 )],
275 version: Default::default(),
276 description: None,
277 };
278
279 let extracted_metadata =
280 AccountComponentMetadata::try_from(&package_with_metadata).unwrap();
281 assert_eq!(extracted_metadata.name(), "test_component");
282 assert!(
283 extracted_metadata
284 .supported_types()
285 .contains(&AccountType::RegularAccountImmutableCode)
286 );
287
288 let package_without_metadata = Package {
290 name: "test_package_no_metadata".to_string(),
291 mast: MastArtifact::Library(Arc::new(library)),
292 manifest: PackageManifest::new(None),
293 kind: PackageKind::AccountComponent,
294 sections: vec![], version: Default::default(),
296 description: None,
297 };
298
299 let result = AccountComponentMetadata::try_from(&package_without_metadata);
300 assert!(result.is_err());
301 let error_msg = result.unwrap_err().to_string();
302 assert!(error_msg.contains("package does not contain account component metadata"));
303 }
304
305 #[test]
306 fn test_from_library_with_init_data() {
307 let library = Assembler::default().assemble_library([CODE]).unwrap();
309 let component_code = AccountComponentCode::from(library.clone());
310
311 let metadata = AccountComponentMetadata::new("test_component", AccountType::regular())
313 .with_description("A test component")
314 .with_version(Version::new(1, 0, 0));
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}