miden_objects/account/code/
mod.rs1use alloc::{collections::BTreeSet, sync::Arc, vec::Vec};
2
3use miden_crypto::hash::rpo::RpoDigest;
4use vm_core::{mast::MastForest, prettier::PrettyPrint};
5
6use super::{
7 AccountError, ByteReader, ByteWriter, Deserializable, DeserializationError, Digest, Felt,
8 Hasher, Serializable,
9};
10use crate::account::{AccountComponent, AccountType};
11
12pub mod procedure;
13use procedure::{AccountProcedureInfo, PrintableProcedure};
14
15#[derive(Debug, Clone)]
33pub struct AccountCode {
34 mast: Arc<MastForest>,
35 procedures: Vec<AccountProcedureInfo>,
36 commitment: Digest,
37}
38
39impl AccountCode {
40 pub const MAX_NUM_PROCEDURES: usize = 256;
42 pub const MIN_NUM_PROCEDURES: usize = 2;
44
45 #[cfg(any(feature = "testing", test))]
52 pub fn from_components(
53 components: &[AccountComponent],
54 account_type: AccountType,
55 ) -> Result<Self, AccountError> {
56 super::validate_components_support_account_type(components, account_type)?;
57 Self::from_components_unchecked(components, account_type)
58 }
59
60 pub(super) fn from_components_unchecked(
78 components: &[AccountComponent],
79 account_type: AccountType,
80 ) -> Result<Self, AccountError> {
81 let (merged_mast_forest, _) =
82 MastForest::merge(components.iter().map(|component| component.mast_forest()))
83 .map_err(AccountError::AccountComponentMastForestMergeError)?;
84
85 let mut builder = ProcedureInfoBuilder::new(account_type);
86 let mut components_iter = components.iter();
87
88 let first_component =
89 components_iter.next().ok_or(AccountError::AccountCodeNoAuthComponent)?;
90 builder.add_auth_component(first_component)?;
91
92 for component in components_iter {
93 builder.add_component(component)?;
94 }
95
96 let procedures = builder.build()?;
97
98 Ok(Self {
99 commitment: build_procedure_commitment(&procedures),
100 procedures,
101 mast: Arc::new(merged_mast_forest),
102 })
103 }
104
105 pub fn from_bytes(bytes: &[u8]) -> Result<Self, AccountError> {
110 Self::read_from_bytes(bytes).map_err(AccountError::AccountCodeDeserializationError)
111 }
112
113 pub fn from_parts(mast: Arc<MastForest>, procedures: Vec<AccountProcedureInfo>) -> Self {
122 assert!(!procedures.is_empty(), "no account procedures");
123 assert!(procedures.len() <= Self::MAX_NUM_PROCEDURES, "too many account procedures");
124
125 Self {
126 commitment: build_procedure_commitment(&procedures),
127 procedures,
128 mast,
129 }
130 }
131
132 pub fn commitment(&self) -> Digest {
137 self.commitment
138 }
139
140 pub fn mast(&self) -> Arc<MastForest> {
142 self.mast.clone()
143 }
144
145 pub fn procedures(&self) -> &[AccountProcedureInfo] {
147 &self.procedures
148 }
149
150 pub fn procedure_roots(&self) -> impl Iterator<Item = Digest> + '_ {
152 self.procedures().iter().map(|procedure| *procedure.mast_root())
153 }
154
155 pub fn num_procedures(&self) -> usize {
157 self.procedures.len()
158 }
159
160 pub fn has_procedure(&self, mast_root: Digest) -> bool {
162 self.procedures.iter().any(|procedure| procedure.mast_root() == &mast_root)
163 }
164
165 pub fn get_procedure_by_index(&self, index: usize) -> &AccountProcedureInfo {
170 &self.procedures[index]
171 }
172
173 pub fn get_procedure_index_by_root(&self, root: Digest) -> Option<usize> {
176 self.procedures
177 .iter()
178 .map(|procedure| procedure.mast_root())
179 .position(|r| r == &root)
180 }
181
182 pub fn as_elements(&self) -> Vec<Felt> {
190 procedures_as_elements(self.procedures())
191 }
192
193 pub fn printable_procedures(&self) -> impl Iterator<Item = PrintableProcedure> {
199 self.procedures()
200 .iter()
201 .filter_map(move |procedure_info| self.printable_procedure(procedure_info).ok())
202 }
203
204 fn printable_procedure(
212 &self,
213 proc_info: &AccountProcedureInfo,
214 ) -> Result<PrintableProcedure, AccountError> {
215 let node_id = self
216 .mast
217 .find_procedure_root(*proc_info.mast_root())
218 .expect("procedure root should be present in the mast forest");
219
220 Ok(PrintableProcedure::new(self.mast.clone(), *proc_info, node_id))
221 }
222}
223
224impl PartialEq for AccountCode {
228 fn eq(&self, other: &Self) -> bool {
229 self.mast == other.mast && self.procedures == other.procedures
231 }
232}
233
234impl Ord for AccountCode {
235 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
236 self.commitment.cmp(&other.commitment)
237 }
238}
239
240impl PartialOrd for AccountCode {
241 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
242 Some(self.cmp(other))
243 }
244}
245
246impl Eq for AccountCode {}
247
248impl Serializable for AccountCode {
252 fn write_into<W: ByteWriter>(&self, target: &mut W) {
253 self.mast.write_into(target);
254 target.write_u8((self.procedures.len() - 1) as u8);
257 target.write_many(self.procedures());
258 }
259
260 fn get_size_hint(&self) -> usize {
261 let mut mast_forest_target = Vec::new();
263 self.mast.write_into(&mut mast_forest_target);
264
265 let u8_size = 0u8.get_size_hint();
267 let mut size = u8_size + mast_forest_target.len();
268
269 for procedure in self.procedures() {
270 size += procedure.get_size_hint();
271 }
272
273 size
274 }
275}
276
277impl Deserializable for AccountCode {
278 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
279 let module = Arc::new(MastForest::read_from(source)?);
280 let num_procedures = (source.read_u8()? as usize) + 1;
281 let procedures = source.read_many::<AccountProcedureInfo>(num_procedures)?;
282
283 Ok(Self::from_parts(module, procedures))
284 }
285}
286
287impl PrettyPrint for AccountCode {
291 fn render(&self) -> vm_core::prettier::Document {
292 use vm_core::prettier::*;
293 let mut partial = Document::Empty;
294 let len_procedures = self.num_procedures();
295
296 for (index, printable_procedure) in self.printable_procedures().enumerate() {
297 partial += indent(
298 0,
299 indent(
300 4,
301 text(format!("proc.{}", printable_procedure.mast_root()))
302 + nl()
303 + text(format!(
304 "storage.{}.{}",
305 printable_procedure.storage_offset(),
306 printable_procedure.storage_size()
307 ))
308 + nl()
309 + printable_procedure.render(),
310 ) + nl()
311 + const_text("end"),
312 );
313 if index < len_procedures - 1 {
314 partial += nl();
315 }
316 }
317 partial
318 }
319}
320
321struct ProcedureInfoBuilder {
325 procedures: Vec<AccountProcedureInfo>,
326 proc_root_set: BTreeSet<RpoDigest>,
327 storage_offset: u8,
328}
329
330impl ProcedureInfoBuilder {
331 fn new(account_type: AccountType) -> Self {
332 let storage_offset = if account_type.is_faucet() { 1 } else { 0 };
333
334 Self {
335 procedures: Vec::new(),
336 proc_root_set: BTreeSet::new(),
337 storage_offset,
338 }
339 }
340
341 fn add_auth_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
342 let mut auth_proc_count = 0;
343
344 for (proc_root, is_auth) in component.get_procedures() {
345 self.add_procedure(proc_root, component.storage_size())?;
346 if is_auth {
347 let auth_proc_idx = self.procedures.len() - 1;
348 self.procedures.swap(0, auth_proc_idx);
349 auth_proc_count += 1;
350 }
351 }
352
353 if auth_proc_count == 0 {
354 return Err(AccountError::AccountCodeNoAuthComponent);
355 } else if auth_proc_count > 1 {
356 return Err(AccountError::AccountComponentMultipleAuthProcedures);
357 }
358
359 self.storage_offset = self.storage_offset.checked_add(component.storage_size()).expect(
360 "account procedure info constructor should return an error if the addition overflows",
361 );
362
363 Ok(())
364 }
365
366 fn add_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
367 for (proc_mast_root, is_auth) in component.get_procedures() {
368 if is_auth {
369 return Err(AccountError::AccountCodeMultipleAuthComponents);
370 }
371 self.add_procedure(proc_mast_root, component.storage_size())?;
372 }
373
374 self.storage_offset = self.storage_offset.checked_add(component.storage_size()).expect(
375 "account procedure info constructor should return an error if the addition overflows",
376 );
377
378 Ok(())
379 }
380
381 fn add_procedure(
382 &mut self,
383 proc_mast_root: RpoDigest,
384 component_storage_size: u8,
385 ) -> Result<(), AccountError> {
386 if !self.proc_root_set.insert(proc_mast_root) {
391 return Err(AccountError::AccountComponentDuplicateProcedureRoot(proc_mast_root));
392 }
393
394 let (storage_offset, storage_size) = if component_storage_size == 0 {
396 (0, 0)
397 } else {
398 (self.storage_offset, component_storage_size)
399 };
400
401 self.procedures.push(AccountProcedureInfo::new(
403 proc_mast_root,
404 storage_offset,
405 storage_size,
406 )?);
407
408 Ok(())
409 }
410
411 fn build(self) -> Result<Vec<AccountProcedureInfo>, AccountError> {
412 if self.procedures.len() < AccountCode::MIN_NUM_PROCEDURES {
413 Err(AccountError::AccountCodeNoProcedures)
414 } else if self.procedures.len() > AccountCode::MAX_NUM_PROCEDURES {
415 Err(AccountError::AccountCodeTooManyProcedures(self.procedures.len()))
416 } else {
417 Ok(self.procedures)
418 }
419 }
420}
421
422pub(crate) fn build_procedure_commitment(procedures: &[AccountProcedureInfo]) -> Digest {
427 let elements = procedures_as_elements(procedures);
428 Hasher::hash_elements(&elements)
429}
430
431pub(crate) fn procedures_as_elements(procedures: &[AccountProcedureInfo]) -> Vec<Felt> {
433 procedures.iter().flat_map(|procedure| <[Felt; 8]>::from(*procedure)).collect()
434}
435
436#[cfg(test)]
440mod tests {
441
442 use assembly::Assembler;
443 use assert_matches::assert_matches;
444 use vm_core::Word;
445
446 use super::{AccountCode, Deserializable, Serializable};
447 use crate::{
448 AccountError,
449 account::{AccountComponent, AccountType, StorageSlot, code::build_procedure_commitment},
450 testing::{account_code::CODE, account_component::NoopAuthComponent},
451 };
452
453 #[test]
454 fn test_serde_account_code() {
455 let code = AccountCode::mock();
456 let serialized = code.to_bytes();
457 let deserialized = AccountCode::read_from_bytes(&serialized).unwrap();
458 assert_eq!(deserialized, code)
459 }
460
461 #[test]
462 fn test_account_code_procedure_root() {
463 let code = AccountCode::mock();
464 let procedure_root = build_procedure_commitment(code.procedures());
465 assert_eq!(procedure_root, code.commitment())
466 }
467
468 #[test]
469 fn test_account_code_procedure_offset_out_of_bounds() {
470 let code1 = "export.foo add end";
471 let library1 = Assembler::default().assemble_library([code1]).unwrap();
472 let code2 = "export.bar sub end";
473 let library2 = Assembler::default().assemble_library([code2]).unwrap();
474
475 let auth_component: AccountComponent =
476 NoopAuthComponent::new(Assembler::default()).unwrap().into();
477
478 let component1 =
479 AccountComponent::new(library1, vec![StorageSlot::Value(Word::default()); 250])
480 .unwrap()
481 .with_supports_all_types();
482 let mut component2 =
483 AccountComponent::new(library2, vec![StorageSlot::Value(Word::default()); 5])
484 .unwrap()
485 .with_supports_all_types();
486
487 AccountCode::from_components(
489 &[auth_component.clone(), component1.clone(), component2.clone()],
490 AccountType::RegularAccountUpdatableCode,
491 )
492 .unwrap();
493
494 component2.storage_slots.push(StorageSlot::Value(Word::default()));
496
497 let err = AccountCode::from_components(
498 &[auth_component, component1, component2],
499 AccountType::RegularAccountUpdatableCode,
500 )
501 .unwrap_err();
502
503 assert_matches!(err, AccountError::StorageOffsetPlusSizeOutOfBounds(256))
504 }
505
506 #[test]
507 fn test_account_code_only_auth_component() {
508 let auth_component: AccountComponent =
509 NoopAuthComponent::new(Assembler::default()).unwrap().into();
510
511 let err = AccountCode::from_components(
512 &[auth_component],
513 AccountType::RegularAccountUpdatableCode,
514 )
515 .unwrap_err();
516
517 assert_matches!(err, AccountError::AccountCodeNoProcedures);
518 }
519
520 #[test]
521 fn test_account_code_no_auth_component() {
522 let component = AccountComponent::compile(CODE, Assembler::default(), vec![])
523 .unwrap()
524 .with_supports_all_types();
525
526 let err =
527 AccountCode::from_components(&[component], AccountType::RegularAccountUpdatableCode)
528 .unwrap_err();
529
530 assert_matches!(err, AccountError::AccountCodeNoAuthComponent);
531 }
532
533 #[test]
534 fn test_account_code_multiple_auth_components() {
535 let auth_component1: AccountComponent =
536 NoopAuthComponent::new(Assembler::default()).unwrap().into();
537 let auth_component2: AccountComponent =
538 NoopAuthComponent::new(Assembler::default()).unwrap().into();
539
540 let err = AccountCode::from_components(
541 &[auth_component1, auth_component2],
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 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}