miden_protocol/account/code/
mod.rs1use alloc::collections::BTreeSet;
2use alloc::string::ToString;
3use alloc::sync::Arc;
4use alloc::vec::Vec;
5
6use miden_core::mast::MastForest;
7use miden_core::prettier::PrettyPrint;
8use miden_mast_package::debug_info::PackageDebugInfo;
9use miden_processor::LoadedMastForest;
10
11use super::{
12 AccountError,
13 ByteReader,
14 ByteWriter,
15 Deserializable,
16 DeserializationError,
17 Felt,
18 Hasher,
19 Serializable,
20};
21use crate::Word;
22use crate::account::{AccountCodeInterface, AccountComponent, AccountId};
23use crate::package::{loaded_mast_forest, package_debug_info};
24
25pub mod procedure;
26use procedure::{AccountProcedureRoot, PrintableProcedure};
27
28#[derive(Debug, Clone)]
48pub struct AccountCode {
49 mast: Arc<MastForest>,
50 procedures: Vec<AccountProcedureRoot>,
51 commitment: Word,
52 package_debug_info: Option<Arc<PackageDebugInfo>>,
53}
54
55impl AccountCode {
56 pub const MIN_NUM_PROCEDURES: usize = 2;
61
62 pub const MAX_NUM_PROCEDURES: usize = 256;
64
65 pub fn from_parts(
78 mast: Arc<MastForest>,
79 procedures: Vec<AccountProcedureRoot>,
80 ) -> Result<Self, AccountError> {
81 if procedures.len() < Self::MIN_NUM_PROCEDURES {
82 return Err(AccountError::AccountCodeNoProcedures);
83 }
84 if procedures.len() > Self::MAX_NUM_PROCEDURES {
85 return Err(AccountError::AccountCodeTooManyProcedures(procedures.len()));
86 }
87
88 let mut unique_roots = BTreeSet::new();
89 for procedure in &procedures {
90 if !unique_roots.insert(procedure.as_word()) {
91 return Err(AccountError::AccountCodeDuplicateProcedureRoot(procedure.as_word()));
92 }
93 }
94
95 for procedure in procedures.iter() {
97 if mast.find_procedure_root(procedure.as_word()).is_none() {
98 return Err(AccountError::AccountCodeProcedureNotInMastForest(*procedure));
99 }
100 }
101
102 Ok(Self {
103 commitment: build_procedure_commitment(&procedures),
104 procedures,
105 mast,
106 package_debug_info: None,
107 })
108 }
109
110 #[cfg(any(feature = "testing", test))]
114 pub fn from_components(components: &[AccountComponent]) -> Result<Self, AccountError> {
115 Self::from_components_unchecked(components)
116 }
117
118 pub(super) fn from_components_unchecked(
136 components: &[AccountComponent],
137 ) -> Result<Self, AccountError> {
138 let (merged_mast_forest, root_map) =
139 MastForest::merge(components.iter().map(|component| component.mast_forest()))
140 .map_err(AccountError::AccountComponentMastForestMergeError)?;
141 let package_debug_info = merge_component_debug_info(components, &root_map)?;
142
143 let mut builder = AccountProcedureBuilder::new();
144 let mut num_auth_components = 0;
145
146 for component in components {
147 if component.is_auth_component() {
148 num_auth_components += 1;
149 builder.add_auth_component(component)?
150 } else {
151 builder.add_component(component)?;
152 }
153 }
154
155 if num_auth_components == 0 {
156 return Err(AccountError::AccountCodeNoAuthComponent);
157 } else if num_auth_components > 1 {
158 return Err(AccountError::AccountCodeMultipleAuthComponents);
159 }
160
161 let procedures = builder.build()?;
162
163 Ok(Self {
164 commitment: build_procedure_commitment(&procedures),
165 procedures,
166 mast: Arc::new(merged_mast_forest),
167 package_debug_info,
168 })
169 }
170
171 pub fn commitment(&self) -> Word {
176 self.commitment
177 }
178
179 pub fn mast(&self) -> Arc<MastForest> {
181 self.mast.clone()
182 }
183
184 pub fn loaded_mast_forest(&self) -> LoadedMastForest {
186 loaded_mast_forest(self.mast.clone(), self.package_debug_info.clone())
187 }
188
189 pub fn procedures(&self) -> &[AccountProcedureRoot] {
191 &self.procedures
192 }
193
194 pub fn procedure_roots(&self) -> impl Iterator<Item = Word> + '_ {
196 self.procedures().iter().map(|procedure| *procedure.mast_root())
197 }
198
199 pub fn num_procedures(&self) -> usize {
201 self.procedures.len()
202 }
203
204 pub fn has_procedure(&self, mast_root: Word) -> bool {
206 self.procedures.iter().any(|procedure| procedure.mast_root() == &mast_root)
207 }
208
209 pub fn get(&self, index: usize) -> Option<&AccountProcedureRoot> {
211 self.procedures.get(index)
212 }
213
214 pub fn to_elements(&self) -> Vec<Felt> {
224 procedures_as_elements(self.procedures())
225 }
226
227 pub fn interface(&self, account_id: AccountId) -> AccountCodeInterface {
230 AccountCodeInterface::new(account_id, self.procedures.iter().copied().collect())
231 .expect("account code procedure count is enforced by AccountCode invariants")
232 }
233
234 pub fn printable_procedures(&self) -> impl Iterator<Item = PrintableProcedure> {
241 self.procedures()
242 .iter()
243 .filter_map(move |proc_root| self.printable_procedure(proc_root).ok())
244 }
245
246 fn printable_procedure(
254 &self,
255 proc_root: &AccountProcedureRoot,
256 ) -> Result<PrintableProcedure, AccountError> {
257 let node_id = self
258 .mast
259 .find_procedure_root(*proc_root.mast_root())
260 .expect("procedure root should be present in the mast forest");
261
262 Ok(PrintableProcedure::new(self.mast.clone(), *proc_root, node_id))
263 }
264}
265
266impl PartialEq for AccountCode {
270 fn eq(&self, other: &Self) -> bool {
271 self.mast == other.mast && self.procedures == other.procedures
273 }
274}
275
276impl Ord for AccountCode {
277 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
278 self.commitment.cmp(&other.commitment)
279 }
280}
281
282impl PartialOrd for AccountCode {
283 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
284 Some(self.cmp(other))
285 }
286}
287
288impl Eq for AccountCode {}
289
290impl Serializable for AccountCode {
294 fn write_into<W: ByteWriter>(&self, target: &mut W) {
295 self.mast.write_into(target);
296 target.write_u8((self.procedures.len() - 1) as u8);
299 target.write_many(self.procedures());
300 }
301
302 fn get_size_hint(&self) -> usize {
303 let mut mast_forest_target = Vec::new();
305 self.mast.write_into(&mut mast_forest_target);
306
307 let u8_size = 0u8.get_size_hint();
309 let mut size = u8_size + mast_forest_target.len();
310
311 for procedure in self.procedures() {
312 size += procedure.get_size_hint();
313 }
314
315 size
316 }
317}
318
319impl Deserializable for AccountCode {
320 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
321 let mast = Arc::new(MastForest::read_from(source)?);
322 let num_procedures = (source.read_u8()? as usize) + 1;
323
324 let procedures = source
325 .read_many_iter(num_procedures)?
326 .collect::<Result<Vec<AccountProcedureRoot>, _>>()?;
327
328 Self::from_parts(mast, procedures)
329 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
330 }
331}
332
333impl PrettyPrint for AccountCode {
337 fn render(&self) -> miden_core::prettier::Document {
338 use miden_core::prettier::*;
339 let mut partial = Document::Empty;
340 let len_procedures = self.num_procedures();
341
342 for (index, printable_procedure) in self.printable_procedures().enumerate() {
343 partial += indent(
344 0,
345 indent(
346 4,
347 text(format!("proc {}", printable_procedure.mast_root()))
348 + nl()
349 + printable_procedure.render(),
350 ) + nl()
351 + const_text("end"),
352 );
353 if index < len_procedures - 1 {
354 partial += nl();
355 }
356 }
357 partial
358 }
359}
360
361struct AccountProcedureBuilder {
368 procedures: Vec<AccountProcedureRoot>,
369}
370
371impl AccountProcedureBuilder {
372 fn new() -> Self {
373 Self { procedures: Vec::new() }
374 }
375
376 fn add_auth_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
378 let mut auth_proc_count = 0;
379
380 for (proc_root, is_auth) in component.procedures() {
381 self.add_procedure(proc_root);
382
383 if is_auth {
384 let auth_proc_idx = self.procedures.len() - 1;
385 self.procedures.swap(0, auth_proc_idx);
386 auth_proc_count += 1;
387 }
388 }
389
390 if auth_proc_count == 0 {
391 return Err(AccountError::AccountCodeNoAuthComponent);
392 } else if auth_proc_count > 1 {
393 return Err(AccountError::AccountComponentMultipleAuthProcedures);
394 }
395
396 Ok(())
397 }
398
399 fn add_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
400 for (proc_root, is_auth) in component.procedures() {
401 if is_auth {
402 return Err(AccountError::AccountCodeMultipleAuthComponents);
403 }
404 self.add_procedure(proc_root);
405 }
406
407 Ok(())
408 }
409
410 fn add_procedure(&mut self, proc_root: AccountProcedureRoot) {
411 if !self.procedures.contains(&proc_root) {
414 self.procedures.push(proc_root);
415 }
416 }
417
418 fn build(self) -> Result<Vec<AccountProcedureRoot>, AccountError> {
419 if self.procedures.len() < AccountCode::MIN_NUM_PROCEDURES {
420 Err(AccountError::AccountCodeNoProcedures)
421 } else if self.procedures.len() > AccountCode::MAX_NUM_PROCEDURES {
422 Err(AccountError::AccountCodeTooManyProcedures(self.procedures.len()))
423 } else {
424 Ok(self.procedures)
425 }
426 }
427}
428
429fn build_procedure_commitment(procedures: &[AccountProcedureRoot]) -> Word {
434 let elements = procedures_as_elements(procedures);
435 Hasher::hash_elements(&elements)
436}
437
438fn merge_component_debug_info(
439 components: &[AccountComponent],
440 root_map: &miden_core::mast::MastForestRootMap,
441) -> Result<Option<Arc<PackageDebugInfo>>, AccountError> {
442 let component_debug_info = components
443 .iter()
444 .enumerate()
445 .filter_map(|(idx, component)| {
446 package_debug_info(component.component_code().as_package()).map(|debug| (idx, debug))
447 })
448 .collect::<Vec<_>>();
449
450 if component_debug_info.is_empty() {
451 return Ok(None);
452 }
453
454 let debug_info = PackageDebugInfo::merge_source_debug(
455 component_debug_info.iter().map(|(idx, debug)| (*idx, debug.as_ref())),
456 root_map,
457 )
458 .map_err(|err| {
459 AccountError::other_with_source("failed to merge account component debug info", err)
460 })?;
461
462 Ok(Some(Arc::new(debug_info)))
463}
464
465fn procedures_as_elements(procedures: &[AccountProcedureRoot]) -> Vec<Felt> {
467 procedures.iter().flat_map(AccountProcedureRoot::as_elements).copied().collect()
468}
469
470#[cfg(test)]
474mod tests {
475 use alloc::vec::Vec;
476
477 use assert_matches::assert_matches;
478
479 use super::{AccountCode, ByteWriter, Deserializable, DeserializationError, Serializable};
480 use crate::Word;
481 use crate::account::code::build_procedure_commitment;
482 use crate::account::component::AccountComponentMetadata;
483 use crate::account::{AccountComponent, AccountProcedureRoot};
484 use crate::errors::AccountError;
485 use crate::testing::account_code::CODE;
486 use crate::testing::assembler::assemble_test_package;
487 use crate::testing::noop_auth_component::NoopAuthComponent;
488
489 #[test]
490 fn test_serde_account_code() {
491 let code = AccountCode::mock();
492 let serialized = code.to_bytes();
493 let deserialized = AccountCode::read_from_bytes(&serialized).unwrap();
494 assert_eq!(deserialized, code)
495 }
496
497 #[test]
498 fn test_account_code_procedure_root() {
499 let code = AccountCode::mock();
500 let procedure_root = build_procedure_commitment(code.procedures());
501 assert_eq!(procedure_root, code.commitment())
502 }
503
504 #[test]
505 fn test_account_code_only_auth_component() {
506 let err = AccountCode::from_components(&[NoopAuthComponent.into()]).unwrap_err();
507
508 assert_matches!(err, AccountError::AccountCodeNoProcedures);
509 }
510
511 #[test]
512 fn test_account_code_no_auth_component() {
513 let package =
514 assemble_test_package("test-account-code-no-auth", "test::account_code", CODE);
515 let metadata = AccountComponentMetadata::new("test::no_auth");
516 let component = AccountComponent::new(package, vec![], metadata).unwrap();
517
518 let err = AccountCode::from_components(&[component]).unwrap_err();
519
520 assert_matches!(err, AccountError::AccountCodeNoAuthComponent);
521 }
522
523 #[test]
524 fn test_account_code_preserves_component_debug_info() {
525 let package =
526 assemble_test_package("test-account-code-debug-info", "test::account_code", CODE);
527 let metadata = AccountComponentMetadata::new("test::debug_info");
528 let component = AccountComponent::new(package, vec![], metadata).unwrap();
529
530 let code = AccountCode::from_components(&[NoopAuthComponent.into(), component]).unwrap();
531
532 assert!(code.loaded_mast_forest().package_debug_info().unwrap().is_some());
533 }
534
535 #[test]
536 fn test_account_code_multiple_auth_components() {
537 let err =
538 AccountCode::from_components(&[NoopAuthComponent.into(), NoopAuthComponent.into()])
539 .unwrap_err();
540
541 assert_matches!(err, AccountError::AccountCodeMultipleAuthComponents);
542 }
543
544 #[test]
545 fn test_account_component_multiple_auth_procedures() {
546 let code_with_multiple_auth = "
547 @auth_script
548 pub proc auth_basic
549 push.1 drop
550 end
551
552 @auth_script
553 pub proc auth_secondary
554 push.0 drop
555 end
556 ";
557
558 let package = assemble_test_package(
559 "test-account-code-multiple-auth",
560 "test::account_code_multiple_auth",
561 code_with_multiple_auth,
562 );
563 let metadata = AccountComponentMetadata::new("test::multiple_auth");
564 let component = AccountComponent::new(package, vec![], metadata).unwrap();
565
566 let err = AccountCode::from_components(&[component]).unwrap_err();
567
568 assert_matches!(err, AccountError::AccountComponentMultipleAuthProcedures);
569 }
570
571 #[test]
572 fn test_account_code_from_parts_rejects_duplicate_roots() {
573 let code = AccountCode::mock();
574 let procedures = code.procedures();
575
576 let duplicated = vec![procedures[0], procedures[1], procedures[1]];
578 let err = AccountCode::from_parts(code.mast(), duplicated).unwrap_err();
579
580 assert_matches!(
581 err,
582 AccountError::AccountCodeDuplicateProcedureRoot(root) if root == procedures[1].as_word()
583 );
584 }
585
586 #[test]
587 fn test_account_code_from_parts_rejects_missing_root() {
588 let code = AccountCode::mock();
589 let procedures = code.procedures();
590 let non_existent_root = AccountProcedureRoot::from_raw(Word::from([1, 2, 3, 4u32]));
591
592 let procedures = vec![procedures[0], non_existent_root];
594 let err = AccountCode::from_parts(code.mast(), procedures).unwrap_err();
595
596 assert_matches!(
597 err,
598 AccountError::AccountCodeProcedureNotInMastForest(root) if root == non_existent_root
599 );
600 }
601
602 #[test]
603 fn test_account_code_deserialization_rejects_duplicate_roots() {
604 let code = AccountCode::mock();
605 let procedures = code.procedures();
606
607 let mut bytes = Vec::new();
608 code.mast().write_into(&mut bytes);
609 bytes.write_u8(3 - 1); procedures[0].write_into(&mut bytes);
611 procedures[1].write_into(&mut bytes);
612 procedures[1].write_into(&mut bytes);
613
614 let err = AccountCode::read_from_bytes(&bytes).unwrap_err();
615
616 assert_matches!(
617 err,
618 DeserializationError::InvalidValue(msg) if msg.contains("duplicate procedure with root")
619 );
620 }
621}