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#[cfg(any(feature = "testing", test))]
20use crate::account::AccountType;
21
22pub mod procedure;
23use procedure::{AccountProcedureRoot, PrintableProcedure};
24
25#[derive(Debug, Clone)]
45pub struct AccountCode {
46 mast: Arc<MastForest>,
47 procedures: Vec<AccountProcedureRoot>,
48 commitment: Word,
49}
50
51impl AccountCode {
52 pub const MIN_NUM_PROCEDURES: usize = 2;
57
58 pub const MAX_NUM_PROCEDURES: usize = 256;
60
61 #[cfg(any(feature = "testing", test))]
68 pub fn from_components(
69 components: &[AccountComponent],
70 account_type: AccountType,
71 ) -> Result<Self, AccountError> {
72 super::validate_components_support_account_type(components, account_type)?;
73 Self::from_components_unchecked(components)
74 }
75
76 pub(super) fn from_components_unchecked(
94 components: &[AccountComponent],
95 ) -> Result<Self, AccountError> {
96 let (merged_mast_forest, _) =
97 MastForest::merge(components.iter().map(|component| component.mast_forest()))
98 .map_err(AccountError::AccountComponentMastForestMergeError)?;
99
100 let mut builder = AccountProcedureBuilder::new();
101 let mut components_iter = components.iter();
102
103 let first_component =
104 components_iter.next().ok_or(AccountError::AccountCodeNoAuthComponent)?;
105 builder.add_auth_component(first_component)?;
106
107 for component in components_iter {
108 builder.add_component(component)?;
109 }
110
111 let procedures = builder.build()?;
112
113 Ok(Self {
114 commitment: build_procedure_commitment(&procedures),
115 procedures,
116 mast: Arc::new(merged_mast_forest),
117 })
118 }
119
120 pub fn from_bytes(bytes: &[u8]) -> Result<Self, AccountError> {
125 Self::read_from_bytes(bytes).map_err(AccountError::AccountCodeDeserializationError)
126 }
127
128 pub fn from_parts(mast: Arc<MastForest>, procedures: Vec<AccountProcedureRoot>) -> Self {
136 assert!(procedures.len() >= Self::MIN_NUM_PROCEDURES, "not enough account procedures");
137 assert!(procedures.len() <= Self::MAX_NUM_PROCEDURES, "too many account procedures");
138
139 Self {
140 commitment: build_procedure_commitment(&procedures),
141 procedures,
142 mast,
143 }
144 }
145
146 pub fn commitment(&self) -> Word {
151 self.commitment
152 }
153
154 pub fn mast(&self) -> Arc<MastForest> {
156 self.mast.clone()
157 }
158
159 pub fn procedures(&self) -> &[AccountProcedureRoot] {
161 &self.procedures
162 }
163
164 pub fn procedure_roots(&self) -> impl Iterator<Item = Word> + '_ {
166 self.procedures().iter().map(|procedure| *procedure.mast_root())
167 }
168
169 pub fn num_procedures(&self) -> usize {
171 self.procedures.len()
172 }
173
174 pub fn has_procedure(&self, mast_root: Word) -> bool {
176 self.procedures.iter().any(|procedure| procedure.mast_root() == &mast_root)
177 }
178
179 pub fn get(&self, index: usize) -> Option<&AccountProcedureRoot> {
181 self.procedures.get(index)
182 }
183
184 pub fn as_elements(&self) -> Vec<Felt> {
194 procedures_as_elements(self.procedures())
195 }
196
197 pub fn printable_procedures(&self) -> impl Iterator<Item = PrintableProcedure> {
204 self.procedures()
205 .iter()
206 .filter_map(move |proc_root| self.printable_procedure(proc_root).ok())
207 }
208
209 fn printable_procedure(
217 &self,
218 proc_root: &AccountProcedureRoot,
219 ) -> Result<PrintableProcedure, AccountError> {
220 let node_id = self
221 .mast
222 .find_procedure_root(*proc_root.mast_root())
223 .expect("procedure root should be present in the mast forest");
224
225 Ok(PrintableProcedure::new(self.mast.clone(), *proc_root, node_id))
226 }
227}
228
229impl PartialEq for AccountCode {
233 fn eq(&self, other: &Self) -> bool {
234 self.mast == other.mast && self.procedures == other.procedures
236 }
237}
238
239impl Ord for AccountCode {
240 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
241 self.commitment.cmp(&other.commitment)
242 }
243}
244
245impl PartialOrd for AccountCode {
246 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
247 Some(self.cmp(other))
248 }
249}
250
251impl Eq for AccountCode {}
252
253impl Serializable for AccountCode {
257 fn write_into<W: ByteWriter>(&self, target: &mut W) {
258 self.mast.write_into(target);
259 target.write_u8((self.procedures.len() - 1) as u8);
262 target.write_many(self.procedures());
263 }
264
265 fn get_size_hint(&self) -> usize {
266 let mut mast_forest_target = Vec::new();
268 self.mast.write_into(&mut mast_forest_target);
269
270 let u8_size = 0u8.get_size_hint();
272 let mut size = u8_size + mast_forest_target.len();
273
274 for procedure in self.procedures() {
275 size += procedure.get_size_hint();
276 }
277
278 size
279 }
280}
281
282impl Deserializable for AccountCode {
283 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
284 let module = Arc::new(MastForest::read_from(source)?);
285 let num_procedures = (source.read_u8()? as usize) + 1;
286 let procedures = source
287 .read_many_iter(num_procedures)?
288 .collect::<Result<Vec<AccountProcedureRoot>, _>>()?;
289
290 Ok(Self::from_parts(module, procedures))
291 }
292}
293
294impl PrettyPrint for AccountCode {
298 fn render(&self) -> miden_core::prettier::Document {
299 use miden_core::prettier::*;
300 let mut partial = Document::Empty;
301 let len_procedures = self.num_procedures();
302
303 for (index, printable_procedure) in self.printable_procedures().enumerate() {
304 partial += indent(
305 0,
306 indent(
307 4,
308 text(format!("proc {}", printable_procedure.mast_root()))
309 + nl()
310 + printable_procedure.render(),
311 ) + nl()
312 + const_text("end"),
313 );
314 if index < len_procedures - 1 {
315 partial += nl();
316 }
317 }
318 partial
319 }
320}
321
322struct AccountProcedureBuilder {
329 procedures: Vec<AccountProcedureRoot>,
330}
331
332impl AccountProcedureBuilder {
333 fn new() -> Self {
334 Self { procedures: Vec::new() }
335 }
336
337 fn add_auth_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
339 let mut auth_proc_count = 0;
340
341 for (proc_root, is_auth) in component.get_procedures() {
342 self.add_procedure(proc_root);
343
344 if is_auth {
345 let auth_proc_idx = self.procedures.len() - 1;
346 self.procedures.swap(0, auth_proc_idx);
347 auth_proc_count += 1;
348 }
349 }
350
351 if auth_proc_count == 0 {
352 return Err(AccountError::AccountCodeNoAuthComponent);
353 } else if auth_proc_count > 1 {
354 return Err(AccountError::AccountComponentMultipleAuthProcedures);
355 }
356
357 Ok(())
358 }
359
360 fn add_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
361 for (proc_mast_root, is_auth) in component.get_procedures() {
362 if is_auth {
363 return Err(AccountError::AccountCodeMultipleAuthComponents);
364 }
365 self.add_procedure(proc_mast_root);
366 }
367
368 Ok(())
369 }
370
371 fn add_procedure(&mut self, proc_mast_root: Word) {
372 let proc_root = AccountProcedureRoot::from_raw(proc_mast_root);
375 if !self.procedures.contains(&proc_root) {
376 self.procedures.push(proc_root);
377 }
378 }
379
380 fn build(self) -> Result<Vec<AccountProcedureRoot>, AccountError> {
381 if self.procedures.len() < AccountCode::MIN_NUM_PROCEDURES {
382 Err(AccountError::AccountCodeNoProcedures)
383 } else if self.procedures.len() > AccountCode::MAX_NUM_PROCEDURES {
384 Err(AccountError::AccountCodeTooManyProcedures(self.procedures.len()))
385 } else {
386 Ok(self.procedures)
387 }
388 }
389}
390
391pub(crate) fn build_procedure_commitment(procedures: &[AccountProcedureRoot]) -> Word {
396 let elements = procedures_as_elements(procedures);
397 Hasher::hash_elements(&elements)
398}
399
400pub(crate) fn procedures_as_elements(procedures: &[AccountProcedureRoot]) -> Vec<Felt> {
402 procedures.iter().flat_map(AccountProcedureRoot::as_elements).copied().collect()
403}
404
405#[cfg(test)]
409mod tests {
410
411 use assert_matches::assert_matches;
412 use miden_assembly::Assembler;
413
414 use super::{AccountCode, Deserializable, Serializable};
415 use crate::account::code::build_procedure_commitment;
416 use crate::account::component::AccountComponentMetadata;
417 use crate::account::{AccountComponent, AccountType};
418 use crate::errors::AccountError;
419 use crate::testing::account_code::CODE;
420 use crate::testing::noop_auth_component::NoopAuthComponent;
421
422 #[test]
423 fn test_serde_account_code() {
424 let code = AccountCode::mock();
425 let serialized = code.to_bytes();
426 let deserialized = AccountCode::read_from_bytes(&serialized).unwrap();
427 assert_eq!(deserialized, code)
428 }
429
430 #[test]
431 fn test_account_code_procedure_root() {
432 let code = AccountCode::mock();
433 let procedure_root = build_procedure_commitment(code.procedures());
434 assert_eq!(procedure_root, code.commitment())
435 }
436
437 #[test]
438 fn test_account_code_only_auth_component() {
439 let err = AccountCode::from_components(
440 &[NoopAuthComponent.into()],
441 AccountType::RegularAccountUpdatableCode,
442 )
443 .unwrap_err();
444
445 assert_matches!(err, AccountError::AccountCodeNoProcedures);
446 }
447
448 #[test]
449 fn test_account_code_no_auth_component() {
450 let library = Assembler::default().assemble_library([CODE]).unwrap();
451 let metadata = AccountComponentMetadata::new("test::no_auth", AccountType::all());
452 let component = AccountComponent::new(library, vec![], metadata).unwrap();
453
454 let err =
455 AccountCode::from_components(&[component], AccountType::RegularAccountUpdatableCode)
456 .unwrap_err();
457
458 assert_matches!(err, AccountError::AccountCodeNoAuthComponent);
459 }
460
461 #[test]
462 fn test_account_code_multiple_auth_components() {
463 let err = AccountCode::from_components(
464 &[NoopAuthComponent.into(), NoopAuthComponent.into()],
465 AccountType::RegularAccountUpdatableCode,
466 )
467 .unwrap_err();
468
469 assert_matches!(err, AccountError::AccountCodeMultipleAuthComponents);
470 }
471
472 #[test]
473 fn test_account_component_multiple_auth_procedures() {
474 use miden_assembly::Assembler;
475
476 let code_with_multiple_auth = "
477 @auth_script
478 pub proc auth_basic
479 push.1 drop
480 end
481
482 @auth_script
483 pub proc auth_secondary
484 push.0 drop
485 end
486 ";
487
488 let library = Assembler::default().assemble_library([code_with_multiple_auth]).unwrap();
489 let metadata = AccountComponentMetadata::new("test::multiple_auth", AccountType::all());
490 let component = AccountComponent::new(library, vec![], metadata).unwrap();
491
492 let err =
493 AccountCode::from_components(&[component], AccountType::RegularAccountUpdatableCode)
494 .unwrap_err();
495
496 assert_matches!(err, AccountError::AccountComponentMultipleAuthProcedures);
497 }
498}