use alloc::{string::ToString, sync::Arc};
use assembly::{Assembler, DefaultSourceManager};
use miden_air::ExecutionOptions;
use rstest::rstest;
use vm_core::{Kernel, StackInputs, assert_matches};
use super::*;
use crate::{DefaultHost, Process, system::FMP_MAX};
mod advice_provider;
mod all_ops;
mod masm_consistency;
mod memory;
#[test]
fn test_stack_underflow_and_overflow_bounds_failure() {
let mut host = DefaultHost::default();
{
const NUM_DROPS_NO_UNDERFLOW_SWAPS_ALLOWED: usize =
INITIAL_STACK_TOP_IDX - MIN_STACK_DEPTH - 1;
let ops = {
let mut ops = vec![Operation::Drop; NUM_DROPS_NO_UNDERFLOW_SWAPS_ALLOWED];
ops.extend(vec![Operation::Swap; 25]);
ops
};
let program_no_underflow = simple_program_with_ops(ops);
let result = FastProcessor::new(&[]).execute(&program_no_underflow, &mut host);
assert!(result.is_ok());
const NUM_DROPS_NO_UNDERFLOW: usize = NUM_DROPS_NO_UNDERFLOW_SWAPS_ALLOWED + 1;
let program_no_underflow =
simple_program_with_ops(vec![Operation::Drop; NUM_DROPS_NO_UNDERFLOW]);
let result = FastProcessor::new(&[]).execute(&program_no_underflow, &mut host);
assert!(result.is_ok());
const NUM_DROPS_WITH_UNDERFLOW: usize = NUM_DROPS_NO_UNDERFLOW + 1;
let program_with_underflow =
simple_program_with_ops(vec![Operation::Drop; NUM_DROPS_WITH_UNDERFLOW]);
let err = FastProcessor::new(&[]).execute(&program_with_underflow, &mut host);
assert_matches!(err, Err(ExecutionError::FailedToExecuteProgram(_)));
}
{
const NUM_DUPS_NO_OVERFLOW_SWAPS_ALLOWED: usize =
STACK_BUFFER_SIZE - INITIAL_STACK_TOP_IDX - 1;
let ops = {
let mut ops = vec![Operation::Dup0; NUM_DUPS_NO_OVERFLOW_SWAPS_ALLOWED];
ops.extend(vec![Operation::Swap; 25]);
ops.extend(vec![Operation::Drop; NUM_DUPS_NO_OVERFLOW_SWAPS_ALLOWED]);
ops
};
let program_no_overflow = simple_program_with_ops(ops);
let result = FastProcessor::new(&[]).execute(&program_no_overflow, &mut host);
assert_matches!(result, Ok(_));
const NUM_DUPS_NO_OVERFLOW: usize = NUM_DUPS_NO_OVERFLOW_SWAPS_ALLOWED + 1;
let ops = vec![Operation::Dup0; NUM_DUPS_NO_OVERFLOW];
let program_output_overflow = simple_program_with_ops(ops);
let result = FastProcessor::new(&[]).execute(&program_output_overflow, &mut host);
assert_matches!(result, Err(ExecutionError::OutputStackOverflow(_)));
const NUM_DUPS_WITH_OVERFLOW: usize = NUM_DUPS_NO_OVERFLOW + 1;
let ops = vec![Operation::Dup0; NUM_DUPS_WITH_OVERFLOW];
let program_with_overflow = simple_program_with_ops(ops);
let result = FastProcessor::new(&[]).execute(&program_with_overflow, &mut host);
assert_matches!(result, Err(ExecutionError::FailedToExecuteProgram(_)));
}
}
#[test]
fn test_stack_overflow_bounds_success() {
let mut host = DefaultHost::default();
{
let program = simple_program_with_ops(vec![Operation::Dup1, Operation::Add]);
FastProcessor::new(&[]).execute(&program, &mut host).unwrap();
}
{
let program =
simple_program_with_ops(vec![Operation::Add, Operation::Dup1, Operation::Add]);
FastProcessor::new(&[]).execute(&program, &mut host).unwrap();
}
{
let program = simple_program_with_ops(vec![
Operation::Add,
Operation::Dup1,
Operation::Add,
Operation::Dup1,
Operation::Swap,
Operation::Add,
Operation::Dup1,
Operation::Add,
Operation::Swap,
]);
FastProcessor::new(&[]).execute(&program, &mut host).unwrap();
}
}
#[test]
fn test_fmp_add() {
let mut host = DefaultHost::default();
let initial_fmp = Felt::new(FMP_MIN + 4);
let stack_inputs = vec![1_u32.into(), 2_u32.into(), 3_u32.into()];
let program = simple_program_with_ops(vec![Operation::FmpAdd]);
let mut processor = FastProcessor::new(&stack_inputs);
processor.fmp = initial_fmp;
let stack_outputs = processor.execute(&program, &mut host).unwrap();
let expected_top = initial_fmp + stack_inputs[2];
assert_eq!(stack_outputs.stack_truncated(1)[0], expected_top);
}
#[test]
fn test_fmp_update() {
let mut host = DefaultHost::default();
let initial_fmp = Felt::new(FMP_MIN + 4);
let stack_inputs = vec![5_u32.into()];
let program = simple_program_with_ops(vec![Operation::FmpUpdate]);
let mut processor = FastProcessor::new(&stack_inputs);
processor.fmp = initial_fmp;
let stack_outputs = processor.execute_impl(&program, &mut host).unwrap();
let expected_fmp = initial_fmp + stack_inputs[0];
assert_eq!(processor.fmp, expected_fmp);
assert_eq!(stack_outputs.stack_truncated(0).len(), 0);
}
#[test]
fn test_fmp_update_fail() {
let mut host = DefaultHost::default();
let initial_fmp = Felt::new(FMP_MAX - 4);
let stack_inputs = vec![5_u32.into()];
let program = simple_program_with_ops(vec![Operation::FmpUpdate]);
let mut processor = FastProcessor::new(&stack_inputs);
processor.fmp = initial_fmp;
let err = processor.execute(&program, &mut host).unwrap_err();
assert_matches!(err, ExecutionError::InvalidFmpValue(_, _));
let initial_fmp = Felt::new(FMP_MIN + 4);
let stack_inputs = vec![-Felt::new(5_u64)];
let program = simple_program_with_ops(vec![Operation::FmpUpdate]);
let mut processor = FastProcessor::new(&stack_inputs);
processor.fmp = initial_fmp;
let err = processor.execute(&program, &mut host).unwrap_err();
assert_matches!(err, ExecutionError::InvalidFmpValue(_, _));
}
#[test]
fn test_syscall_fail() {
let mut host = DefaultHost::default();
let stack_inputs = vec![5_u32.into()];
let program = {
let mut program = MastForest::new();
let basic_block_id = program.add_block(vec![Operation::Add], None).unwrap();
let root_id = program.add_syscall(basic_block_id).unwrap();
program.make_root(root_id);
Program::new(program.into(), root_id)
};
let processor = FastProcessor::new(&stack_inputs);
let err = processor.execute(&program, &mut host).unwrap_err();
assert_matches!(
err,
ExecutionError::SyscallTargetNotInKernel { label: _, source_file: _, proc_root: _ }
);
}
#[test]
fn test_assert() {
let mut host = DefaultHost::default();
{
let stack_inputs = vec![ONE];
let program = simple_program_with_ops(vec![Operation::Assert(ZERO)]);
let processor = FastProcessor::new(&stack_inputs);
let result = processor.execute(&program, &mut host);
assert!(result.is_ok());
}
{
let stack_inputs = vec![ZERO];
let program = simple_program_with_ops(vec![Operation::Assert(ZERO)]);
let processor = FastProcessor::new(&stack_inputs);
let err = processor.execute(&program, &mut host).unwrap_err();
assert_matches!(err, ExecutionError::FailedAssertion { .. });
}
}
#[rstest]
#[case(vec![ZERO, ZERO], ZERO)]
#[case(vec![ZERO, ONE], ZERO)]
#[case(vec![ONE, ZERO], ZERO)]
#[case(vec![ONE, ONE], ONE)]
fn test_valid_combinations_and(#[case] stack_inputs: Vec<Felt>, #[case] expected_output: Felt) {
let program = simple_program_with_ops(vec![Operation::And]);
let mut host = DefaultHost::default();
let processor = FastProcessor::new(&stack_inputs);
let stack_outputs = processor.execute(&program, &mut host).unwrap();
assert_eq!(stack_outputs.stack_truncated(1)[0], expected_output);
}
#[rstest]
#[case(vec![ZERO, ZERO], ZERO)]
#[case(vec![ZERO, ONE], ONE)]
#[case(vec![ONE, ZERO], ONE)]
#[case(vec![ONE, ONE], ONE)]
fn test_valid_combinations_or(#[case] stack_inputs: Vec<Felt>, #[case] expected_output: Felt) {
let program = simple_program_with_ops(vec![Operation::Or]);
let mut host = DefaultHost::default();
let processor = FastProcessor::new(&stack_inputs);
let stack_outputs = processor.execute(&program, &mut host).unwrap();
assert_eq!(stack_outputs.stack_truncated(1)[0], expected_output);
}
#[test]
fn test_frie2f4() {
let mut host = DefaultHost::default();
let previous_value = [10_u32.into(), 11_u32.into()];
let stack_inputs = vec![
1_u32.into(),
2_u32.into(),
3_u32.into(),
4_u32.into(),
previous_value[0], previous_value[1], 7_u32.into(),
2_u32.into(), 9_u32.into(),
10_u32.into(),
11_u32.into(),
12_u32.into(),
13_u32.into(),
previous_value[0], previous_value[1], 16_u32.into(),
];
let program =
simple_program_with_ops(vec![Operation::Push(Felt::new(42_u64)), Operation::FriE2F4]);
let fast_processor = FastProcessor::new(&stack_inputs);
let fast_stack_outputs = fast_processor.execute(&program, &mut host).unwrap();
let mut slow_processor = Process::new(
Kernel::default(),
StackInputs::new(stack_inputs).unwrap(),
ExecutionOptions::default(),
);
let slow_stack_outputs = slow_processor.execute(&program, &mut host).unwrap();
assert_eq!(fast_stack_outputs, slow_stack_outputs);
}
#[test]
fn test_call_node_preserves_stack_overflow_table() {
let mut host = DefaultHost::default();
let program = {
let mut program = MastForest::new();
let foo_id = program.add_block(vec![Operation::Add], None).unwrap();
let push10_push20_id = program
.add_block(vec![Operation::Push(10_u32.into()), Operation::Push(20_u32.into())], None)
.unwrap();
let call_node_id = program.add_call(foo_id).unwrap();
let swap_drop_swap_drop = program
.add_block(
vec![Operation::Swap, Operation::Drop, Operation::Swap, Operation::Drop],
None,
)
.unwrap();
let join_call_swap = program.add_join(call_node_id, swap_drop_swap_drop).unwrap();
let root_id = program.add_join(push10_push20_id, join_call_swap).unwrap();
program.make_root(root_id);
Program::new(program.into(), root_id)
};
let mut processor = FastProcessor::new(&[
16_u32.into(),
15_u32.into(),
14_u32.into(),
13_u32.into(),
12_u32.into(),
11_u32.into(),
10_u32.into(),
9_u32.into(),
8_u32.into(),
7_u32.into(),
6_u32.into(),
5_u32.into(),
4_u32.into(),
3_u32.into(),
2_u32.into(),
1_u32.into(),
]);
let result = processor.execute_impl(&program, &mut host).unwrap();
assert_eq!(
result.stack_truncated(16),
&[
30_u32.into(),
3_u32.into(),
4_u32.into(),
5_u32.into(),
6_u32.into(),
7_u32.into(),
8_u32.into(),
9_u32.into(),
10_u32.into(),
11_u32.into(),
12_u32.into(),
13_u32.into(),
14_u32.into(),
0_u32.into(),
15_u32.into(),
16_u32.into(),
]
);
}
fn simple_program_with_ops(ops: Vec<Operation>) -> Program {
let program: Program = {
let mut program = MastForest::new();
let root_id = program.add_block(ops, None).unwrap();
program.make_root(root_id);
Program::new(program.into(), root_id)
};
program
}