Skip to main content

miden_testing/
executor.rs

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