Skip to main content

miden_test_utils/
lib.rs

1#![no_std]
2
3extern crate alloc;
4
5#[cfg(feature = "std")]
6extern crate std;
7
8use alloc::{
9    format,
10    string::{String, ToString},
11    sync::Arc,
12    vec,
13    vec::Vec,
14};
15
16use miden_assembly::{KernelLibrary, Library, Parse, diagnostics::reporting::PrintDiagnostic};
17pub use miden_assembly::{
18    Path,
19    debuginfo::{DefaultSourceManager, SourceFile, SourceLanguage, SourceManager},
20    diagnostics::Report,
21};
22pub use miden_core::{
23    EMPTY_WORD, Felt, ONE, WORD_SIZE, Word, ZERO,
24    chiplets::hasher::{STATE_WIDTH, hash_elements},
25    field::{Field, PrimeCharacteristicRing, PrimeField64, QuadFelt},
26    program::{MIN_STACK_DEPTH, StackInputs, StackOutputs},
27    utils::{IntoBytes, ToElements, group_slice_elements},
28};
29use miden_core::{
30    chiplets::hasher::apply_permutation,
31    events::{EventName, SystemEvent},
32    program::ProgramInfo,
33};
34pub use miden_processor::{
35    ContextId, ExecutionError, ProcessorState,
36    advice::{AdviceInputs, AdviceProvider, AdviceStackBuilder},
37    trace::ExecutionTrace,
38};
39use miden_processor::{
40    DefaultDebugHandler, DefaultHost, ExecutionOutput, FastProcessor, Program, TraceBuildInputs,
41    event::EventHandler, trace::build_trace,
42};
43#[cfg(not(target_arch = "wasm32"))]
44pub use miden_prover::prove_sync;
45pub use miden_prover::{ProvingOptions, prove};
46pub use miden_verifier::verify;
47pub use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
48#[cfg(not(target_family = "wasm"))]
49use proptest::prelude::{Arbitrary, Strategy};
50pub use test_case::test_case;
51
52pub mod math {
53    pub use miden_core::{
54        field::{ExtensionField, Field, PrimeField64, QuadFelt},
55        utils::ToElements,
56    };
57}
58
59pub use miden_core::serde;
60
61pub mod crypto;
62
63#[cfg(not(target_family = "wasm"))]
64pub mod rand;
65
66mod test_builders;
67
68#[cfg(not(target_family = "wasm"))]
69pub use proptest;
70// CONSTANTS
71// ================================================================================================
72
73/// A value just over what a [u32] integer can hold.
74pub const U32_BOUND: u64 = u32::MAX as u64 + 1;
75
76/// A source code of the `truncate_stack` procedure.
77pub const TRUNCATE_STACK_PROC: &str = "
78@locals(4)
79proc truncate_stack
80     loc_storew_be.0 dropw movupw.3
81    sdepth neq.16
82    while.true
83        dropw movupw.3
84        sdepth neq.16
85    end
86    loc_loadw_be.0
87end
88";
89
90// TEST HANDLER
91// ================================================================================================
92
93/// Asserts that running the given assembler test will result in the expected error.
94#[cfg(all(feature = "std", not(target_family = "wasm")))]
95#[macro_export]
96macro_rules! expect_assembly_error {
97    ($test:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )? $(,)?) => {
98        let error = $test.compile().expect_err("expected assembly to fail");
99        match error.downcast::<::miden_assembly::AssemblyError>() {
100            Ok(error) => {
101                ::miden_core::assert_matches!(error, $( $pattern )|+ $( if $guard )?);
102            }
103            Err(report) => {
104                panic!(r#"
105assertion failed (expected assembly error, but got a different type):
106    left: `{:?}`,
107    right: `{}`"#, report, stringify!($($pattern)|+ $(if $guard)?));
108            }
109        }
110    };
111}
112
113/// Asserts that running the given execution test will result in the expected error.
114#[cfg(all(feature = "std", not(target_family = "wasm")))]
115#[macro_export]
116macro_rules! expect_exec_error_matches {
117    ($test:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )? $(,)?) => {
118        match $test.execute() {
119            Ok(_) => panic!("expected execution to fail @ {}:{}", file!(), line!()),
120            Err(error) => ::miden_core::assert_matches!(error, $( $pattern )|+ $( if $guard )?),
121        }
122    };
123}
124
125/// Like [miden_assembly::testing::assert_diagnostic], but matches each non-empty line of the
126/// rendered output to a corresponding pattern.
127///
128/// So if the output has 3 lines, the second of which is empty, and you provide 2 patterns, the
129/// assertion passes if the first line matches the first pattern, and the third line matches the
130/// second pattern - the second line is ignored because it is empty.
131#[cfg(not(target_family = "wasm"))]
132#[macro_export]
133macro_rules! assert_diagnostic_lines {
134    ($diagnostic:expr, $($expected:expr),+) => {{
135        use miden_assembly::testing::Pattern;
136        let actual = format!("{}", miden_assembly::diagnostics::reporting::PrintDiagnostic::new_without_color($diagnostic));
137        let lines = actual.lines().filter(|l| !l.trim().is_empty()).zip([$(Pattern::from($expected)),*].into_iter());
138        for (actual_line, expected) in lines {
139            expected.assert_match_with_context(actual_line, &actual);
140        }
141    }};
142}
143
144#[cfg(not(target_family = "wasm"))]
145#[macro_export]
146macro_rules! assert_assembler_diagnostic {
147    ($test:ident, $($expected:literal),+) => {{
148        let error = $test
149            .compile()
150            .expect_err("expected diagnostic to be raised, but compilation succeeded");
151        assert_diagnostic_lines!(error, $($expected),*);
152    }};
153
154    ($test:ident, $($expected:expr),+) => {{
155        let error = $test
156            .compile()
157            .expect_err("expected diagnostic to be raised, but compilation succeeded");
158        assert_diagnostic_lines!(error, $($expected),*);
159    }};
160}
161
162/// This is a container for the data required to run tests, which allows for running several
163/// different types of tests.
164///
165/// Types of valid result tests:
166/// - Execution test: check that running a program compiled from the given source has the specified
167///   results for the given (optional) inputs.
168/// - Proptest: run an execution test inside a proptest.
169///
170/// Types of failure tests:
171/// - Assembly error test: check that attempting to compile the given source causes an AssemblyError
172///   which contains the specified substring.
173/// - Execution error test: check that running a program compiled from the given source causes an
174///   ExecutionError which contains the specified substring.
175pub struct Test {
176    pub source_manager: Arc<DefaultSourceManager>,
177    pub source: Arc<SourceFile>,
178    pub kernel_source: Option<Arc<SourceFile>>,
179    pub stack_inputs: StackInputs,
180    pub advice_inputs: AdviceInputs,
181    pub in_debug_mode: bool,
182    pub libraries: Vec<Library>,
183    pub handlers: Vec<(EventName, Arc<dyn EventHandler>)>,
184    pub add_modules: Vec<(Arc<Path>, String)>,
185}
186
187// BUFFER WRITER FOR TESTING
188// ================================================================================================
189
190/// A writer that buffers output in a String for testing debug output.
191#[derive(Default)]
192pub struct BufferWriter {
193    pub buffer: String,
194}
195
196impl core::fmt::Write for BufferWriter {
197    fn write_str(&mut self, s: &str) -> core::fmt::Result {
198        self.buffer.push_str(s);
199        Ok(())
200    }
201}
202
203impl Test {
204    // CONSTRUCTOR
205    // --------------------------------------------------------------------------------------------
206
207    /// Creates the simplest possible new test, with only a source string and no inputs.
208    pub fn new(name: &str, source: &str, in_debug_mode: bool) -> Self {
209        let source_manager = Arc::new(DefaultSourceManager::default());
210        let source = source_manager.load(SourceLanguage::Masm, name.into(), source.to_string());
211        Self {
212            source_manager,
213            source,
214            kernel_source: None,
215            stack_inputs: StackInputs::default(),
216            advice_inputs: AdviceInputs::default(),
217            in_debug_mode,
218            libraries: Vec::default(),
219            handlers: Vec::new(),
220            add_modules: Vec::default(),
221        }
222    }
223
224    /// Adds kernel source to this test so it is assembled and linked during compilation.
225    #[track_caller]
226    pub fn with_kernel(self, kernel_source: impl ToString) -> Self {
227        self.with_kernel_source(
228            format!("kernel{}", core::panic::Location::caller().line()),
229            kernel_source,
230        )
231    }
232
233    /// Adds kernel source to this test so it is assembled and linked during compilation.
234    pub fn with_kernel_source(
235        mut self,
236        kernel_name: impl Into<String>,
237        kernel_source: impl ToString,
238    ) -> Self {
239        self.kernel_source = Some(self.source_manager.load(
240            SourceLanguage::Masm,
241            kernel_name.into().into(),
242            kernel_source.to_string(),
243        ));
244        self
245    }
246
247    /// Add an extra module to link in during assembly
248    pub fn add_module(&mut self, path: impl AsRef<Path>, source: impl ToString) {
249        self.add_modules.push((path.as_ref().into(), source.to_string()));
250    }
251
252    /// Add a handler for a specific event when running the `Host`.
253    pub fn add_event_handler(&mut self, event: EventName, handler: impl EventHandler) {
254        self.add_event_handlers(vec![(event, Arc::new(handler))]);
255    }
256
257    /// Add a handler for a specific event when running the `Host`.
258    pub fn add_event_handlers(&mut self, handlers: Vec<(EventName, Arc<dyn EventHandler>)>) {
259        for (event, handler) in handlers {
260            let event_name = event.as_str();
261            if SystemEvent::from_name(event_name).is_some() {
262                panic!("tried to register handler for reserved system event: {event_name}")
263            }
264            let event_id = event.to_event_id();
265            if self.handlers.iter().any(|(e, _)| e.to_event_id() == event_id) {
266                panic!("handler for event '{event_name}' was already added")
267            }
268            self.handlers.push((event, handler));
269        }
270    }
271
272    // TEST METHODS
273    // --------------------------------------------------------------------------------------------
274
275    /// Builds a final stack from the provided stack-ordered array and asserts that executing the
276    /// test will result in the expected final stack state.
277    #[cfg(not(target_arch = "wasm32"))]
278    #[track_caller]
279    pub fn expect_stack(&self, final_stack: &[u64]) {
280        let result = self.get_last_stack_state().as_int_vec();
281        let expected = resize_to_min_stack_depth(final_stack);
282        assert_eq!(expected, result, "Expected stack to be {:?}, found {:?}", expected, result);
283    }
284
285    /// Executes the test and validates that the process memory has the elements of `expected_mem`
286    /// at address `mem_start_addr` and that the end of the stack execution trace matches the
287    /// `final_stack`.
288    #[cfg(not(target_arch = "wasm32"))]
289    #[track_caller]
290    pub fn expect_stack_and_memory(
291        &self,
292        final_stack: &[u64],
293        mem_start_addr: u32,
294        expected_mem: &[u64],
295    ) {
296        // compile the program
297        let (program, host) = self.get_program_and_host();
298        let mut host = host.with_source_manager(self.source_manager.clone());
299
300        // execute the test
301        let processor = FastProcessor::new(self.stack_inputs)
302            .with_advice(self.advice_inputs.clone())
303            .with_debugging(self.in_debug_mode)
304            .with_tracing(self.in_debug_mode);
305        let execution_output = processor.execute_sync(&program, &mut host).unwrap();
306
307        // validate the memory state
308        for (addr, mem_value) in ((mem_start_addr as usize)
309            ..(mem_start_addr as usize + expected_mem.len()))
310            .zip(expected_mem.iter())
311        {
312            let mem_state = execution_output
313                .memory
314                .read_element(ContextId::root(), Felt::from_u32(addr as u32))
315                .unwrap();
316            assert_eq!(
317                *mem_value,
318                mem_state.as_canonical_u64(),
319                "Expected memory [{}] => {:?}, found {:?}",
320                addr,
321                mem_value,
322                mem_state
323            );
324        }
325
326        // validate the stack states
327        self.expect_stack(final_stack);
328    }
329
330    /// Asserts that executing the test inside a proptest results in the expected final stack state.
331    /// The proptest will return a test failure instead of panicking if the assertion condition
332    /// fails.
333    #[cfg(not(target_family = "wasm"))]
334    pub fn prop_expect_stack(
335        &self,
336        final_stack: &[u64],
337    ) -> Result<(), proptest::prelude::TestCaseError> {
338        let result = self.get_last_stack_state().as_int_vec();
339        proptest::prop_assert_eq!(resize_to_min_stack_depth(final_stack), result);
340
341        Ok(())
342    }
343
344    // UTILITY METHODS
345    // --------------------------------------------------------------------------------------------
346
347    /// Compiles a test's source and returns the resulting Program together with the associated
348    /// kernel library (when specified).
349    ///
350    /// # Errors
351    /// Returns an error if compilation of the program source or the kernel fails.
352    pub fn compile(&self) -> Result<(Program, Option<KernelLibrary>), Report> {
353        use miden_assembly::{Assembler, ParseOptions, ast::ModuleKind};
354
355        // Enable debug tracing to stderr via the MIDEN_LOG environment variable, if present
356        #[cfg(not(target_family = "wasm"))]
357        {
358            let _ = env_logger::Builder::from_env("MIDEN_LOG").format_timestamp(None).try_init();
359        }
360
361        let (assembler, kernel_lib) = if let Some(kernel) = self.kernel_source.clone() {
362            let kernel_lib =
363                Assembler::new(self.source_manager.clone()).assemble_kernel(kernel).unwrap();
364
365            (
366                Assembler::with_kernel(self.source_manager.clone(), kernel_lib.clone()),
367                Some(kernel_lib),
368            )
369        } else {
370            (Assembler::new(self.source_manager.clone()), None)
371        };
372
373        let mut assembler =
374            self.add_modules.iter().fold(assembler, |mut assembler, (path, source)| {
375                let module = source
376                    .parse_with_options(
377                        self.source_manager.clone(),
378                        ParseOptions::new(ModuleKind::Library, path.clone()),
379                    )
380                    .expect("invalid masm source code");
381                assembler.compile_and_statically_link(module).expect("failed to link module");
382                assembler
383            });
384        // Debug mode is now always enabled
385        for library in &self.libraries {
386            assembler.link_dynamic_library(library).unwrap();
387        }
388
389        Ok((assembler.assemble_program(self.source.clone())?, kernel_lib))
390    }
391
392    /// Compiles the test's source to a Program and executes it with the tests inputs. Returns a
393    /// resulting execution trace or error.
394    ///
395    /// Internally, this also checks that the slow and fast processors agree on the stack
396    /// outputs.
397    #[cfg(not(target_arch = "wasm32"))]
398    #[track_caller]
399    pub fn execute(&self) -> Result<ExecutionTrace, ExecutionError> {
400        // Note: we fix a large fragment size here, as we're not testing the fragment boundaries
401        // with these tests (which are tested separately), but rather only the per-fragment trace
402        // generation logic - though not too big so as to over-allocate memory.
403        const FRAGMENT_SIZE: usize = 1 << 16;
404
405        let (program, host) = self.get_program_and_host();
406        let mut host = host.with_source_manager(self.source_manager.clone());
407
408        let fast_stack_result = {
409            let fast_processor = FastProcessor::new_with_options(
410                self.stack_inputs,
411                self.advice_inputs.clone(),
412                miden_processor::ExecutionOptions::default()
413                    .with_debugging(self.in_debug_mode)
414                    .with_core_trace_fragment_size(FRAGMENT_SIZE)
415                    .unwrap(),
416            );
417            fast_processor.execute_trace_inputs_sync(&program, &mut host)
418        };
419
420        // compare fast and slow processors' stack outputs
421        self.assert_result_with_step_execution(&fast_stack_result);
422
423        fast_stack_result.and_then(|trace_inputs| {
424            let trace = build_trace(trace_inputs)?;
425
426            assert_eq!(&program.hash(), trace.program_hash(), "inconsistent program hash");
427            Ok(trace)
428        })
429    }
430
431    /// Compiles the test's source to a Program and executes it with the tests inputs.
432    ///
433    /// Returns the [`ExecutionOutput`] once execution is finished.
434    #[cfg(not(target_arch = "wasm32"))]
435    pub fn execute_for_output(&self) -> Result<(ExecutionOutput, DefaultHost), ExecutionError> {
436        let (program, host) = self.get_program_and_host();
437        let mut host = host.with_source_manager(self.source_manager.clone());
438
439        let processor = FastProcessor::new(self.stack_inputs)
440            .with_advice(self.advice_inputs.clone())
441            .with_debugging(true)
442            .with_tracing(true);
443
444        processor.execute_sync(&program, &mut host).map(|output| (output, host))
445    }
446
447    /// Compiles the test's source to a Program and executes it with the tests inputs. Returns
448    /// the [`StackOutputs`] and a [`String`] containing all debug output.
449    ///
450    /// If the execution fails, the output is printed `stderr`.
451    #[cfg(not(target_arch = "wasm32"))]
452    pub fn execute_with_debug_buffer(&self) -> Result<(StackOutputs, String), ExecutionError> {
453        let debug_handler = DefaultDebugHandler::new(BufferWriter::default());
454
455        let (program, host) = self.get_program_and_host();
456        let mut host = host
457            .with_source_manager(self.source_manager.clone())
458            .with_debug_handler(debug_handler);
459
460        let processor = FastProcessor::new(self.stack_inputs)
461            .with_advice(self.advice_inputs.clone())
462            .with_debugging(true)
463            .with_tracing(true);
464
465        let stack_result = processor.execute_sync(&program, &mut host);
466
467        let debug_output = host.debug_handler().writer().buffer.clone();
468
469        match stack_result {
470            Ok(exec_output) => Ok((exec_output.stack, debug_output)),
471            Err(err) => {
472                // If we get an error, we print the output as an error
473                #[cfg(feature = "std")]
474                std::eprintln!("{}", debug_output);
475                Err(err)
476            },
477        }
478    }
479
480    /// Compiles the test's code into a program, then generates and verifies a STARK proof of
481    /// execution. When `test_fail` is true, forces a failure by modifying the first output.
482    ///
483    /// Prefer [`check_constraints`](Self::check_constraints) for constraint validation — it is
484    /// much faster and provides better error diagnostics. Use this method only when you need to
485    /// exercise the full STARK prove/verify pipeline (e.g., testing proof serialization,
486    /// verifier logic, or precompile request handling).
487    #[cfg(not(target_arch = "wasm32"))]
488    pub fn prove_and_verify(&self, pub_inputs: Vec<u64>, test_fail: bool) {
489        let (program, mut host) = self.get_program_and_host();
490        let stack_inputs = StackInputs::try_from_ints(pub_inputs).unwrap();
491        let (mut stack_outputs, proof) = miden_prover::prove_sync(
492            &program,
493            stack_inputs,
494            self.advice_inputs.clone(),
495            &mut host,
496            miden_processor::ExecutionOptions::default(),
497            ProvingOptions::default(),
498        )
499        .unwrap();
500
501        let program_info = ProgramInfo::from(program);
502        if test_fail {
503            stack_outputs.as_mut()[0] += ONE;
504            assert!(
505                miden_verifier::verify(program_info, stack_inputs, stack_outputs, proof).is_err()
506            );
507        } else {
508            let result = miden_verifier::verify(program_info, stack_inputs, stack_outputs, proof);
509            assert!(result.is_ok(), "error: {result:?}");
510        }
511    }
512
513    /// Executes the test program and checks all AIR constraints without generating a STARK proof.
514    ///
515    /// This is the recommended way to validate constraints in tests. It delegates to
516    /// [`ExecutionTrace::check_constraints`], which is much faster than the
517    /// full prove/verify pipeline and provides better error diagnostics. Use
518    /// [`prove_and_verify`](Self::prove_and_verify) only when you need to exercise the
519    /// complete STARK proof generation and verification flow.
520    ///
521    /// # Panics
522    ///
523    /// Panics if execution fails or if any AIR constraint evaluates to nonzero on any row.
524    #[cfg(not(target_arch = "wasm32"))]
525    #[track_caller]
526    pub fn check_constraints(&self) {
527        let trace = self
528            .execute()
529            .inspect_err(|_err| {
530                #[cfg(feature = "std")]
531                std::eprintln!("{}", PrintDiagnostic::new_without_color(_err))
532            })
533            .expect("failed to execute");
534        trace.check_constraints();
535    }
536
537    /// Returns the last state of the stack after executing a test.
538    #[cfg(not(target_arch = "wasm32"))]
539    #[track_caller]
540    pub fn get_last_stack_state(&self) -> StackOutputs {
541        let trace = self
542            .execute()
543            .inspect_err(|_err| {
544                #[cfg(feature = "std")]
545                std::eprintln!("{}", PrintDiagnostic::new_without_color(_err))
546            })
547            .expect("failed to execute");
548
549        trace.last_stack_state()
550    }
551
552    // HELPERS
553    // ------------------------------------------------------------------------------------------
554
555    /// Returns the program and host for the test.
556    ///
557    /// The host is initialized with the advice inputs provided in the test, as well as the kernel
558    /// and library MAST forests.
559    fn get_program_and_host(&self) -> (Program, DefaultHost) {
560        let (program, kernel) = self.compile().expect("Failed to compile test source.");
561        let mut host = DefaultHost::default();
562        if let Some(kernel) = kernel {
563            host.load_library(kernel.mast_forest()).unwrap();
564        }
565        for library in &self.libraries {
566            host.load_library(library.mast_forest()).unwrap();
567        }
568        for (event, handler) in &self.handlers {
569            host.register_handler(event.clone(), handler.clone()).unwrap();
570        }
571
572        (program, host)
573    }
574
575    fn assert_result_with_step_execution(
576        &self,
577        fast_result: &Result<TraceBuildInputs, ExecutionError>,
578    ) {
579        fn compare_results(
580            left_result: Result<StackOutputs, &ExecutionError>,
581            right_result: &Result<StackOutputs, ExecutionError>,
582            left_name: &str,
583            right_name: &str,
584        ) {
585            match (left_result, right_result) {
586                (Ok(left_stack_outputs), Ok(right_stack_outputs)) => {
587                    assert_eq!(
588                        left_stack_outputs, *right_stack_outputs,
589                        "stack outputs do not match between {left_name} and {right_name}"
590                    );
591                },
592                (Err(left_err), Err(right_err)) => {
593                    // assert that diagnostics match
594                    let right_diagnostic =
595                        format!("{}", PrintDiagnostic::new_without_color(right_err));
596                    let left_diagnostic =
597                        format!("{}", PrintDiagnostic::new_without_color(left_err));
598
599                    assert_eq!(
600                        left_diagnostic, right_diagnostic,
601                        "diagnostics do not match between {left_name} and {right_name}:\n{left_name}: {}\n{right_name}: {}",
602                        left_diagnostic, right_diagnostic
603                    );
604                },
605                (Ok(_), Err(right_err)) => {
606                    let right_diagnostic =
607                        format!("{}", PrintDiagnostic::new_without_color(right_err));
608                    panic!(
609                        "expected error, but {left_name} succeeded. {right_name} error:\n{right_diagnostic}"
610                    );
611                },
612                (Err(left_err), Ok(_)) => {
613                    panic!(
614                        "expected success, but {left_name} failed. {left_name} error:\n{left_err}"
615                    );
616                },
617            }
618        }
619
620        let (program, host) = self.get_program_and_host();
621        let mut host = host.with_source_manager(self.source_manager.clone());
622
623        let fast_result_by_step = {
624            let fast_process = FastProcessor::new(self.stack_inputs)
625                .with_advice(self.advice_inputs.clone())
626                .with_debugging(self.in_debug_mode)
627                .with_tracing(self.in_debug_mode);
628            fast_process.execute_by_step_sync(&program, &mut host)
629        };
630
631        compare_results(
632            fast_result.as_ref().map(|trace_inputs| *trace_inputs.stack_outputs()),
633            &fast_result_by_step,
634            "fast processor",
635            "fast processor by step",
636        );
637    }
638}
639
640// HELPER FUNCTIONS
641// ================================================================================================
642
643/// Appends a Word to an operand stack Vec.
644pub fn append_word_to_vec(target: &mut Vec<u64>, word: Word) {
645    target.extend(word.iter().map(Felt::as_canonical_u64));
646}
647
648/// Converts a slice of Felts into a vector of u64 values.
649pub fn felt_slice_to_ints(values: &[Felt]) -> Vec<u64> {
650    values.iter().map(|e| (*e).as_canonical_u64()).collect()
651}
652
653pub fn resize_to_min_stack_depth(values: &[u64]) -> Vec<u64> {
654    let mut result: Vec<u64> = values.to_vec();
655    result.resize(MIN_STACK_DEPTH, 0);
656    result
657}
658
659/// A proptest strategy for generating a random word with 4 values of type T.
660#[cfg(not(target_family = "wasm"))]
661pub fn prop_randw<T: Arbitrary>() -> impl Strategy<Value = Vec<T>> {
662    use proptest::prelude::{any, prop};
663    prop::collection::vec(any::<T>(), 4)
664}
665
666/// Given a hasher state, perform one permutation.
667///
668/// This helper reconstructs that state, applies a permutation, and returns the resulting
669/// `[RATE0',RATE1',CAP']` back in stack order.
670pub fn build_expected_perm(values: &[u64]) -> [Felt; STATE_WIDTH] {
671    assert!(values.len() >= STATE_WIDTH, "expected at least 12 values for hperm test");
672
673    // Reconstruct the internal Poseidon2 state from the initial stack:
674    // stack[0..12] = [v0, ..., v11]
675    // => state[0..12] = stack[0..12] in [RATE0,RATE1,CAPACITY] layout.
676    let mut state = [ZERO; STATE_WIDTH];
677    for i in 0..STATE_WIDTH {
678        state[i] = Felt::new(values[i]);
679    }
680
681    // Apply the permutation
682    apply_permutation(&mut state);
683
684    // Map internal state back to stack layout [RATE0', RATE1', CAP']
685    let mut out = [ZERO; STATE_WIDTH];
686    out[..STATE_WIDTH].copy_from_slice(&state[..STATE_WIDTH]);
687
688    out
689}
690
691pub fn build_expected_hash(values: &[u64]) -> [Felt; 4] {
692    let digest = hash_elements(&values.iter().map(|&v| Felt::new(v)).collect::<Vec<_>>());
693    digest.into()
694}
695
696// Generates the MASM code which pushes the input values during the execution of the program.
697#[cfg(all(feature = "std", not(target_family = "wasm")))]
698pub fn push_inputs(inputs: &[u64]) -> String {
699    let mut result = String::new();
700
701    inputs.iter().for_each(|v| result.push_str(&format!("push.{v}\n")));
702    result
703}
704
705/// Helper function to get column name for debugging
706pub fn get_column_name(col_idx: usize) -> String {
707    use miden_air::trace::{
708        CLK_COL_IDX, CTX_COL_IDX, DECODER_TRACE_OFFSET, FN_HASH_OFFSET, RANGE_CHECK_TRACE_OFFSET,
709        STACK_TRACE_OFFSET,
710        decoder::{
711            ADDR_COL_IDX, GROUP_COUNT_COL_IDX, HASHER_STATE_OFFSET, IN_SPAN_COL_IDX,
712            NUM_HASHER_COLUMNS, NUM_OP_BATCH_FLAGS, NUM_OP_BITS, NUM_OP_BITS_EXTRA_COLS,
713            OP_BATCH_FLAGS_OFFSET, OP_BITS_EXTRA_COLS_OFFSET, OP_BITS_OFFSET, OP_INDEX_COL_IDX,
714        },
715        stack::{B0_COL_IDX, B1_COL_IDX, H0_COL_IDX, STACK_TOP_OFFSET},
716    };
717
718    match col_idx {
719        // System columns
720        CLK_COL_IDX => "clk".to_string(),
721        CTX_COL_IDX => "ctx".to_string(),
722        i if (FN_HASH_OFFSET..FN_HASH_OFFSET + 4).contains(&i) => {
723            format!("fn_hash[{}]", i - FN_HASH_OFFSET)
724        },
725
726        // Decoder columns
727        i if i == DECODER_TRACE_OFFSET + ADDR_COL_IDX => "decoder_addr".to_string(),
728        i if (DECODER_TRACE_OFFSET + OP_BITS_OFFSET
729            ..DECODER_TRACE_OFFSET + OP_BITS_OFFSET + NUM_OP_BITS)
730            .contains(&i) =>
731        {
732            format!("op_bits[{}]", i - (DECODER_TRACE_OFFSET + OP_BITS_OFFSET))
733        },
734        i if (DECODER_TRACE_OFFSET + HASHER_STATE_OFFSET
735            ..DECODER_TRACE_OFFSET + HASHER_STATE_OFFSET + NUM_HASHER_COLUMNS)
736            .contains(&i) =>
737        {
738            format!("hasher_state[{}]", i - (DECODER_TRACE_OFFSET + HASHER_STATE_OFFSET))
739        },
740        i if i == DECODER_TRACE_OFFSET + IN_SPAN_COL_IDX => "in_span".to_string(),
741        i if i == DECODER_TRACE_OFFSET + GROUP_COUNT_COL_IDX => "group_count".to_string(),
742        i if i == DECODER_TRACE_OFFSET + OP_INDEX_COL_IDX => "op_index".to_string(),
743        i if (DECODER_TRACE_OFFSET + OP_BATCH_FLAGS_OFFSET
744            ..DECODER_TRACE_OFFSET + OP_BATCH_FLAGS_OFFSET + NUM_OP_BATCH_FLAGS)
745            .contains(&i) =>
746        {
747            format!("op_batch_flag[{}]", i - (DECODER_TRACE_OFFSET + OP_BATCH_FLAGS_OFFSET))
748        },
749        i if (DECODER_TRACE_OFFSET + OP_BITS_EXTRA_COLS_OFFSET
750            ..DECODER_TRACE_OFFSET + OP_BITS_EXTRA_COLS_OFFSET + NUM_OP_BITS_EXTRA_COLS)
751            .contains(&i) =>
752        {
753            format!("op_bits_extra[{}]", i - (DECODER_TRACE_OFFSET + OP_BITS_EXTRA_COLS_OFFSET))
754        },
755        i if (DECODER_TRACE_OFFSET + OP_BITS_EXTRA_COLS_OFFSET
756            ..DECODER_TRACE_OFFSET + OP_BITS_EXTRA_COLS_OFFSET + NUM_OP_BITS_EXTRA_COLS)
757            .contains(&i) =>
758        {
759            format!("op_bits_extra[{}]", i - (DECODER_TRACE_OFFSET + OP_BITS_EXTRA_COLS_OFFSET))
760        },
761
762        // Stack columns
763        i if (STACK_TRACE_OFFSET + STACK_TOP_OFFSET
764            ..STACK_TRACE_OFFSET + STACK_TOP_OFFSET + MIN_STACK_DEPTH)
765            .contains(&i) =>
766        {
767            format!("stack[{}]", i - (STACK_TRACE_OFFSET + STACK_TOP_OFFSET))
768        },
769        i if i == STACK_TRACE_OFFSET + B0_COL_IDX => "stack_b0".to_string(),
770        i if i == STACK_TRACE_OFFSET + B1_COL_IDX => "stack_b1".to_string(),
771        i if i == STACK_TRACE_OFFSET + H0_COL_IDX => "stack_h0".to_string(),
772
773        // Range check columns
774        i if i >= RANGE_CHECK_TRACE_OFFSET => {
775            format!("range_check[{}]", i - RANGE_CHECK_TRACE_OFFSET)
776        },
777
778        // Default case
779        _ => format!("unknown_col[{}]", col_idx),
780    }
781}