miden_protocol/account/code/
mod.rs1use alloc::sync::Arc;
2use alloc::vec::Vec;
3
4use miden_core::mast::MastForest;
5use miden_core::prettier::PrettyPrint;
6
7use super::{
8 AccountError,
9 ByteReader,
10 ByteWriter,
11 Deserializable,
12 DeserializationError,
13 Felt,
14 Hasher,
15 Serializable,
16};
17use crate::Word;
18use crate::account::AccountComponent;
19
20pub mod procedure;
21use procedure::{AccountProcedureRoot, PrintableProcedure};
22
23#[derive(Debug, Clone)]
43pub struct AccountCode {
44 mast: Arc<MastForest>,
45 procedures: Vec<AccountProcedureRoot>,
46 commitment: Word,
47}
48
49impl AccountCode {
50 pub const MIN_NUM_PROCEDURES: usize = 2;
55
56 pub const MAX_NUM_PROCEDURES: usize = 256;
58
59 pub fn from_parts(mast: Arc<MastForest>, procedures: Vec<AccountProcedureRoot>) -> Self {
70 assert!(procedures.len() >= Self::MIN_NUM_PROCEDURES, "not enough account procedures");
71 assert!(procedures.len() <= Self::MAX_NUM_PROCEDURES, "too many account procedures");
72
73 Self {
74 commitment: build_procedure_commitment(&procedures),
75 procedures,
76 mast,
77 }
78 }
79
80 #[cfg(any(feature = "testing", test))]
84 pub fn from_components(components: &[AccountComponent]) -> Result<Self, AccountError> {
85 Self::from_components_unchecked(components)
86 }
87
88 pub(super) fn from_components_unchecked(
106 components: &[AccountComponent],
107 ) -> Result<Self, AccountError> {
108 let (merged_mast_forest, _) =
109 MastForest::merge(components.iter().map(|component| component.mast_forest()))
110 .map_err(AccountError::AccountComponentMastForestMergeError)?;
111
112 let mut builder = AccountProcedureBuilder::new();
113 let mut components_iter = components.iter();
114
115 let first_component =
116 components_iter.next().ok_or(AccountError::AccountCodeNoAuthComponent)?;
117 builder.add_auth_component(first_component)?;
118
119 for component in components_iter {
120 builder.add_component(component)?;
121 }
122
123 let procedures = builder.build()?;
124
125 Ok(Self {
126 commitment: build_procedure_commitment(&procedures),
127 procedures,
128 mast: Arc::new(merged_mast_forest),
129 })
130 }
131
132 pub fn commitment(&self) -> Word {
137 self.commitment
138 }
139
140 pub fn mast(&self) -> Arc<MastForest> {
142 self.mast.clone()
143 }
144
145 pub fn procedures(&self) -> &[AccountProcedureRoot] {
147 &self.procedures
148 }
149
150 pub fn procedure_roots(&self) -> impl Iterator<Item = Word> + '_ {
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: Word) -> bool {
162 self.procedures.iter().any(|procedure| procedure.mast_root() == &mast_root)
163 }
164
165 pub fn get(&self, index: usize) -> Option<&AccountProcedureRoot> {
167 self.procedures.get(index)
168 }
169
170 pub fn to_elements(&self) -> Vec<Felt> {
180 procedures_as_elements(self.procedures())
181 }
182
183 pub fn printable_procedures(&self) -> impl Iterator<Item = PrintableProcedure> {
190 self.procedures()
191 .iter()
192 .filter_map(move |proc_root| self.printable_procedure(proc_root).ok())
193 }
194
195 fn printable_procedure(
203 &self,
204 proc_root: &AccountProcedureRoot,
205 ) -> Result<PrintableProcedure, AccountError> {
206 let node_id = self
207 .mast
208 .find_procedure_root(*proc_root.mast_root())
209 .expect("procedure root should be present in the mast forest");
210
211 Ok(PrintableProcedure::new(self.mast.clone(), *proc_root, node_id))
212 }
213}
214
215impl PartialEq for AccountCode {
219 fn eq(&self, other: &Self) -> bool {
220 self.mast == other.mast && self.procedures == other.procedures
222 }
223}
224
225impl Ord for AccountCode {
226 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
227 self.commitment.cmp(&other.commitment)
228 }
229}
230
231impl PartialOrd for AccountCode {
232 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
233 Some(self.cmp(other))
234 }
235}
236
237impl Eq for AccountCode {}
238
239impl Serializable for AccountCode {
243 fn write_into<W: ByteWriter>(&self, target: &mut W) {
244 self.mast.write_into(target);
245 target.write_u8((self.procedures.len() - 1) as u8);
248 target.write_many(self.procedures());
249 }
250
251 fn get_size_hint(&self) -> usize {
252 let mut mast_forest_target = Vec::new();
254 self.mast.write_into(&mut mast_forest_target);
255
256 let u8_size = 0u8.get_size_hint();
258 let mut size = u8_size + mast_forest_target.len();
259
260 for procedure in self.procedures() {
261 size += procedure.get_size_hint();
262 }
263
264 size
265 }
266}
267
268impl Deserializable for AccountCode {
269 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
270 let mast = Arc::new(MastForest::read_from(source)?);
271 let num_procedures = (source.read_u8()? as usize) + 1;
272
273 if num_procedures < Self::MIN_NUM_PROCEDURES {
276 return Err(DeserializationError::InvalidValue(format!(
277 "account code must contain at least {} procedures, but has only {num_procedures} procedures",
278 Self::MIN_NUM_PROCEDURES
279 )));
280 }
281
282 let procedures = source
283 .read_many_iter(num_procedures)?
284 .collect::<Result<Vec<AccountProcedureRoot>, _>>()?;
285
286 for procedure in procedures.iter() {
288 if mast.find_procedure_root(procedure.as_word()).is_none() {
289 return Err(DeserializationError::InvalidValue(format!(
290 "procedure with root {} is missing from account code's MAST forest",
291 procedure.as_word()
292 )));
293 }
294 }
295
296 Ok(Self::from_parts(mast, procedures))
297 }
298}
299
300impl PrettyPrint for AccountCode {
304 fn render(&self) -> miden_core::prettier::Document {
305 use miden_core::prettier::*;
306 let mut partial = Document::Empty;
307 let len_procedures = self.num_procedures();
308
309 for (index, printable_procedure) in self.printable_procedures().enumerate() {
310 partial += indent(
311 0,
312 indent(
313 4,
314 text(format!("proc {}", printable_procedure.mast_root()))
315 + nl()
316 + printable_procedure.render(),
317 ) + nl()
318 + const_text("end"),
319 );
320 if index < len_procedures - 1 {
321 partial += nl();
322 }
323 }
324 partial
325 }
326}
327
328struct AccountProcedureBuilder {
335 procedures: Vec<AccountProcedureRoot>,
336}
337
338impl AccountProcedureBuilder {
339 fn new() -> Self {
340 Self { procedures: Vec::new() }
341 }
342
343 fn add_auth_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
345 let mut auth_proc_count = 0;
346
347 for (proc_root, is_auth) in component.procedures() {
348 self.add_procedure(proc_root);
349
350 if is_auth {
351 let auth_proc_idx = self.procedures.len() - 1;
352 self.procedures.swap(0, auth_proc_idx);
353 auth_proc_count += 1;
354 }
355 }
356
357 if auth_proc_count == 0 {
358 return Err(AccountError::AccountCodeNoAuthComponent);
359 } else if auth_proc_count > 1 {
360 return Err(AccountError::AccountComponentMultipleAuthProcedures);
361 }
362
363 Ok(())
364 }
365
366 fn add_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
367 for (proc_root, is_auth) in component.procedures() {
368 if is_auth {
369 return Err(AccountError::AccountCodeMultipleAuthComponents);
370 }
371 self.add_procedure(proc_root);
372 }
373
374 Ok(())
375 }
376
377 fn add_procedure(&mut self, proc_root: AccountProcedureRoot) {
378 if !self.procedures.contains(&proc_root) {
381 self.procedures.push(proc_root);
382 }
383 }
384
385 fn build(self) -> Result<Vec<AccountProcedureRoot>, AccountError> {
386 if self.procedures.len() < AccountCode::MIN_NUM_PROCEDURES {
387 Err(AccountError::AccountCodeNoProcedures)
388 } else if self.procedures.len() > AccountCode::MAX_NUM_PROCEDURES {
389 Err(AccountError::AccountCodeTooManyProcedures(self.procedures.len()))
390 } else {
391 Ok(self.procedures)
392 }
393 }
394}
395
396fn build_procedure_commitment(procedures: &[AccountProcedureRoot]) -> Word {
401 let elements = procedures_as_elements(procedures);
402 Hasher::hash_elements(&elements)
403}
404
405fn procedures_as_elements(procedures: &[AccountProcedureRoot]) -> Vec<Felt> {
407 procedures.iter().flat_map(AccountProcedureRoot::as_elements).copied().collect()
408}
409
410#[cfg(test)]
414mod tests {
415
416 use alloc::sync::Arc;
417
418 use assert_matches::assert_matches;
419 use miden_assembly::Assembler;
420
421 use super::{AccountCode, Deserializable, Serializable};
422 use crate::account::AccountComponent;
423 use crate::account::code::build_procedure_commitment;
424 use crate::account::component::AccountComponentMetadata;
425 use crate::errors::AccountError;
426 use crate::testing::account_code::CODE;
427 use crate::testing::noop_auth_component::NoopAuthComponent;
428
429 #[test]
430 fn test_serde_account_code() {
431 let code = AccountCode::mock();
432 let serialized = code.to_bytes();
433 let deserialized = AccountCode::read_from_bytes(&serialized).unwrap();
434 assert_eq!(deserialized, code)
435 }
436
437 #[test]
438 fn test_account_code_procedure_root() {
439 let code = AccountCode::mock();
440 let procedure_root = build_procedure_commitment(code.procedures());
441 assert_eq!(procedure_root, code.commitment())
442 }
443
444 #[test]
445 fn test_account_code_only_auth_component() {
446 let err = AccountCode::from_components(&[NoopAuthComponent.into()]).unwrap_err();
447
448 assert_matches!(err, AccountError::AccountCodeNoProcedures);
449 }
450
451 #[test]
452 fn test_account_code_no_auth_component() {
453 let library = Arc::unwrap_or_clone(Assembler::default().assemble_library([CODE]).unwrap());
454 let metadata = AccountComponentMetadata::new("test::no_auth");
455 let component = AccountComponent::new(library, vec![], metadata).unwrap();
456
457 let err = AccountCode::from_components(&[component]).unwrap_err();
458
459 assert_matches!(err, AccountError::AccountCodeNoAuthComponent);
460 }
461
462 #[test]
463 fn test_account_code_multiple_auth_components() {
464 let err =
465 AccountCode::from_components(&[NoopAuthComponent.into(), NoopAuthComponent.into()])
466 .unwrap_err();
467
468 assert_matches!(err, AccountError::AccountCodeMultipleAuthComponents);
469 }
470
471 #[test]
472 fn test_account_component_multiple_auth_procedures() {
473 use miden_assembly::Assembler;
474
475 let code_with_multiple_auth = "
476 @auth_script
477 pub proc auth_basic
478 push.1 drop
479 end
480
481 @auth_script
482 pub proc auth_secondary
483 push.0 drop
484 end
485 ";
486
487 let library = Arc::unwrap_or_clone(
488 Assembler::default().assemble_library([code_with_multiple_auth]).unwrap(),
489 );
490 let metadata = AccountComponentMetadata::new("test::multiple_auth");
491 let component = AccountComponent::new(library, vec![], metadata).unwrap();
492
493 let err = AccountCode::from_components(&[component]).unwrap_err();
494
495 assert_matches!(err, AccountError::AccountComponentMultipleAuthProcedures);
496 }
497}