#![no_std]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
#[cfg(not(target_family = "wasm"))]
use alloc::format;
use alloc::{
string::{String, ToString},
sync::Arc,
vec::Vec,
};
use assembly::{KernelLibrary, Library};
pub use assembly::{LibraryPath, SourceFile, SourceManager, diagnostics::Report};
pub use pretty_assertions::{assert_eq, assert_ne, assert_str_eq};
pub use processor::{
AdviceInputs, AdviceProvider, ContextId, ExecutionError, ExecutionOptions, ExecutionTrace,
Process, ProcessState, VmStateIterator,
};
use processor::{Program, fast::FastProcessor};
#[cfg(not(target_family = "wasm"))]
use proptest::prelude::{Arbitrary, Strategy};
use prover::utils::range;
pub use prover::{MemAdviceProvider, MerkleTreeVC, ProvingOptions, prove};
pub use test_case::test_case;
pub use verifier::{AcceptableOptions, VerifierError, verify};
pub use vm_core::{
EMPTY_WORD, Felt, FieldElement, ONE, StackInputs, StackOutputs, StarkField, WORD_SIZE, Word,
ZERO,
chiplets::hasher::{STATE_WIDTH, hash_elements},
stack::MIN_STACK_DEPTH,
utils::{IntoBytes, ToElements, collections, group_slice_elements},
};
use vm_core::{ProgramInfo, chiplets::hasher::apply_permutation};
pub mod math {
pub use winter_prover::math::{
ExtensionOf, FieldElement, StarkField, ToElements, fft, fields::QuadExtension, polynom,
};
}
pub mod serde {
pub use vm_core::utils::{
ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, SliceReader,
};
}
pub mod crypto;
pub mod host;
use host::TestHost;
#[cfg(not(target_family = "wasm"))]
pub mod rand;
mod test_builders;
#[cfg(not(target_family = "wasm"))]
pub use proptest;
pub type QuadFelt = vm_core::QuadExtension<Felt>;
pub const U32_BOUND: u64 = u32::MAX as u64 + 1;
pub const TRUNCATE_STACK_PROC: &str = "
proc.truncate_stack.4
loc_storew.0 dropw movupw.3
sdepth neq.16
while.true
dropw movupw.3
sdepth neq.16
end
loc_loadw.0
end
";
#[cfg(all(feature = "std", not(target_family = "wasm")))]
#[macro_export]
macro_rules! expect_assembly_error {
($test:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )? $(,)?) => {
let error = $test.compile().expect_err("expected assembly to fail");
match error.downcast::<assembly::AssemblyError>() {
Ok(error) => {
::vm_core::assert_matches!(error, $( $pattern )|+ $( if $guard )?);
}
Err(report) => {
panic!(r#"
assertion failed (expected assembly error, but got a different type):
left: `{:?}`,
right: `{}`"#, report, stringify!($($pattern)|+ $(if $guard)?));
}
}
};
}
#[cfg(all(feature = "std", not(target_family = "wasm")))]
#[macro_export]
macro_rules! expect_exec_error_matches {
($test:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )? $(,)?) => {
match $test.execute() {
Ok(_) => panic!("expected execution to fail @ {}:{}", file!(), line!()),
Err(error) => ::vm_core::assert_matches!(error, $( $pattern )|+ $( if $guard )?),
}
};
}
#[cfg(not(target_family = "wasm"))]
#[macro_export]
macro_rules! assert_diagnostic_lines {
($diagnostic:expr, $($expected:expr),+) => {{
use assembly::testing::Pattern;
let actual = format!("{}", assembly::diagnostics::reporting::PrintDiagnostic::new_without_color($diagnostic));
let lines = actual.lines().filter(|l| !l.trim().is_empty()).zip([$(Pattern::from($expected)),*].into_iter());
for (actual_line, expected) in lines {
expected.assert_match_with_context(actual_line, &actual);
}
}};
}
#[cfg(not(target_family = "wasm"))]
#[macro_export]
macro_rules! assert_assembler_diagnostic {
($test:ident, $($expected:literal),+) => {{
let error = $test
.compile()
.expect_err("expected diagnostic to be raised, but compilation succeeded");
assert_diagnostic_lines!(error, $($expected),*);
}};
($test:ident, $($expected:expr),+) => {{
let error = $test
.compile()
.expect_err("expected diagnostic to be raised, but compilation succeeded");
assert_diagnostic_lines!(error, $($expected),*);
}};
}
pub struct Test {
pub source_manager: Arc<dyn SourceManager>,
pub source: Arc<SourceFile>,
pub kernel_source: Option<Arc<SourceFile>>,
pub stack_inputs: StackInputs,
pub advice_inputs: AdviceInputs,
pub in_debug_mode: bool,
pub libraries: Vec<Library>,
pub add_modules: Vec<(LibraryPath, String)>,
}
impl Test {
pub fn new(name: &str, source: &str, in_debug_mode: bool) -> Self {
let source_manager = Arc::new(assembly::DefaultSourceManager::default());
let source = source_manager.load(name, source.to_string());
Test {
source_manager,
source,
kernel_source: None,
stack_inputs: StackInputs::default(),
advice_inputs: AdviceInputs::default(),
in_debug_mode,
libraries: Vec::default(),
add_modules: Vec::default(),
}
}
pub fn add_module(&mut self, path: assembly::LibraryPath, source: impl ToString) {
self.add_modules.push((path, source.to_string()));
}
#[track_caller]
pub fn expect_stack(&self, final_stack: &[u64]) {
let result = self.get_last_stack_state().as_int_vec();
let expected = resize_to_min_stack_depth(final_stack);
assert_eq!(expected, result, "Expected stack to be {:?}, found {:?}", expected, result);
}
#[track_caller]
pub fn expect_stack_and_memory(
&self,
final_stack: &[u64],
mem_start_addr: u32,
expected_mem: &[u64],
) {
let (program, kernel) = self.compile().expect("Failed to compile test source.");
let mut host = TestHost::new(MemAdviceProvider::from(self.advice_inputs.clone()));
if let Some(kernel) = kernel {
host.load_mast_forest(kernel.mast_forest().clone()).unwrap();
}
for library in &self.libraries {
host.load_mast_forest(library.mast_forest().clone()).unwrap();
}
let mut process = Process::new(
program.kernel().clone(),
self.stack_inputs.clone(),
ExecutionOptions::default().with_debugging(self.in_debug_mode),
)
.with_source_manager(self.source_manager.clone());
process.execute(&program, &mut host).unwrap();
for (addr, mem_value) in
(range(mem_start_addr as usize, expected_mem.len())).zip(expected_mem.iter())
{
let mem_state = process
.chiplets
.memory
.get_value(ContextId::root(), addr as u32)
.unwrap_or(ZERO);
assert_eq!(
*mem_value,
mem_state.as_int(),
"Expected memory [{}] => {:?}, found {:?}",
addr,
mem_value,
mem_state
);
}
self.expect_stack(final_stack);
}
#[cfg(not(target_family = "wasm"))]
pub fn prop_expect_stack(
&self,
final_stack: &[u64],
) -> Result<(), proptest::prelude::TestCaseError> {
let result = self.get_last_stack_state().as_int_vec();
proptest::prop_assert_eq!(resize_to_min_stack_depth(final_stack), result);
Ok(())
}
pub fn compile(&self) -> Result<(Program, Option<KernelLibrary>), Report> {
use assembly::{Assembler, CompileOptions, ast::ModuleKind};
let (assembler, kernel_lib) = if let Some(kernel) = self.kernel_source.clone() {
let kernel_lib =
Assembler::new(self.source_manager.clone()).assemble_kernel(kernel).unwrap();
(
Assembler::with_kernel(self.source_manager.clone(), kernel_lib.clone()),
Some(kernel_lib),
)
} else {
(Assembler::new(self.source_manager.clone()), None)
};
let mut assembler = self
.add_modules
.iter()
.fold(assembler, |assembler, (path, source)| {
assembler
.with_module_and_options(
source,
CompileOptions::new(ModuleKind::Library, path.clone()).unwrap(),
)
.expect("invalid masm source code")
})
.with_debug_mode(self.in_debug_mode);
for library in &self.libraries {
assembler.add_library(library).unwrap();
}
Ok((assembler.assemble_program(self.source.clone())?, kernel_lib))
}
#[track_caller]
pub fn execute(&self) -> Result<ExecutionTrace, ExecutionError> {
let (program, mut host) = self.get_program_and_host();
let mut process = Process::new(
program.kernel().clone(),
self.stack_inputs.clone(),
ExecutionOptions::default().with_debugging(self.in_debug_mode),
)
.with_source_manager(self.source_manager.clone());
let slow_stack_outputs = process.execute(&program, &mut host)?;
let trace = ExecutionTrace::new(process, slow_stack_outputs.clone());
assert_eq!(&program.hash(), trace.program_hash(), "inconsistent program hash");
self.assert_outputs_with_fast_processor(slow_stack_outputs);
Ok(trace)
}
pub fn execute_process(&self) -> Result<(Process, TestHost), ExecutionError> {
let (program, mut host) = self.get_program_and_host();
let mut process = Process::new(
program.kernel().clone(),
self.stack_inputs.clone(),
ExecutionOptions::default().with_debugging(self.in_debug_mode),
)
.with_source_manager(self.source_manager.clone());
let stack_outputs = process.execute(&program, &mut host)?;
self.assert_outputs_with_fast_processor(stack_outputs);
Ok((process, host))
}
pub fn prove_and_verify(&self, pub_inputs: Vec<u64>, test_fail: bool) {
let (program, mut host) = self.get_program_and_host();
let stack_inputs = StackInputs::try_from_ints(pub_inputs).unwrap();
let (mut stack_outputs, proof) = prover::prove(
&program,
stack_inputs.clone(),
&mut host,
ProvingOptions::default(),
self.source_manager.clone(),
)
.unwrap();
self.assert_outputs_with_fast_processor(stack_outputs.clone());
let program_info = ProgramInfo::from(program);
if test_fail {
stack_outputs.stack_mut()[0] += ONE;
assert!(verifier::verify(program_info, stack_inputs, stack_outputs, proof).is_err());
} else {
let result = verifier::verify(program_info, stack_inputs, stack_outputs, proof);
assert!(result.is_ok(), "error: {result:?}");
}
}
pub fn execute_iter(&self) -> VmStateIterator {
let (program, mut host) = self.get_program_and_host();
let mut process = Process::new(
program.kernel().clone(),
self.stack_inputs.clone(),
ExecutionOptions::default().with_debugging(self.in_debug_mode),
)
.with_source_manager(self.source_manager.clone());
let result = process.execute(&program, &mut host);
if let Ok(stack_outputs) = &result {
assert_eq!(
program.hash(),
process.decoder.program_hash().into(),
"inconsistent program hash"
);
self.assert_outputs_with_fast_processor(stack_outputs.clone());
}
VmStateIterator::new(process, result)
}
#[track_caller]
pub fn get_last_stack_state(&self) -> StackOutputs {
let trace = self.execute().unwrap();
trace.last_stack_state()
}
fn get_program_and_host(&self) -> (Program, TestHost) {
let (program, kernel) = self.compile().expect("Failed to compile test source.");
let mut host = TestHost::new(MemAdviceProvider::from(self.advice_inputs.clone()));
if let Some(kernel) = kernel {
host.load_mast_forest(kernel.mast_forest().clone()).unwrap();
}
for library in &self.libraries {
host.load_mast_forest(library.mast_forest().clone()).unwrap();
}
(program, host)
}
fn assert_outputs_with_fast_processor(&self, slow_stack_outputs: StackOutputs) {
let (program, mut host) = self.get_program_and_host();
let stack_inputs: Vec<Felt> = self.stack_inputs.clone().into_iter().rev().collect();
let fast_process = FastProcessor::new(&stack_inputs);
let fast_stack_outputs = fast_process.execute(&program, &mut host).unwrap();
assert_eq!(
slow_stack_outputs, fast_stack_outputs,
"stack outputs do not match between slow and fast processors"
);
}
}
pub fn felt_slice_to_ints(values: &[Felt]) -> Vec<u64> {
values.iter().map(|e| (*e).as_int()).collect()
}
pub fn resize_to_min_stack_depth(values: &[u64]) -> Vec<u64> {
let mut result: Vec<u64> = values.to_vec();
result.resize(MIN_STACK_DEPTH, 0);
result
}
#[cfg(not(target_family = "wasm"))]
pub fn prop_randw<T: Arbitrary>() -> impl Strategy<Value = Vec<T>> {
use proptest::prelude::{any, prop};
prop::collection::vec(any::<T>(), 4)
}
pub fn build_expected_perm(values: &[u64]) -> [Felt; STATE_WIDTH] {
let mut expected = [ZERO; STATE_WIDTH];
for (&value, result) in values.iter().zip(expected.iter_mut()) {
*result = Felt::new(value);
}
apply_permutation(&mut expected);
expected.reverse();
expected
}
pub fn build_expected_hash(values: &[u64]) -> [Felt; 4] {
let digest = hash_elements(&values.iter().map(|&v| Felt::new(v)).collect::<Vec<_>>());
let mut expected: [Felt; 4] = digest.into();
expected.reverse();
expected
}
#[cfg(all(feature = "std", not(target_family = "wasm")))]
pub fn push_inputs(inputs: &[u64]) -> String {
let mut result = String::new();
inputs.iter().for_each(|v| result.push_str(&format!("push.{v}\n")));
result
}