Skip to main content

miden_protocol/account/component/
code.rs

1use miden_assembly::Library;
2use miden_assembly::library::ProcedureExport;
3use miden_processor::mast::{MastForest, MastNodeExt};
4
5use crate::account::AccountProcedureRoot;
6use crate::assembly::Path;
7use crate::vm::AdviceMap;
8
9// ACCOUNT COMPONENT CODE
10// ================================================================================================
11
12/// A [`Library`] that has been assembled for use as component code.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct AccountComponentCode(Library);
15
16impl AccountComponentCode {
17    /// Returns a reference to the underlying [`Library`]
18    pub fn as_library(&self) -> &Library {
19        &self.0
20    }
21
22    /// Returns a reference to the code's [`MastForest`]
23    pub fn mast_forest(&self) -> &MastForest {
24        self.0.mast_forest().as_ref()
25    }
26
27    /// Consumes `self` and returns the underlying [`Library`]
28    pub fn into_library(self) -> Library {
29        self.0
30    }
31
32    /// Returns an iterator over the [`AccountProcedureRoot`]s of this component's exported
33    /// procedures.
34    pub fn procedure_roots(&self) -> impl Iterator<Item = AccountProcedureRoot> + '_ {
35        self.0.exports().filter_map(|export| {
36            export.as_procedure().map(|proc_export| {
37                let digest = self.0.mast_forest()[proc_export.node].digest();
38                AccountProcedureRoot::from_raw(digest)
39            })
40        })
41    }
42
43    /// Returns the procedure exports of this component.
44    pub fn exports(&self) -> impl Iterator<Item = &ProcedureExport> + '_ {
45        self.0.exports().filter_map(|export| export.as_procedure())
46    }
47
48    /// Returns the [`AccountProcedureRoot`] of the procedure with the specified path, or `None`
49    /// if it was not found in this component's library.
50    pub fn get_procedure_root_by_path(
51        &self,
52        proc_name: impl AsRef<Path>,
53    ) -> Option<AccountProcedureRoot> {
54        self.0.get_procedure_root_by_path(proc_name).map(AccountProcedureRoot::from_raw)
55    }
56
57    /// Returns a new [AccountComponentCode] with the provided advice map entries merged into the
58    /// underlying [Library]'s [MastForest].
59    ///
60    /// This allows adding advice map entries to an already-compiled account component,
61    /// which is useful when the entries are determined after compilation.
62    pub fn with_advice_map(self, advice_map: AdviceMap) -> Self {
63        if advice_map.is_empty() {
64            return self;
65        }
66
67        Self(self.0.with_advice_map(advice_map))
68    }
69}
70
71impl AsRef<Library> for AccountComponentCode {
72    fn as_ref(&self) -> &Library {
73        self.as_library()
74    }
75}
76
77// CONVERSIONS
78// ================================================================================================
79
80impl From<Library> for AccountComponentCode {
81    fn from(value: Library) -> Self {
82        Self(value)
83    }
84}
85
86impl From<AccountComponentCode> for Library {
87    fn from(value: AccountComponentCode) -> Self {
88        value.into_library()
89    }
90}
91
92// TESTS
93// ================================================================================================
94
95#[cfg(test)]
96mod tests {
97    use alloc::string::ToString;
98    use alloc::sync::Arc;
99
100    use miden_core::{Felt, Word};
101
102    use super::*;
103    use crate::assembly::Assembler;
104
105    #[test]
106    fn test_account_component_code_with_advice_map() {
107        let assembler = Assembler::default();
108        let library = Arc::unwrap_or_clone(
109            assembler
110                .assemble_library(["pub proc test nop end"])
111                .expect("failed to assemble library"),
112        );
113        let component_code = AccountComponentCode::from(library);
114
115        assert!(component_code.mast_forest().advice_map().is_empty());
116
117        // Empty advice map should be a no-op (digest stays the same)
118        let cloned = component_code.clone();
119        let original_digest = cloned.as_library().digest();
120        let component_code = component_code.with_advice_map(AdviceMap::default());
121        assert_eq!(original_digest, component_code.as_library().digest());
122
123        // Non-empty advice map should add entries
124        let key = Word::from([10u32, 20, 30, 40]);
125        let value = vec![Felt::from(200_u8)];
126        let mut advice_map = AdviceMap::default();
127        advice_map.insert(key, value.clone());
128
129        let component_code = component_code.with_advice_map(advice_map);
130
131        let mast = component_code.mast_forest();
132        let stored = mast.advice_map().get(&key).expect("entry should be present");
133        assert_eq!(stored.as_ref(), value.as_slice());
134    }
135
136    #[test]
137    fn test_get_procedure_root_by_path() {
138        let assembler = Assembler::default();
139        let library = Arc::unwrap_or_clone(
140            assembler
141                .assemble_library(["pub proc test_proc nop end"])
142                .expect("failed to assemble library"),
143        );
144        let component_code = AccountComponentCode::from(library);
145
146        // The test library exports exactly one procedure.
147        assert_eq!(component_code.procedure_roots().count(), 1);
148        let expected = component_code.procedure_roots().next().expect("one procedure exported");
149
150        let library_namespace = component_code
151            .as_library()
152            .module_infos()
153            .next()
154            .expect("library should have one module")
155            .path()
156            .to_string();
157        let proc_path = alloc::format!("{library_namespace}::test_proc");
158
159        let root = component_code
160            .get_procedure_root_by_path(proc_path.as_str())
161            .expect("test_proc should be present");
162        assert_eq!(root, expected);
163
164        assert!(component_code.get_procedure_root_by_path("bogus::missing").is_none());
165    }
166}