miden_objects/account/code/
mod.rs1use alloc::collections::BTreeSet;
2use alloc::sync::Arc;
3use alloc::vec::Vec;
4
5use miden_core::mast::MastForest;
6use miden_core::prettier::PrettyPrint;
7
8use super::{
9 AccountError,
10 ByteReader,
11 ByteWriter,
12 Deserializable,
13 DeserializationError,
14 Felt,
15 Hasher,
16 Serializable,
17};
18use crate::Word;
19use crate::account::{AccountComponent, AccountType};
20
21pub mod procedure;
22use procedure::{AccountProcedureInfo, PrintableProcedure};
23
24#[derive(Debug, Clone)]
42pub struct AccountCode {
43 mast: Arc<MastForest>,
44 procedures: Vec<AccountProcedureInfo>,
45 commitment: Word,
46}
47
48impl AccountCode {
49 pub const MAX_NUM_PROCEDURES: usize = 256;
51 pub const MIN_NUM_PROCEDURES: usize = 2;
53
54 #[cfg(any(feature = "testing", test))]
61 pub fn from_components(
62 components: &[AccountComponent],
63 account_type: AccountType,
64 ) -> Result<Self, AccountError> {
65 super::validate_components_support_account_type(components, account_type)?;
66 Self::from_components_unchecked(components, account_type)
67 }
68
69 pub(super) fn from_components_unchecked(
87 components: &[AccountComponent],
88 account_type: AccountType,
89 ) -> Result<Self, AccountError> {
90 let (merged_mast_forest, _) =
91 MastForest::merge(components.iter().map(|component| component.mast_forest()))
92 .map_err(AccountError::AccountComponentMastForestMergeError)?;
93
94 let mut builder = ProcedureInfoBuilder::new(account_type);
95 let mut components_iter = components.iter();
96
97 let first_component =
98 components_iter.next().ok_or(AccountError::AccountCodeNoAuthComponent)?;
99 builder.add_auth_component(first_component)?;
100
101 for component in components_iter {
102 builder.add_component(component)?;
103 }
104
105 let procedures = builder.build()?;
106
107 Ok(Self {
108 commitment: build_procedure_commitment(&procedures),
109 procedures,
110 mast: Arc::new(merged_mast_forest),
111 })
112 }
113
114 pub fn from_bytes(bytes: &[u8]) -> Result<Self, AccountError> {
119 Self::read_from_bytes(bytes).map_err(AccountError::AccountCodeDeserializationError)
120 }
121
122 pub fn from_parts(mast: Arc<MastForest>, procedures: Vec<AccountProcedureInfo>) -> Self {
131 assert!(!procedures.is_empty(), "no account procedures");
132 assert!(procedures.len() <= Self::MAX_NUM_PROCEDURES, "too many account procedures");
133
134 Self {
135 commitment: build_procedure_commitment(&procedures),
136 procedures,
137 mast,
138 }
139 }
140
141 pub fn commitment(&self) -> Word {
146 self.commitment
147 }
148
149 pub fn mast(&self) -> Arc<MastForest> {
151 self.mast.clone()
152 }
153
154 pub fn procedures(&self) -> &[AccountProcedureInfo] {
156 &self.procedures
157 }
158
159 pub fn procedure_roots(&self) -> impl Iterator<Item = Word> + '_ {
161 self.procedures().iter().map(|procedure| *procedure.mast_root())
162 }
163
164 pub fn num_procedures(&self) -> usize {
166 self.procedures.len()
167 }
168
169 pub fn has_procedure(&self, mast_root: Word) -> bool {
171 self.procedures.iter().any(|procedure| procedure.mast_root() == &mast_root)
172 }
173
174 pub fn get_procedure_by_index(&self, index: usize) -> &AccountProcedureInfo {
179 &self.procedures[index]
180 }
181
182 pub fn get_procedure_index_by_root(&self, root: Word) -> Option<usize> {
185 self.procedures
186 .iter()
187 .map(|procedure| procedure.mast_root())
188 .position(|r| r == &root)
189 }
190
191 pub fn as_elements(&self) -> Vec<Felt> {
199 procedures_as_elements(self.procedures())
200 }
201
202 pub fn printable_procedures(&self) -> impl Iterator<Item = PrintableProcedure> {
208 self.procedures()
209 .iter()
210 .filter_map(move |procedure_info| self.printable_procedure(procedure_info).ok())
211 }
212
213 fn printable_procedure(
221 &self,
222 proc_info: &AccountProcedureInfo,
223 ) -> Result<PrintableProcedure, AccountError> {
224 let node_id = self
225 .mast
226 .find_procedure_root(*proc_info.mast_root())
227 .expect("procedure root should be present in the mast forest");
228
229 Ok(PrintableProcedure::new(self.mast.clone(), *proc_info, node_id))
230 }
231}
232
233impl PartialEq for AccountCode {
237 fn eq(&self, other: &Self) -> bool {
238 self.mast == other.mast && self.procedures == other.procedures
240 }
241}
242
243impl Ord for AccountCode {
244 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
245 self.commitment.cmp(&other.commitment)
246 }
247}
248
249impl PartialOrd for AccountCode {
250 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
251 Some(self.cmp(other))
252 }
253}
254
255impl Eq for AccountCode {}
256
257impl Serializable for AccountCode {
261 fn write_into<W: ByteWriter>(&self, target: &mut W) {
262 self.mast.write_into(target);
263 target.write_u8((self.procedures.len() - 1) as u8);
266 target.write_many(self.procedures());
267 }
268
269 fn get_size_hint(&self) -> usize {
270 let mut mast_forest_target = Vec::new();
272 self.mast.write_into(&mut mast_forest_target);
273
274 let u8_size = 0u8.get_size_hint();
276 let mut size = u8_size + mast_forest_target.len();
277
278 for procedure in self.procedures() {
279 size += procedure.get_size_hint();
280 }
281
282 size
283 }
284}
285
286impl Deserializable for AccountCode {
287 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
288 let module = Arc::new(MastForest::read_from(source)?);
289 let num_procedures = (source.read_u8()? as usize) + 1;
290 let procedures = source.read_many::<AccountProcedureInfo>(num_procedures)?;
291
292 Ok(Self::from_parts(module, procedures))
293 }
294}
295
296impl PrettyPrint for AccountCode {
300 fn render(&self) -> miden_core::prettier::Document {
301 use miden_core::prettier::*;
302 let mut partial = Document::Empty;
303 let len_procedures = self.num_procedures();
304
305 for (index, printable_procedure) in self.printable_procedures().enumerate() {
306 partial += indent(
307 0,
308 indent(
309 4,
310 text(format!("proc.{}", printable_procedure.mast_root()))
311 + nl()
312 + text(format!(
313 "storage.{}.{}",
314 printable_procedure.storage_offset(),
315 printable_procedure.storage_size()
316 ))
317 + nl()
318 + printable_procedure.render(),
319 ) + nl()
320 + const_text("end"),
321 );
322 if index < len_procedures - 1 {
323 partial += nl();
324 }
325 }
326 partial
327 }
328}
329
330struct ProcedureInfoBuilder {
334 procedures: Vec<AccountProcedureInfo>,
335 proc_root_set: BTreeSet<Word>,
336 storage_offset: u8,
337}
338
339impl ProcedureInfoBuilder {
340 fn new(account_type: AccountType) -> Self {
341 let storage_offset = if account_type.is_faucet() { 1 } else { 0 };
342
343 Self {
344 procedures: Vec::new(),
345 proc_root_set: BTreeSet::new(),
346 storage_offset,
347 }
348 }
349
350 fn add_auth_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
351 let mut auth_proc_count = 0;
352
353 for (proc_root, is_auth) in component.get_procedures() {
354 self.add_procedure(proc_root, component.storage_size())?;
355 if is_auth {
356 let auth_proc_idx = self.procedures.len() - 1;
357 self.procedures.swap(0, auth_proc_idx);
358 auth_proc_count += 1;
359 }
360 }
361
362 if auth_proc_count == 0 {
363 return Err(AccountError::AccountCodeNoAuthComponent);
364 } else if auth_proc_count > 1 {
365 return Err(AccountError::AccountComponentMultipleAuthProcedures);
366 }
367
368 self.storage_offset = self.storage_offset.checked_add(component.storage_size()).expect(
369 "account procedure info constructor should return an error if the addition overflows",
370 );
371
372 Ok(())
373 }
374
375 fn add_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
376 for (proc_mast_root, is_auth) in component.get_procedures() {
377 if is_auth {
378 return Err(AccountError::AccountCodeMultipleAuthComponents);
379 }
380 self.add_procedure(proc_mast_root, component.storage_size())?;
381 }
382
383 self.storage_offset = self.storage_offset.checked_add(component.storage_size()).expect(
384 "account procedure info constructor should return an error if the addition overflows",
385 );
386
387 Ok(())
388 }
389
390 fn add_procedure(
391 &mut self,
392 proc_mast_root: Word,
393 component_storage_size: u8,
394 ) -> Result<(), AccountError> {
395 if !self.proc_root_set.insert(proc_mast_root) {
400 return Err(AccountError::AccountComponentDuplicateProcedureRoot(proc_mast_root));
401 }
402
403 let (storage_offset, storage_size) = if component_storage_size == 0 {
405 (0, 0)
406 } else {
407 (self.storage_offset, component_storage_size)
408 };
409
410 self.procedures.push(AccountProcedureInfo::new(
412 proc_mast_root,
413 storage_offset,
414 storage_size,
415 )?);
416
417 Ok(())
418 }
419
420 fn build(self) -> Result<Vec<AccountProcedureInfo>, AccountError> {
421 if self.procedures.len() < AccountCode::MIN_NUM_PROCEDURES {
422 Err(AccountError::AccountCodeNoProcedures)
423 } else if self.procedures.len() > AccountCode::MAX_NUM_PROCEDURES {
424 Err(AccountError::AccountCodeTooManyProcedures(self.procedures.len()))
425 } else {
426 Ok(self.procedures)
427 }
428 }
429}
430
431pub(crate) fn build_procedure_commitment(procedures: &[AccountProcedureInfo]) -> Word {
436 let elements = procedures_as_elements(procedures);
437 Hasher::hash_elements(&elements)
438}
439
440pub(crate) fn procedures_as_elements(procedures: &[AccountProcedureInfo]) -> Vec<Felt> {
442 procedures.iter().flat_map(|procedure| <[Felt; 8]>::from(*procedure)).collect()
443}
444
445#[cfg(test)]
449mod tests {
450
451 use assert_matches::assert_matches;
452 use miden_assembly::Assembler;
453 use miden_core::Word;
454
455 use super::{AccountCode, Deserializable, Serializable};
456 use crate::AccountError;
457 use crate::account::code::build_procedure_commitment;
458 use crate::account::{AccountComponent, AccountType, StorageSlot};
459 use crate::testing::account_code::CODE;
460 use crate::testing::noop_auth_component::NoopAuthComponent;
461
462 #[test]
463 fn test_serde_account_code() {
464 let code = AccountCode::mock();
465 let serialized = code.to_bytes();
466 let deserialized = AccountCode::read_from_bytes(&serialized).unwrap();
467 assert_eq!(deserialized, code)
468 }
469
470 #[test]
471 fn test_account_code_procedure_root() {
472 let code = AccountCode::mock();
473 let procedure_root = build_procedure_commitment(code.procedures());
474 assert_eq!(procedure_root, code.commitment())
475 }
476
477 #[test]
478 fn test_account_code_procedure_offset_out_of_bounds() {
479 let code1 = "export.foo add end";
480 let library1 = Assembler::default().assemble_library([code1]).unwrap();
481 let code2 = "export.bar sub end";
482 let library2 = Assembler::default().assemble_library([code2]).unwrap();
483
484 let auth_component: AccountComponent = NoopAuthComponent.into();
485
486 let component1 =
487 AccountComponent::new(library1, vec![StorageSlot::Value(Word::empty()); 250])
488 .unwrap()
489 .with_supports_all_types();
490 let mut component2 =
491 AccountComponent::new(library2, vec![StorageSlot::Value(Word::empty()); 5])
492 .unwrap()
493 .with_supports_all_types();
494
495 AccountCode::from_components(
497 &[auth_component.clone(), component1.clone(), component2.clone()],
498 AccountType::RegularAccountUpdatableCode,
499 )
500 .unwrap();
501
502 component2.storage_slots.push(StorageSlot::Value(Word::empty()));
504
505 let err = AccountCode::from_components(
506 &[auth_component, component1, component2],
507 AccountType::RegularAccountUpdatableCode,
508 )
509 .unwrap_err();
510
511 assert_matches!(err, AccountError::StorageOffsetPlusSizeOutOfBounds(256))
512 }
513
514 #[test]
515 fn test_account_code_only_auth_component() {
516 let err = AccountCode::from_components(
517 &[NoopAuthComponent.into()],
518 AccountType::RegularAccountUpdatableCode,
519 )
520 .unwrap_err();
521
522 assert_matches!(err, AccountError::AccountCodeNoProcedures);
523 }
524
525 #[test]
526 fn test_account_code_no_auth_component() {
527 let component = AccountComponent::compile(CODE, Assembler::default(), vec![])
528 .unwrap()
529 .with_supports_all_types();
530
531 let err =
532 AccountCode::from_components(&[component], AccountType::RegularAccountUpdatableCode)
533 .unwrap_err();
534
535 assert_matches!(err, AccountError::AccountCodeNoAuthComponent);
536 }
537
538 #[test]
539 fn test_account_code_multiple_auth_components() {
540 let err = AccountCode::from_components(
541 &[NoopAuthComponent.into(), NoopAuthComponent.into()],
542 AccountType::RegularAccountUpdatableCode,
543 )
544 .unwrap_err();
545
546 assert_matches!(err, AccountError::AccountCodeMultipleAuthComponents);
547 }
548
549 #[test]
550 fn test_account_component_multiple_auth_procedures() {
551 use miden_assembly::Assembler;
552
553 let code_with_multiple_auth = "
554 use.miden::account
555
556 export.auth__basic
557 push.1 drop
558 end
559
560 export.auth__secondary
561 push.0 drop
562 end
563 ";
564
565 let component =
566 AccountComponent::compile(code_with_multiple_auth, Assembler::default(), vec![])
567 .unwrap()
568 .with_supports_all_types();
569
570 let err =
571 AccountCode::from_components(&[component], AccountType::RegularAccountUpdatableCode)
572 .unwrap_err();
573
574 assert_matches!(err, AccountError::AccountComponentMultipleAuthProcedures);
575 }
576}