Skip to main content

miden_testing/
executor.rs

1use alloc::boxed::Box;
2
3#[cfg(test)]
4use miden_processor::DefaultHost;
5use miden_processor::advice::AdviceInputs;
6use miden_processor::{
7    ExecutionError,
8    ExecutionOptions,
9    ExecutionOutput,
10    FastProcessor,
11    Host,
12    Program,
13    StackInputs,
14};
15#[cfg(test)]
16use miden_protocol::assembly::Assembler;
17use miden_protocol::vm::{DebugSourceNodeId, Package, PackageDebugInfo};
18
19use crate::ExecError;
20
21// CODE EXECUTOR
22// ================================================================================================
23
24/// Helper for executing arbitrary code within arbitrary hosts.
25pub(crate) struct CodeExecutor<H> {
26    host: H,
27    stack_inputs: Option<StackInputs>,
28    advice_inputs: AdviceInputs,
29    execution_options: Option<ExecutionOptions>,
30}
31
32impl<H: Host> CodeExecutor<H> {
33    // CONSTRUCTOR
34    // --------------------------------------------------------------------------------------------
35    pub(crate) fn new(host: H) -> Self {
36        Self {
37            host,
38            stack_inputs: None,
39            advice_inputs: AdviceInputs::default(),
40            execution_options: None,
41        }
42    }
43
44    pub fn extend_advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self {
45        self.advice_inputs.extend(advice_inputs);
46        self
47    }
48
49    pub fn stack_inputs(mut self, stack_inputs: StackInputs) -> Self {
50        self.stack_inputs = Some(stack_inputs);
51        self
52    }
53
54    /// Overrides the [`ExecutionOptions`] used to run the program (e.g. to cap `max_cycles`).
55    #[cfg(test)]
56    pub fn execution_options(mut self, options: ExecutionOptions) -> Self {
57        self.execution_options = Some(options);
58        self
59    }
60
61    /// Compiles and runs the desired code in the host and returns the [`Process`] state.
62    #[cfg(test)]
63    pub async fn run(self, code: &str) -> Result<ExecutionOutput, ExecError> {
64        use alloc::borrow::ToOwned;
65        use alloc::sync::Arc;
66
67        use miden_protocol::assembly::debuginfo::{SourceLanguage, Uri};
68        use miden_protocol::assembly::{DefaultSourceManager, SourceManagerSync};
69        use miden_standards::code_builder::CodeBuilder;
70
71        let source_manager: Arc<dyn SourceManagerSync> = Arc::new(DefaultSourceManager::default());
72        let assembler: Assembler = CodeBuilder::with_kernel_library(source_manager.clone()).into();
73
74        // Virtual file name should be unique.
75        let virtual_source_file =
76            source_manager.load(SourceLanguage::Masm, Uri::new("_user_code"), code.to_owned());
77        let package = assembler.assemble_program("tx-context-code", virtual_source_file).unwrap();
78
79        self.execute_package(package).await
80    }
81
82    /// Executes the provided executable [`Package`] and returns the [`Process`] state.
83    ///
84    /// Package-owned debug information is used when present.
85    pub async fn execute_package(
86        self,
87        package: impl Into<Box<Package>>,
88    ) -> Result<ExecutionOutput, ExecError> {
89        let package = package.into();
90        let package_debug_info = package.debug_info().ok().flatten();
91        let entrypoint_source_node = package.entrypoint_source_node();
92        let program = package.try_into_program().expect("package should be executable");
93
94        self.execute_program_with_package_debug_info(
95            program,
96            package_debug_info,
97            entrypoint_source_node,
98        )
99        .await
100    }
101
102    async fn execute_program_with_package_debug_info(
103        mut self,
104        program: Program,
105        package_debug_info: Option<PackageDebugInfo>,
106        entrypoint_source_node: Option<DebugSourceNodeId>,
107    ) -> Result<ExecutionOutput, ExecError> {
108        let stack_inputs = self.stack_inputs.unwrap_or_default();
109
110        let processor = FastProcessor::new(stack_inputs)
111            .with_advice(self.advice_inputs)
112            .map_err(ExecutionError::advice_error_no_context)
113            .map_err(ExecError::new)?
114            .with_options(self.execution_options.unwrap_or_default())
115            .map_err(ExecutionError::advice_error_no_context)
116            .map_err(ExecError::new)?;
117
118        let execution_output = match package_debug_info {
119            Some(package_debug_info) => match entrypoint_source_node {
120                Some(entrypoint_source_node) => {
121                    processor
122                        .execute_with_package_debug_info_at_source_node(
123                            &program,
124                            &package_debug_info,
125                            entrypoint_source_node,
126                            &mut self.host,
127                        )
128                        .await
129                },
130                None => {
131                    processor
132                        .execute_with_package_debug_info(
133                            &program,
134                            &package_debug_info,
135                            &mut self.host,
136                        )
137                        .await
138                },
139            },
140            None => processor.execute(&program, &mut self.host).await,
141        }
142        .map_err(ExecError::new)?;
143
144        Ok(execution_output)
145    }
146}
147
148#[cfg(test)]
149impl CodeExecutor<DefaultHost> {
150    pub fn with_default_host() -> Self {
151        use miden_core_lib::CoreLibrary;
152        use miden_protocol::ProtocolLib;
153        use miden_protocol::transaction::TransactionKernel;
154        use miden_standards::StandardsLib;
155
156        let mut host = DefaultHost::default();
157
158        let core_lib = CoreLibrary::default();
159        host.load_library(core_lib.mast_forest()).unwrap();
160
161        let standards_lib = StandardsLib::default();
162        host.load_library(standards_lib.mast_forest()).unwrap();
163
164        let protocol_lib = ProtocolLib::default();
165        host.load_library(protocol_lib.mast_forest()).unwrap();
166
167        let kernel_lib = TransactionKernel::library();
168        host.load_library(kernel_lib.mast_forest()).unwrap();
169
170        CodeExecutor::new(host)
171    }
172}