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.read_many::<AccountProcedureRoot>(num_procedures)?;
287
288 Ok(Self::from_parts(module, procedures))
289 }
290}
291
292impl PrettyPrint for AccountCode {
296 fn render(&self) -> miden_core::prettier::Document {
297 use miden_core::prettier::*;
298 let mut partial = Document::Empty;
299 let len_procedures = self.num_procedures();
300
301 for (index, printable_procedure) in self.printable_procedures().enumerate() {
302 partial += indent(
303 0,
304 indent(
305 4,
306 text(format!("proc {}", printable_procedure.mast_root()))
307 + nl()
308 + printable_procedure.render(),
309 ) + nl()
310 + const_text("end"),
311 );
312 if index < len_procedures - 1 {
313 partial += nl();
314 }
315 }
316 partial
317 }
318}
319
320struct AccountProcedureBuilder {
327 procedures: Vec<AccountProcedureRoot>,
328}
329
330impl AccountProcedureBuilder {
331 fn new() -> Self {
332 Self { procedures: Vec::new() }
333 }
334
335 fn add_auth_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
337 let mut auth_proc_count = 0;
338
339 for (proc_root, is_auth) in component.get_procedures() {
340 self.add_procedure(proc_root);
341
342 if is_auth {
343 let auth_proc_idx = self.procedures.len() - 1;
344 self.procedures.swap(0, auth_proc_idx);
345 auth_proc_count += 1;
346 }
347 }
348
349 if auth_proc_count == 0 {
350 return Err(AccountError::AccountCodeNoAuthComponent);
351 } else if auth_proc_count > 1 {
352 return Err(AccountError::AccountComponentMultipleAuthProcedures);
353 }
354
355 Ok(())
356 }
357
358 fn add_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
359 for (proc_mast_root, is_auth) in component.get_procedures() {
360 if is_auth {
361 return Err(AccountError::AccountCodeMultipleAuthComponents);
362 }
363 self.add_procedure(proc_mast_root);
364 }
365
366 Ok(())
367 }
368
369 fn add_procedure(&mut self, proc_mast_root: Word) {
370 let proc_root = AccountProcedureRoot::from_raw(proc_mast_root);
373 if !self.procedures.contains(&proc_root) {
374 self.procedures.push(proc_root);
375 }
376 }
377
378 fn build(self) -> Result<Vec<AccountProcedureRoot>, AccountError> {
379 if self.procedures.len() < AccountCode::MIN_NUM_PROCEDURES {
380 Err(AccountError::AccountCodeNoProcedures)
381 } else if self.procedures.len() > AccountCode::MAX_NUM_PROCEDURES {
382 Err(AccountError::AccountCodeTooManyProcedures(self.procedures.len()))
383 } else {
384 Ok(self.procedures)
385 }
386 }
387}
388
389pub(crate) fn build_procedure_commitment(procedures: &[AccountProcedureRoot]) -> Word {
394 let elements = procedures_as_elements(procedures);
395 Hasher::hash_elements(&elements)
396}
397
398pub(crate) fn procedures_as_elements(procedures: &[AccountProcedureRoot]) -> Vec<Felt> {
400 procedures.iter().flat_map(AccountProcedureRoot::as_elements).copied().collect()
401}
402
403#[cfg(test)]
407mod tests {
408
409 use assert_matches::assert_matches;
410 use miden_assembly::Assembler;
411
412 use super::{AccountCode, Deserializable, Serializable};
413 use crate::account::code::build_procedure_commitment;
414 use crate::account::{AccountComponent, AccountType};
415 use crate::errors::AccountError;
416 use crate::testing::account_code::CODE;
417 use crate::testing::noop_auth_component::NoopAuthComponent;
418
419 #[test]
420 fn test_serde_account_code() {
421 let code = AccountCode::mock();
422 let serialized = code.to_bytes();
423 let deserialized = AccountCode::read_from_bytes(&serialized).unwrap();
424 assert_eq!(deserialized, code)
425 }
426
427 #[test]
428 fn test_account_code_procedure_root() {
429 let code = AccountCode::mock();
430 let procedure_root = build_procedure_commitment(code.procedures());
431 assert_eq!(procedure_root, code.commitment())
432 }
433
434 #[test]
435 fn test_account_code_only_auth_component() {
436 let err = AccountCode::from_components(
437 &[NoopAuthComponent.into()],
438 AccountType::RegularAccountUpdatableCode,
439 )
440 .unwrap_err();
441
442 assert_matches!(err, AccountError::AccountCodeNoProcedures);
443 }
444
445 #[test]
446 fn test_account_code_no_auth_component() {
447 let library = Assembler::default().assemble_library([CODE]).unwrap();
448 let component = AccountComponent::new(library, vec![]).unwrap().with_supports_all_types();
449
450 let err =
451 AccountCode::from_components(&[component], AccountType::RegularAccountUpdatableCode)
452 .unwrap_err();
453
454 assert_matches!(err, AccountError::AccountCodeNoAuthComponent);
455 }
456
457 #[test]
458 fn test_account_code_multiple_auth_components() {
459 let err = AccountCode::from_components(
460 &[NoopAuthComponent.into(), NoopAuthComponent.into()],
461 AccountType::RegularAccountUpdatableCode,
462 )
463 .unwrap_err();
464
465 assert_matches!(err, AccountError::AccountCodeMultipleAuthComponents);
466 }
467
468 #[test]
469 fn test_account_component_multiple_auth_procedures() {
470 use miden_assembly::Assembler;
471
472 let code_with_multiple_auth = "
473 pub proc auth_basic
474 push.1 drop
475 end
476
477 pub proc auth_secondary
478 push.0 drop
479 end
480 ";
481
482 let library = Assembler::default().assemble_library([code_with_multiple_auth]).unwrap();
483 let component = AccountComponent::new(library, vec![]).unwrap().with_supports_all_types();
484
485 let err =
486 AccountCode::from_components(&[component], AccountType::RegularAccountUpdatableCode)
487 .unwrap_err();
488
489 assert_matches!(err, AccountError::AccountComponentMultipleAuthProcedures);
490 }
491}