miden_protocol/account/component/
mod.rs1use alloc::vec::Vec;
2
3use miden_mast_package::Package;
4use miden_processor::mast::MastNodeExt;
5
6mod metadata;
7pub use metadata::*;
8
9pub mod storage;
10pub use storage::*;
11
12mod code;
13pub use code::AccountComponentCode;
14
15use crate::MastForest;
16use crate::account::{AccountProcedureRoot, StorageSlot};
17use crate::assembly::Path;
18use crate::errors::AccountError;
19
20const AUTH_SCRIPT_ATTRIBUTE: &str = "auth_script";
22
23const ACCOUNT_PROCEDURE_ATTRIBUTE: &str = "account_procedure";
25
26#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct AccountComponent {
40 pub(super) code: AccountComponentCode,
41 pub(super) storage_slots: Vec<StorageSlot>,
42 pub(super) metadata: AccountComponentMetadata,
43}
44
45impl AccountComponent {
46 pub fn new(
65 code: impl Into<AccountComponentCode>,
66 storage_slots: Vec<StorageSlot>,
67 metadata: AccountComponentMetadata,
68 ) -> Result<Self, AccountError> {
69 u8::try_from(storage_slots.len())
71 .map_err(|_| AccountError::StorageTooManySlots(storage_slots.len() as u64))?;
72
73 Ok(Self {
74 code: code.into(),
75 storage_slots,
76 metadata,
77 })
78 }
79
80 pub fn from_package(
98 package: &Package,
99 init_storage_data: &InitStorageData,
100 ) -> Result<Self, AccountError> {
101 let metadata = AccountComponentMetadata::try_from(package)?;
102 let component_code = AccountComponentCode::from(package.clone());
103
104 let storage_slots = metadata
105 .storage_schema()
106 .build_storage_slots(init_storage_data)
107 .map_err(|err| {
108 AccountError::other_with_source("failed to instantiate account component", err)
109 })?;
110
111 AccountComponent::new(component_code, storage_slots, metadata)
112 }
113
114 pub fn storage_size(&self) -> u8 {
119 u8::try_from(self.storage_slots.len())
120 .expect("storage slots len should fit in u8 per the constructor")
121 }
122
123 pub fn component_code(&self) -> &AccountComponentCode {
125 &self.code
126 }
127
128 pub fn mast_forest(&self) -> &MastForest {
130 self.code.mast_forest()
131 }
132
133 pub fn storage_slots(&self) -> &[StorageSlot] {
135 self.storage_slots.as_slice()
136 }
137
138 pub fn metadata(&self) -> &AccountComponentMetadata {
140 &self.metadata
141 }
142
143 pub fn storage_schema(&self) -> &StorageSchema {
145 self.metadata.storage_schema()
146 }
147
148 pub fn procedures(&self) -> impl Iterator<Item = (AccountProcedureRoot, bool)> + '_ {
155 self.code.exports().map(|proc_export| {
156 let digest = if let Some(node) = proc_export.node {
159 self.code
160 .mast_forest()
161 .get_node_by_id(node)
162 .expect("export node not in the forest")
163 .digest()
164 } else {
165 proc_export.digest
166 };
167 let is_auth = proc_export.attributes.has(AUTH_SCRIPT_ATTRIBUTE);
168 (AccountProcedureRoot::from_raw(digest), is_auth)
169 })
170 }
171
172 pub fn get_procedure_root_by_path(
175 &self,
176 proc_name: impl AsRef<Path>,
177 ) -> Option<AccountProcedureRoot> {
178 self.code.get_procedure_root_by_path(proc_name)
179 }
180
181 pub fn has_procedure(&self, root: AccountProcedureRoot) -> bool {
184 self.procedures().any(|(proc_root, _)| proc_root == root)
185 }
186
187 pub fn is_auth_component(&self) -> bool {
190 self.procedures().any(|(_, is_auth)| is_auth)
191 }
192}
193
194impl From<AccountComponent> for AccountComponentCode {
195 fn from(component: AccountComponent) -> Self {
196 component.code
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use alloc::string::ToString;
203
204 use miden_mast_package::{Section, SectionId};
205 use semver::Version;
206
207 use super::*;
208 use crate::testing::account_code::CODE;
209 use crate::testing::assembler::assemble_test_package;
210 use crate::utils::serde::Serializable;
211
212 #[test]
213 fn test_extract_metadata_from_package() {
214 let package =
216 assemble_test_package("test-extract-metadata", "test::extract_metadata", CODE);
217
218 let metadata = AccountComponentMetadata::new("test_component")
220 .with_description("A test component")
221 .with_version(Version::new(1, 0, 0));
222
223 let metadata_bytes = metadata.to_bytes();
224 let mut package_with_metadata = package.clone();
225 package_with_metadata
226 .sections
227 .push(Section::new(SectionId::ACCOUNT_COMPONENT_METADATA, metadata_bytes.clone()));
228
229 let extracted_metadata =
230 AccountComponentMetadata::try_from(&package_with_metadata).unwrap();
231 assert_eq!(extracted_metadata.name(), "test_component");
232
233 let package_without_metadata = package;
235
236 let result = AccountComponentMetadata::try_from(&package_without_metadata);
237 assert!(result.is_err());
238 let error_msg = result.unwrap_err().to_string();
239 assert!(error_msg.contains("package does not contain account component metadata"));
240 }
241
242 #[test]
243 fn test_from_package_with_init_data() {
244 let package =
246 assemble_test_package("test-from-package-init-data", "test::from_package", CODE);
247
248 let metadata = AccountComponentMetadata::new("test_component")
250 .with_description("A test component")
251 .with_version(Version::new(1, 0, 0));
252
253 let mut package_with_metadata = package.clone();
254 package_with_metadata
255 .sections
256 .push(Section::new(SectionId::ACCOUNT_COMPONENT_METADATA, metadata.to_bytes()));
257
258 let init_data = InitStorageData::default();
261 let component = AccountComponent::from_package(&package_with_metadata, &init_data).unwrap();
262
263 assert_eq!(component.storage_size(), 0);
265
266 let result = AccountComponent::from_package(&package, &init_data);
268 assert!(result.is_err());
269 let error_msg = result.unwrap_err().to_string();
270 assert!(error_msg.contains("package does not contain account component metadata"));
271 }
272}