miden_protocol/account/code/
mod.rs1use alloc::sync::Arc;
2use alloc::vec::Vec;
3
4use miden_core::mast::MastForest;
5use miden_core::prettier::PrettyPrint;
6use miden_mast_package::debug_info::PackageDebugInfo;
7use miden_processor::LoadedMastForest;
8
9use super::{
10 AccountError,
11 ByteReader,
12 ByteWriter,
13 Deserializable,
14 DeserializationError,
15 Felt,
16 Hasher,
17 Serializable,
18};
19use crate::Word;
20use crate::account::{AccountCodeInterface, AccountComponent, AccountId};
21use crate::package::{loaded_mast_forest, package_debug_info};
22
23pub mod procedure;
24use procedure::{AccountProcedureRoot, PrintableProcedure};
25
26#[derive(Debug, Clone)]
46pub struct AccountCode {
47 mast: Arc<MastForest>,
48 procedures: Vec<AccountProcedureRoot>,
49 commitment: Word,
50 package_debug_info: Option<Arc<PackageDebugInfo>>,
51}
52
53impl AccountCode {
54 pub const MIN_NUM_PROCEDURES: usize = 2;
59
60 pub const MAX_NUM_PROCEDURES: usize = 256;
62
63 pub fn from_parts(mast: Arc<MastForest>, procedures: Vec<AccountProcedureRoot>) -> Self {
74 assert!(procedures.len() >= Self::MIN_NUM_PROCEDURES, "not enough account procedures");
75 assert!(procedures.len() <= Self::MAX_NUM_PROCEDURES, "too many account procedures");
76
77 Self {
78 commitment: build_procedure_commitment(&procedures),
79 procedures,
80 mast,
81 package_debug_info: None,
82 }
83 }
84
85 #[cfg(any(feature = "testing", test))]
89 pub fn from_components(components: &[AccountComponent]) -> Result<Self, AccountError> {
90 Self::from_components_unchecked(components)
91 }
92
93 pub(super) fn from_components_unchecked(
111 components: &[AccountComponent],
112 ) -> Result<Self, AccountError> {
113 let (merged_mast_forest, root_map) =
114 MastForest::merge(components.iter().map(|component| component.mast_forest()))
115 .map_err(AccountError::AccountComponentMastForestMergeError)?;
116 let package_debug_info = merge_component_debug_info(components, &root_map)?;
117
118 let mut builder = AccountProcedureBuilder::new();
119 let mut components_iter = components.iter();
120
121 let first_component =
122 components_iter.next().ok_or(AccountError::AccountCodeNoAuthComponent)?;
123 builder.add_auth_component(first_component)?;
124
125 for component in components_iter {
126 builder.add_component(component)?;
127 }
128
129 let procedures = builder.build()?;
130
131 Ok(Self {
132 commitment: build_procedure_commitment(&procedures),
133 procedures,
134 mast: Arc::new(merged_mast_forest),
135 package_debug_info,
136 })
137 }
138
139 pub fn commitment(&self) -> Word {
144 self.commitment
145 }
146
147 pub fn mast(&self) -> Arc<MastForest> {
149 self.mast.clone()
150 }
151
152 pub fn loaded_mast_forest(&self) -> LoadedMastForest {
154 loaded_mast_forest(self.mast.clone(), self.package_debug_info.clone())
155 }
156
157 pub fn procedures(&self) -> &[AccountProcedureRoot] {
159 &self.procedures
160 }
161
162 pub fn procedure_roots(&self) -> impl Iterator<Item = Word> + '_ {
164 self.procedures().iter().map(|procedure| *procedure.mast_root())
165 }
166
167 pub fn num_procedures(&self) -> usize {
169 self.procedures.len()
170 }
171
172 pub fn has_procedure(&self, mast_root: Word) -> bool {
174 self.procedures.iter().any(|procedure| procedure.mast_root() == &mast_root)
175 }
176
177 pub fn get(&self, index: usize) -> Option<&AccountProcedureRoot> {
179 self.procedures.get(index)
180 }
181
182 pub fn to_elements(&self) -> Vec<Felt> {
192 procedures_as_elements(self.procedures())
193 }
194
195 pub fn interface(&self, account_id: AccountId) -> AccountCodeInterface {
198 AccountCodeInterface::new(account_id, self.procedures.iter().copied().collect())
199 .expect("account code procedure count is enforced by AccountCode invariants")
200 }
201
202 pub fn printable_procedures(&self) -> impl Iterator<Item = PrintableProcedure> {
209 self.procedures()
210 .iter()
211 .filter_map(move |proc_root| self.printable_procedure(proc_root).ok())
212 }
213
214 fn printable_procedure(
222 &self,
223 proc_root: &AccountProcedureRoot,
224 ) -> Result<PrintableProcedure, AccountError> {
225 let node_id = self
226 .mast
227 .find_procedure_root(*proc_root.mast_root())
228 .expect("procedure root should be present in the mast forest");
229
230 Ok(PrintableProcedure::new(self.mast.clone(), *proc_root, node_id))
231 }
232}
233
234impl PartialEq for AccountCode {
238 fn eq(&self, other: &Self) -> bool {
239 self.mast == other.mast && self.procedures == other.procedures
241 }
242}
243
244impl Ord for AccountCode {
245 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
246 self.commitment.cmp(&other.commitment)
247 }
248}
249
250impl PartialOrd for AccountCode {
251 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
252 Some(self.cmp(other))
253 }
254}
255
256impl Eq for AccountCode {}
257
258impl Serializable for AccountCode {
262 fn write_into<W: ByteWriter>(&self, target: &mut W) {
263 self.mast.write_into(target);
264 target.write_u8((self.procedures.len() - 1) as u8);
267 target.write_many(self.procedures());
268 }
269
270 fn get_size_hint(&self) -> usize {
271 let mut mast_forest_target = Vec::new();
273 self.mast.write_into(&mut mast_forest_target);
274
275 let u8_size = 0u8.get_size_hint();
277 let mut size = u8_size + mast_forest_target.len();
278
279 for procedure in self.procedures() {
280 size += procedure.get_size_hint();
281 }
282
283 size
284 }
285}
286
287impl Deserializable for AccountCode {
288 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
289 let mast = Arc::new(MastForest::read_from(source)?);
290 let num_procedures = (source.read_u8()? as usize) + 1;
291
292 if num_procedures < Self::MIN_NUM_PROCEDURES {
295 return Err(DeserializationError::InvalidValue(format!(
296 "account code must contain at least {} procedures, but has only {num_procedures} procedures",
297 Self::MIN_NUM_PROCEDURES
298 )));
299 }
300
301 let procedures = source
302 .read_many_iter(num_procedures)?
303 .collect::<Result<Vec<AccountProcedureRoot>, _>>()?;
304
305 for procedure in procedures.iter() {
307 if mast.find_procedure_root(procedure.as_word()).is_none() {
308 return Err(DeserializationError::InvalidValue(format!(
309 "procedure with root {} is missing from account code's MAST forest",
310 procedure.as_word()
311 )));
312 }
313 }
314
315 Ok(Self::from_parts(mast, procedures))
316 }
317}
318
319impl PrettyPrint for AccountCode {
323 fn render(&self) -> miden_core::prettier::Document {
324 use miden_core::prettier::*;
325 let mut partial = Document::Empty;
326 let len_procedures = self.num_procedures();
327
328 for (index, printable_procedure) in self.printable_procedures().enumerate() {
329 partial += indent(
330 0,
331 indent(
332 4,
333 text(format!("proc {}", printable_procedure.mast_root()))
334 + nl()
335 + printable_procedure.render(),
336 ) + nl()
337 + const_text("end"),
338 );
339 if index < len_procedures - 1 {
340 partial += nl();
341 }
342 }
343 partial
344 }
345}
346
347struct AccountProcedureBuilder {
354 procedures: Vec<AccountProcedureRoot>,
355}
356
357impl AccountProcedureBuilder {
358 fn new() -> Self {
359 Self { procedures: Vec::new() }
360 }
361
362 fn add_auth_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
364 let mut auth_proc_count = 0;
365
366 for (proc_root, is_auth) in component.procedures() {
367 self.add_procedure(proc_root);
368
369 if is_auth {
370 let auth_proc_idx = self.procedures.len() - 1;
371 self.procedures.swap(0, auth_proc_idx);
372 auth_proc_count += 1;
373 }
374 }
375
376 if auth_proc_count == 0 {
377 return Err(AccountError::AccountCodeNoAuthComponent);
378 } else if auth_proc_count > 1 {
379 return Err(AccountError::AccountComponentMultipleAuthProcedures);
380 }
381
382 Ok(())
383 }
384
385 fn add_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
386 for (proc_root, is_auth) in component.procedures() {
387 if is_auth {
388 return Err(AccountError::AccountCodeMultipleAuthComponents);
389 }
390 self.add_procedure(proc_root);
391 }
392
393 Ok(())
394 }
395
396 fn add_procedure(&mut self, proc_root: AccountProcedureRoot) {
397 if !self.procedures.contains(&proc_root) {
400 self.procedures.push(proc_root);
401 }
402 }
403
404 fn build(self) -> Result<Vec<AccountProcedureRoot>, AccountError> {
405 if self.procedures.len() < AccountCode::MIN_NUM_PROCEDURES {
406 Err(AccountError::AccountCodeNoProcedures)
407 } else if self.procedures.len() > AccountCode::MAX_NUM_PROCEDURES {
408 Err(AccountError::AccountCodeTooManyProcedures(self.procedures.len()))
409 } else {
410 Ok(self.procedures)
411 }
412 }
413}
414
415fn build_procedure_commitment(procedures: &[AccountProcedureRoot]) -> Word {
420 let elements = procedures_as_elements(procedures);
421 Hasher::hash_elements(&elements)
422}
423
424fn merge_component_debug_info(
425 components: &[AccountComponent],
426 root_map: &miden_core::mast::MastForestRootMap,
427) -> Result<Option<Arc<PackageDebugInfo>>, AccountError> {
428 let component_debug_info = components
429 .iter()
430 .enumerate()
431 .filter_map(|(idx, component)| {
432 package_debug_info(component.component_code().as_library()).map(|debug| (idx, debug))
433 })
434 .collect::<Vec<_>>();
435
436 if component_debug_info.is_empty() {
437 return Ok(None);
438 }
439
440 let debug_info = PackageDebugInfo::merge_source_debug(
441 component_debug_info.iter().map(|(idx, debug)| (*idx, debug.as_ref())),
442 root_map,
443 )
444 .map_err(|err| {
445 AccountError::other_with_source("failed to merge account component debug info", err)
446 })?;
447
448 Ok(Some(Arc::new(debug_info)))
449}
450
451fn procedures_as_elements(procedures: &[AccountProcedureRoot]) -> Vec<Felt> {
453 procedures.iter().flat_map(AccountProcedureRoot::as_elements).copied().collect()
454}
455
456#[cfg(test)]
460mod tests {
461 use assert_matches::assert_matches;
462
463 use super::{AccountCode, Deserializable, Serializable};
464 use crate::account::AccountComponent;
465 use crate::account::code::build_procedure_commitment;
466 use crate::account::component::AccountComponentMetadata;
467 use crate::errors::AccountError;
468 use crate::testing::account_code::CODE;
469 use crate::testing::assembler::assemble_test_library;
470 use crate::testing::noop_auth_component::NoopAuthComponent;
471
472 #[test]
473 fn test_serde_account_code() {
474 let code = AccountCode::mock();
475 let serialized = code.to_bytes();
476 let deserialized = AccountCode::read_from_bytes(&serialized).unwrap();
477 assert_eq!(deserialized, code)
478 }
479
480 #[test]
481 fn test_account_code_procedure_root() {
482 let code = AccountCode::mock();
483 let procedure_root = build_procedure_commitment(code.procedures());
484 assert_eq!(procedure_root, code.commitment())
485 }
486
487 #[test]
488 fn test_account_code_only_auth_component() {
489 let err = AccountCode::from_components(&[NoopAuthComponent.into()]).unwrap_err();
490
491 assert_matches!(err, AccountError::AccountCodeNoProcedures);
492 }
493
494 #[test]
495 fn test_account_code_no_auth_component() {
496 let library =
497 assemble_test_library("test-account-code-no-auth", "test::account_code", CODE);
498 let metadata = AccountComponentMetadata::new("test::no_auth");
499 let component = AccountComponent::new(library, vec![], metadata).unwrap();
500
501 let err = AccountCode::from_components(&[component]).unwrap_err();
502
503 assert_matches!(err, AccountError::AccountCodeNoAuthComponent);
504 }
505
506 #[test]
507 fn test_account_code_preserves_component_debug_info() {
508 let library =
509 assemble_test_library("test-account-code-debug-info", "test::account_code", CODE);
510 let metadata = AccountComponentMetadata::new("test::debug_info");
511 let component = AccountComponent::new(library, vec![], metadata).unwrap();
512
513 let code = AccountCode::from_components(&[NoopAuthComponent.into(), component]).unwrap();
514
515 assert!(code.loaded_mast_forest().package_debug_info().unwrap().is_some());
516 }
517
518 #[test]
519 fn test_account_code_multiple_auth_components() {
520 let err =
521 AccountCode::from_components(&[NoopAuthComponent.into(), NoopAuthComponent.into()])
522 .unwrap_err();
523
524 assert_matches!(err, AccountError::AccountCodeMultipleAuthComponents);
525 }
526
527 #[test]
528 fn test_account_component_multiple_auth_procedures() {
529 let code_with_multiple_auth = "
530 @auth_script
531 pub proc auth_basic
532 push.1 drop
533 end
534
535 @auth_script
536 pub proc auth_secondary
537 push.0 drop
538 end
539 ";
540
541 let library = assemble_test_library(
542 "test-account-code-multiple-auth",
543 "test::account_code_multiple_auth",
544 code_with_multiple_auth,
545 );
546 let metadata = AccountComponentMetadata::new("test::multiple_auth");
547 let component = AccountComponent::new(library, vec![], metadata).unwrap();
548
549 let err = AccountCode::from_components(&[component]).unwrap_err();
550
551 assert_matches!(err, AccountError::AccountComponentMultipleAuthProcedures);
552 }
553}