Skip to main content

miden_protocol/account/code/
mod.rs

1use alloc::sync::Arc;
2use alloc::vec::Vec;
3
4use miden_core::mast::MastForest;
5use miden_core::prettier::PrettyPrint;
6use miden_mast_package::debug_info::PackageDebugInfo;
7use miden_processor::LoadedMastForest;
8
9use super::{
10    AccountError,
11    ByteReader,
12    ByteWriter,
13    Deserializable,
14    DeserializationError,
15    Felt,
16    Hasher,
17    Serializable,
18};
19use crate::Word;
20use crate::account::{AccountCodeInterface, AccountComponent, AccountId};
21use crate::package::{loaded_mast_forest, package_debug_info};
22
23pub mod procedure;
24use procedure::{AccountProcedureRoot, PrintableProcedure};
25
26// ACCOUNT CODE
27// ================================================================================================
28
29/// The public interface of an account.
30///
31/// An account's public interface consists of a set of account procedures, each of which is
32/// identified and committed to by a MAST root. They are represented by [`AccountProcedureRoot`].
33///
34/// The set of procedures has an arbitrary order, i.e. they are not sorted. The only exception is
35/// the authentication procedure of the account, which is always at index 0. This procedure is
36/// automatically called at the end of a transaction to validate an account's state transition.
37///
38/// The code commits to the entire account interface by building a sequential hash of all procedure
39/// MAST roots. Specifically, each procedure contributes exactly 4 field elements to the sequence of
40/// elements to be hashed. Each procedure is represented by its MAST root:
41///
42/// ```text
43/// [PROCEDURE_MAST_ROOT]
44/// ```
45#[derive(Debug, Clone)]
46pub struct AccountCode {
47    mast: Arc<MastForest>,
48    procedures: Vec<AccountProcedureRoot>,
49    commitment: Word,
50    package_debug_info: Option<Arc<PackageDebugInfo>>,
51}
52
53impl AccountCode {
54    // CONSTANTS
55    // --------------------------------------------------------------------------------------------
56
57    /// The minimum number of account interface procedures (one auth and at least one non-auth).
58    pub const MIN_NUM_PROCEDURES: usize = 2;
59
60    /// The maximum number of account interface procedures.
61    pub const MAX_NUM_PROCEDURES: usize = 256;
62
63    // CONSTRUCTORS
64    // --------------------------------------------------------------------------------------------
65
66    /// Returns a new [`AccountCode`] instantiated from the provided [`MastForest`] and a list of
67    /// [`AccountProcedureRoot`]s.
68    ///
69    /// # Panics
70    ///
71    /// Panics if:
72    /// - The number of procedures is smaller than 2 or greater than 256.
73    pub fn from_parts(mast: Arc<MastForest>, procedures: Vec<AccountProcedureRoot>) -> Self {
74        assert!(procedures.len() >= Self::MIN_NUM_PROCEDURES, "not enough account procedures");
75        assert!(procedures.len() <= Self::MAX_NUM_PROCEDURES, "too many account procedures");
76
77        Self {
78            commitment: build_procedure_commitment(&procedures),
79            procedures,
80            mast,
81            package_debug_info: None,
82        }
83    }
84
85    /// Creates a new [`AccountCode`] from the provided components' libraries.
86    ///
87    /// For testing use only.
88    #[cfg(any(feature = "testing", test))]
89    pub fn from_components(components: &[AccountComponent]) -> Result<Self, AccountError> {
90        Self::from_components_unchecked(components)
91    }
92
93    /// Creates a new [`AccountCode`] from the provided components' libraries.
94    ///
95    /// # Warning
96    ///
97    /// This does not check whether the provided components are valid when combined.
98    ///
99    /// # Errors
100    ///
101    /// Returns an error if:
102    /// - The number of procedures in all merged libraries is 0 or exceeds
103    ///   [`AccountCode::MAX_NUM_PROCEDURES`].
104    /// - Two or more libraries export a procedure with the same MAST root.
105    /// - The first component doesn't contain exactly one authentication procedure.
106    /// - Other components contain authentication procedures.
107    /// - The number of [`StorageSlot`](crate::account::StorageSlot)s of a component or of all
108    ///   components exceeds 255.
109    /// - [`MastForest::merge`] fails on all libraries.
110    pub(super) fn from_components_unchecked(
111        components: &[AccountComponent],
112    ) -> Result<Self, AccountError> {
113        let (merged_mast_forest, root_map) =
114            MastForest::merge(components.iter().map(|component| component.mast_forest()))
115                .map_err(AccountError::AccountComponentMastForestMergeError)?;
116        let package_debug_info = merge_component_debug_info(components, &root_map)?;
117
118        let mut builder = AccountProcedureBuilder::new();
119        let mut components_iter = components.iter();
120
121        let first_component =
122            components_iter.next().ok_or(AccountError::AccountCodeNoAuthComponent)?;
123        builder.add_auth_component(first_component)?;
124
125        for component in components_iter {
126            builder.add_component(component)?;
127        }
128
129        let procedures = builder.build()?;
130
131        Ok(Self {
132            commitment: build_procedure_commitment(&procedures),
133            procedures,
134            mast: Arc::new(merged_mast_forest),
135            package_debug_info,
136        })
137    }
138
139    // PUBLIC ACCESSORS
140    // --------------------------------------------------------------------------------------------
141
142    /// Returns a commitment to an account's public interface.
143    pub fn commitment(&self) -> Word {
144        self.commitment
145    }
146
147    /// Returns a reference to the [MastForest] backing this account code.
148    pub fn mast(&self) -> Arc<MastForest> {
149        self.mast.clone()
150    }
151
152    /// Returns the MAST forest and package-owned debug information backing this account code.
153    pub fn loaded_mast_forest(&self) -> LoadedMastForest {
154        loaded_mast_forest(self.mast.clone(), self.package_debug_info.clone())
155    }
156
157    /// Returns a reference to the account procedure roots.
158    pub fn procedures(&self) -> &[AccountProcedureRoot] {
159        &self.procedures
160    }
161
162    /// Returns an iterator over the procedure MAST roots of this account code.
163    pub fn procedure_roots(&self) -> impl Iterator<Item = Word> + '_ {
164        self.procedures().iter().map(|procedure| *procedure.mast_root())
165    }
166
167    /// Returns the number of public interface procedures defined in this account code.
168    pub fn num_procedures(&self) -> usize {
169        self.procedures.len()
170    }
171
172    /// Returns true if a procedure with the specified MAST root is defined in this account code.
173    pub fn has_procedure(&self, mast_root: Word) -> bool {
174        self.procedures.iter().any(|procedure| procedure.mast_root() == &mast_root)
175    }
176
177    /// Returns the procedure root at the specified index.
178    pub fn get(&self, index: usize) -> Option<&AccountProcedureRoot> {
179        self.procedures.get(index)
180    }
181
182    /// Converts the procedure root in this [`AccountCode`] into a vector of field elements.
183    ///
184    /// This is done by first converting each procedure into 4 field elements as follows:
185    ///
186    /// ```text
187    /// [PROCEDURE_MAST_ROOT]
188    /// ```
189    ///
190    /// And then concatenating the resulting elements into a single vector.
191    pub fn to_elements(&self) -> Vec<Felt> {
192        procedures_as_elements(self.procedures())
193    }
194
195    /// Returns the public interface of this account code: the given account ID and the set of
196    /// procedure roots exposed by this code.
197    pub fn interface(&self, account_id: AccountId) -> AccountCodeInterface {
198        AccountCodeInterface::new(account_id, self.procedures.iter().copied().collect())
199            .expect("account code procedure count is enforced by AccountCode invariants")
200    }
201
202    /// Returns an iterator of printable representations for all procedures in this account code.
203    ///
204    /// # Returns
205    ///
206    /// An iterator yielding [`PrintableProcedure`] instances for all procedures in this account
207    /// code.
208    pub fn printable_procedures(&self) -> impl Iterator<Item = PrintableProcedure> {
209        self.procedures()
210            .iter()
211            .filter_map(move |proc_root| self.printable_procedure(proc_root).ok())
212    }
213
214    // HELPER FUNCTIONS
215    // --------------------------------------------------------------------------------------------
216
217    /// Returns a printable representation of the procedure with the specified MAST root.
218    ///
219    /// # Errors
220    /// Returns an error if no procedure with the specified root exists in this account code.
221    fn printable_procedure(
222        &self,
223        proc_root: &AccountProcedureRoot,
224    ) -> Result<PrintableProcedure, AccountError> {
225        let node_id = self
226            .mast
227            .find_procedure_root(*proc_root.mast_root())
228            .expect("procedure root should be present in the mast forest");
229
230        Ok(PrintableProcedure::new(self.mast.clone(), *proc_root, node_id))
231    }
232}
233
234// EQUALITY
235// ================================================================================================
236
237impl PartialEq for AccountCode {
238    fn eq(&self, other: &Self) -> bool {
239        // TODO: consider checking equality based only on the set of procedures
240        self.mast == other.mast && self.procedures == other.procedures
241    }
242}
243
244impl Ord for AccountCode {
245    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
246        self.commitment.cmp(&other.commitment)
247    }
248}
249
250impl PartialOrd for AccountCode {
251    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
252        Some(self.cmp(other))
253    }
254}
255
256impl Eq for AccountCode {}
257
258// SERIALIZATION
259// ================================================================================================
260
261impl Serializable for AccountCode {
262    fn write_into<W: ByteWriter>(&self, target: &mut W) {
263        self.mast.write_into(target);
264        // since the number of procedures is guaranteed to be between 2 and 256, we can store the
265        // number as a single byte - but we do have to subtract 1 to store 256 as 255.
266        target.write_u8((self.procedures.len() - 1) as u8);
267        target.write_many(self.procedures());
268    }
269
270    fn get_size_hint(&self) -> usize {
271        // TODO: Replace with proper calculation.
272        let mut mast_forest_target = Vec::new();
273        self.mast.write_into(&mut mast_forest_target);
274
275        // Size of the serialized procedures length.
276        let u8_size = 0u8.get_size_hint();
277        let mut size = u8_size + mast_forest_target.len();
278
279        for procedure in self.procedures() {
280            size += procedure.get_size_hint();
281        }
282
283        size
284    }
285}
286
287impl Deserializable for AccountCode {
288    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
289        let mast = Arc::new(MastForest::read_from(source)?);
290        let num_procedures = (source.read_u8()? as usize) + 1;
291
292        // make sure the number of procedures is valid; we only check the minimum because
293        // u8::MAX + 1 is guaranteed to be less than or equal to 256
294        if num_procedures < Self::MIN_NUM_PROCEDURES {
295            return Err(DeserializationError::InvalidValue(format!(
296                "account code must contain at least {} procedures, but has only {num_procedures} procedures",
297                Self::MIN_NUM_PROCEDURES
298            )));
299        }
300
301        let procedures = source
302            .read_many_iter(num_procedures)?
303            .collect::<Result<Vec<AccountProcedureRoot>, _>>()?;
304
305        // make sure that all account procedures are in the MAST forest
306        for procedure in procedures.iter() {
307            if mast.find_procedure_root(procedure.as_word()).is_none() {
308                return Err(DeserializationError::InvalidValue(format!(
309                    "procedure with root {} is missing from account code's MAST forest",
310                    procedure.as_word()
311                )));
312            }
313        }
314
315        Ok(Self::from_parts(mast, procedures))
316    }
317}
318
319// PRETTY PRINT
320// ================================================================================================
321
322impl PrettyPrint for AccountCode {
323    fn render(&self) -> miden_core::prettier::Document {
324        use miden_core::prettier::*;
325        let mut partial = Document::Empty;
326        let len_procedures = self.num_procedures();
327
328        for (index, printable_procedure) in self.printable_procedures().enumerate() {
329            partial += indent(
330                0,
331                indent(
332                    4,
333                    text(format!("proc {}", printable_procedure.mast_root()))
334                        + nl()
335                        + printable_procedure.render(),
336                ) + nl()
337                    + const_text("end"),
338            );
339            if index < len_procedures - 1 {
340                partial += nl();
341            }
342        }
343        partial
344    }
345}
346
347// ACCOUNT PROCEDURE BUILDER
348// ================================================================================================
349
350/// A helper type for building the set of account procedures from account components.
351///
352/// In particular, this ensures that the auth procedure ends up at index 0.
353struct AccountProcedureBuilder {
354    procedures: Vec<AccountProcedureRoot>,
355}
356
357impl AccountProcedureBuilder {
358    fn new() -> Self {
359        Self { procedures: Vec::new() }
360    }
361
362    /// This method must be called before add_component is called.
363    fn add_auth_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
364        let mut auth_proc_count = 0;
365
366        for (proc_root, is_auth) in component.procedures() {
367            self.add_procedure(proc_root);
368
369            if is_auth {
370                let auth_proc_idx = self.procedures.len() - 1;
371                self.procedures.swap(0, auth_proc_idx);
372                auth_proc_count += 1;
373            }
374        }
375
376        if auth_proc_count == 0 {
377            return Err(AccountError::AccountCodeNoAuthComponent);
378        } else if auth_proc_count > 1 {
379            return Err(AccountError::AccountComponentMultipleAuthProcedures);
380        }
381
382        Ok(())
383    }
384
385    fn add_component(&mut self, component: &AccountComponent) -> Result<(), AccountError> {
386        for (proc_root, is_auth) in component.procedures() {
387            if is_auth {
388                return Err(AccountError::AccountCodeMultipleAuthComponents);
389            }
390            self.add_procedure(proc_root);
391        }
392
393        Ok(())
394    }
395
396    fn add_procedure(&mut self, proc_root: AccountProcedureRoot) {
397        // Allow procedures with the same MAST root from different components, but only add them
398        // once.
399        if !self.procedures.contains(&proc_root) {
400            self.procedures.push(proc_root);
401        }
402    }
403
404    fn build(self) -> Result<Vec<AccountProcedureRoot>, AccountError> {
405        if self.procedures.len() < AccountCode::MIN_NUM_PROCEDURES {
406            Err(AccountError::AccountCodeNoProcedures)
407        } else if self.procedures.len() > AccountCode::MAX_NUM_PROCEDURES {
408            Err(AccountError::AccountCodeTooManyProcedures(self.procedures.len()))
409        } else {
410            Ok(self.procedures)
411        }
412    }
413}
414
415// HELPER FUNCTIONS
416// ================================================================================================
417
418/// Computes the commitment to the given procedures
419fn build_procedure_commitment(procedures: &[AccountProcedureRoot]) -> Word {
420    let elements = procedures_as_elements(procedures);
421    Hasher::hash_elements(&elements)
422}
423
424fn merge_component_debug_info(
425    components: &[AccountComponent],
426    root_map: &miden_core::mast::MastForestRootMap,
427) -> Result<Option<Arc<PackageDebugInfo>>, AccountError> {
428    let component_debug_info = components
429        .iter()
430        .enumerate()
431        .filter_map(|(idx, component)| {
432            package_debug_info(component.component_code().as_library()).map(|debug| (idx, debug))
433        })
434        .collect::<Vec<_>>();
435
436    if component_debug_info.is_empty() {
437        return Ok(None);
438    }
439
440    let debug_info = PackageDebugInfo::merge_source_debug(
441        component_debug_info.iter().map(|(idx, debug)| (*idx, debug.as_ref())),
442        root_map,
443    )
444    .map_err(|err| {
445        AccountError::other_with_source("failed to merge account component debug info", err)
446    })?;
447
448    Ok(Some(Arc::new(debug_info)))
449}
450
451/// Converts given procedures into field elements
452fn procedures_as_elements(procedures: &[AccountProcedureRoot]) -> Vec<Felt> {
453    procedures.iter().flat_map(AccountProcedureRoot::as_elements).copied().collect()
454}
455
456// TESTS
457// ================================================================================================
458
459#[cfg(test)]
460mod tests {
461    use assert_matches::assert_matches;
462
463    use super::{AccountCode, Deserializable, Serializable};
464    use crate::account::AccountComponent;
465    use crate::account::code::build_procedure_commitment;
466    use crate::account::component::AccountComponentMetadata;
467    use crate::errors::AccountError;
468    use crate::testing::account_code::CODE;
469    use crate::testing::assembler::assemble_test_library;
470    use crate::testing::noop_auth_component::NoopAuthComponent;
471
472    #[test]
473    fn test_serde_account_code() {
474        let code = AccountCode::mock();
475        let serialized = code.to_bytes();
476        let deserialized = AccountCode::read_from_bytes(&serialized).unwrap();
477        assert_eq!(deserialized, code)
478    }
479
480    #[test]
481    fn test_account_code_procedure_root() {
482        let code = AccountCode::mock();
483        let procedure_root = build_procedure_commitment(code.procedures());
484        assert_eq!(procedure_root, code.commitment())
485    }
486
487    #[test]
488    fn test_account_code_only_auth_component() {
489        let err = AccountCode::from_components(&[NoopAuthComponent.into()]).unwrap_err();
490
491        assert_matches!(err, AccountError::AccountCodeNoProcedures);
492    }
493
494    #[test]
495    fn test_account_code_no_auth_component() {
496        let library =
497            assemble_test_library("test-account-code-no-auth", "test::account_code", CODE);
498        let metadata = AccountComponentMetadata::new("test::no_auth");
499        let component = AccountComponent::new(library, vec![], metadata).unwrap();
500
501        let err = AccountCode::from_components(&[component]).unwrap_err();
502
503        assert_matches!(err, AccountError::AccountCodeNoAuthComponent);
504    }
505
506    #[test]
507    fn test_account_code_preserves_component_debug_info() {
508        let library =
509            assemble_test_library("test-account-code-debug-info", "test::account_code", CODE);
510        let metadata = AccountComponentMetadata::new("test::debug_info");
511        let component = AccountComponent::new(library, vec![], metadata).unwrap();
512
513        let code = AccountCode::from_components(&[NoopAuthComponent.into(), component]).unwrap();
514
515        assert!(code.loaded_mast_forest().package_debug_info().unwrap().is_some());
516    }
517
518    #[test]
519    fn test_account_code_multiple_auth_components() {
520        let err =
521            AccountCode::from_components(&[NoopAuthComponent.into(), NoopAuthComponent.into()])
522                .unwrap_err();
523
524        assert_matches!(err, AccountError::AccountCodeMultipleAuthComponents);
525    }
526
527    #[test]
528    fn test_account_component_multiple_auth_procedures() {
529        let code_with_multiple_auth = "
530            @auth_script
531            pub proc auth_basic
532                push.1 drop
533            end
534
535            @auth_script
536            pub proc auth_secondary
537                push.0 drop
538            end
539        ";
540
541        let library = assemble_test_library(
542            "test-account-code-multiple-auth",
543            "test::account_code_multiple_auth",
544            code_with_multiple_auth,
545        );
546        let metadata = AccountComponentMetadata::new("test::multiple_auth");
547        let component = AccountComponent::new(library, vec![], metadata).unwrap();
548
549        let err = AccountCode::from_components(&[component]).unwrap_err();
550
551        assert_matches!(err, AccountError::AccountComponentMultipleAuthProcedures);
552    }
553}