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::{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_mast_package::{
240 MastArtifact,
241 Package,
242 PackageKind,
243 PackageManifest,
244 Section,
245 SectionId,
246 };
247 use semver::Version;
248
249 use super::*;
250 use crate::testing::account_code::CODE;
251 use crate::utils::serde::Serializable;
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(
260 "test_component",
261 [AccountType::RegularAccountImmutableCode],
262 )
263 .with_description("A test component")
264 .with_version(Version::new(1, 0, 0));
265
266 let metadata_bytes = metadata.to_bytes();
267 let package_with_metadata = Package {
268 name: "test_package".to_string(),
269 mast: MastArtifact::Library(Arc::new(library.clone())),
270 manifest: PackageManifest::new(None),
271 kind: PackageKind::AccountComponent,
272 sections: vec![Section::new(
273 SectionId::ACCOUNT_COMPONENT_METADATA,
274 metadata_bytes.clone(),
275 )],
276 version: Default::default(),
277 description: None,
278 };
279
280 let extracted_metadata =
281 AccountComponentMetadata::try_from(&package_with_metadata).unwrap();
282 assert_eq!(extracted_metadata.name(), "test_component");
283 assert!(
284 extracted_metadata
285 .supported_types()
286 .contains(&AccountType::RegularAccountImmutableCode)
287 );
288
289 let package_without_metadata = Package {
291 name: "test_package_no_metadata".to_string(),
292 mast: MastArtifact::Library(Arc::new(library)),
293 manifest: PackageManifest::new(None),
294 kind: PackageKind::AccountComponent,
295 sections: vec![], version: Default::default(),
297 description: None,
298 };
299
300 let result = AccountComponentMetadata::try_from(&package_without_metadata);
301 assert!(result.is_err());
302 let error_msg = result.unwrap_err().to_string();
303 assert!(error_msg.contains("package does not contain account component metadata"));
304 }
305
306 #[test]
307 fn test_from_library_with_init_data() {
308 let library = Assembler::default().assemble_library([CODE]).unwrap();
310 let component_code = AccountComponentCode::from(library.clone());
311
312 let metadata = AccountComponentMetadata::new("test_component", AccountType::regular())
314 .with_description("A test component")
315 .with_version(Version::new(1, 0, 0));
316
317 let init_data = InitStorageData::default();
320 let component =
321 AccountComponent::from_library(&component_code, &metadata, &init_data).unwrap();
322
323 assert_eq!(component.storage_size(), 0);
325 assert!(component.supports_type(AccountType::RegularAccountImmutableCode));
326 assert!(component.supports_type(AccountType::RegularAccountUpdatableCode));
327 assert!(!component.supports_type(AccountType::FungibleFaucet));
328
329 let package_without_metadata = Package {
331 name: "test_package_no_metadata".to_string(),
332 mast: MastArtifact::Library(Arc::new(library)),
333 kind: PackageKind::AccountComponent,
334 manifest: PackageManifest::new(None),
335 sections: vec![], version: Default::default(),
337 description: None,
338 };
339
340 let result = AccountComponent::from_package(&package_without_metadata, &init_data);
341 assert!(result.is_err());
342 let error_msg = result.unwrap_err().to_string();
343 assert!(error_msg.contains("package does not contain account component metadata"));
344 }
345}