Skip to main content

miden_protocol/account/component/
code.rs

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