miden_protocol/account/component/
mod.rs1use alloc::collections::BTreeSet;
2use alloc::vec::Vec;
3
4use miden_mast_package::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 = package.mast.as_ref().clone();
108
109 let component_code = AccountComponentCode::from(library);
110 Self::from_library(&component_code, &metadata, init_storage_data)
111 }
112
113 pub fn from_library(
134 library: &AccountComponentCode,
135 metadata: &AccountComponentMetadata,
136 init_storage_data: &InitStorageData,
137 ) -> Result<Self, AccountError> {
138 let storage_slots = metadata
139 .storage_schema()
140 .build_storage_slots(init_storage_data)
141 .map_err(|err| {
142 AccountError::other_with_source("failed to instantiate account component", err)
143 })?;
144
145 AccountComponent::new(library.clone(), storage_slots, metadata.clone())
146 }
147
148 pub fn storage_size(&self) -> u8 {
153 u8::try_from(self.storage_slots.len())
154 .expect("storage slots len should fit in u8 per the constructor")
155 }
156
157 pub fn component_code(&self) -> &AccountComponentCode {
159 &self.code
160 }
161
162 pub fn mast_forest(&self) -> &MastForest {
164 self.code.mast_forest()
165 }
166
167 pub fn storage_slots(&self) -> &[StorageSlot] {
169 self.storage_slots.as_slice()
170 }
171
172 pub fn metadata(&self) -> &AccountComponentMetadata {
174 &self.metadata
175 }
176
177 pub fn storage_schema(&self) -> &StorageSchema {
179 self.metadata.storage_schema()
180 }
181
182 pub fn supported_types(&self) -> &BTreeSet<AccountType> {
184 self.metadata.supported_types()
185 }
186
187 pub fn supports_type(&self, account_type: AccountType) -> bool {
189 self.metadata.supported_types().contains(&account_type)
190 }
191
192 pub fn procedures(&self) -> impl Iterator<Item = (AccountProcedureRoot, bool)> + '_ {
198 let library = self.code.as_library();
199 library.exports().filter_map(|export| {
200 export.as_procedure().map(|proc_export| {
201 let digest = library
202 .mast_forest()
203 .get_node_by_id(proc_export.node)
204 .expect("export node not in the forest")
205 .digest();
206 let is_auth = proc_export.attributes.has(AUTH_SCRIPT_ATTRIBUTE);
207 (AccountProcedureRoot::from_raw(digest), is_auth)
208 })
209 })
210 }
211
212 pub fn get_procedure_root_by_path(&self, proc_name: impl AsRef<Path>) -> Option<Word> {
215 self.code.as_library().get_procedure_root_by_path(proc_name)
216 }
217}
218
219impl From<AccountComponent> for AccountComponentCode {
220 fn from(component: AccountComponent) -> Self {
221 component.code
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use alloc::string::ToString;
228 use alloc::sync::Arc;
229
230 use miden_assembly::Assembler;
231 use miden_mast_package::{Package, PackageManifest, Section, SectionId, TargetType};
232 use semver::Version;
233
234 use super::*;
235 use crate::testing::account_code::CODE;
236 use crate::utils::serde::Serializable;
237
238 #[test]
239 fn test_extract_metadata_from_package() {
240 let library = Assembler::default().assemble_library([CODE]).unwrap();
242
243 let metadata = AccountComponentMetadata::new(
245 "test_component",
246 [AccountType::RegularAccountImmutableCode],
247 )
248 .with_description("A test component")
249 .with_version(Version::new(1, 0, 0));
250
251 let metadata_bytes = metadata.to_bytes();
252 let package_with_metadata = Package {
253 name: "test_package".into(),
254 mast: library.clone(),
255 manifest: PackageManifest::new(core::iter::empty()).unwrap(),
256 kind: TargetType::AccountComponent,
257 sections: vec![Section::new(
258 SectionId::ACCOUNT_COMPONENT_METADATA,
259 metadata_bytes.clone(),
260 )],
261 version: Version::new(0, 0, 0),
262 description: None,
263 };
264
265 let extracted_metadata =
266 AccountComponentMetadata::try_from(&package_with_metadata).unwrap();
267 assert_eq!(extracted_metadata.name(), "test_component");
268 assert!(
269 extracted_metadata
270 .supported_types()
271 .contains(&AccountType::RegularAccountImmutableCode)
272 );
273
274 let package_without_metadata = Package {
276 name: "test_package_no_metadata".into(),
277 mast: library,
278 manifest: PackageManifest::new(core::iter::empty()).unwrap(),
279 kind: TargetType::AccountComponent,
280 sections: vec![], version: Version::new(0, 0, 0),
282 description: None,
283 };
284
285 let result = AccountComponentMetadata::try_from(&package_without_metadata);
286 assert!(result.is_err());
287 let error_msg = result.unwrap_err().to_string();
288 assert!(error_msg.contains("package does not contain account component metadata"));
289 }
290
291 #[test]
292 fn test_from_library_with_init_data() {
293 let library = Assembler::default().assemble_library([CODE]).unwrap();
295 let component_code = AccountComponentCode::from(Arc::unwrap_or_clone(library.clone()));
296
297 let metadata = AccountComponentMetadata::new("test_component", AccountType::regular())
299 .with_description("A test component")
300 .with_version(Version::new(1, 0, 0));
301
302 let init_data = InitStorageData::default();
305 let component =
306 AccountComponent::from_library(&component_code, &metadata, &init_data).unwrap();
307
308 assert_eq!(component.storage_size(), 0);
310 assert!(component.supports_type(AccountType::RegularAccountImmutableCode));
311 assert!(component.supports_type(AccountType::RegularAccountUpdatableCode));
312 assert!(!component.supports_type(AccountType::FungibleFaucet));
313
314 let package_without_metadata = Package {
316 name: "test_package_no_metadata".into(),
317 mast: library,
318 kind: TargetType::AccountComponent,
319 manifest: PackageManifest::new(core::iter::empty()).unwrap(),
320 sections: vec![], version: Version::new(0, 0, 0),
322 description: None,
323 };
324
325 let result = AccountComponent::from_package(&package_without_metadata, &init_data);
326 assert!(result.is_err());
327 let error_msg = result.unwrap_err().to_string();
328 assert!(error_msg.contains("package does not contain account component metadata"));
329 }
330}