#![forbid(unsafe_code)]
#![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))]
use std::collections::HashMap;
use acir::AcirField;
use acir::brillig::{
BinaryFieldOp, BinaryIntOp, BitSize, ForeignCallParam, ForeignCallResult, HeapArray,
HeapValueType, HeapVector, IntegerBitSize, MemoryAddress, Opcode, ValueOrArray,
};
use acvm_blackbox_solver::BlackBoxFunctionSolver;
use arithmetic::{BrilligArithmeticError, evaluate_binary_field_op, evaluate_binary_int_op};
use black_box::{BrilligBigIntSolver, evaluate_black_box};
pub use acir::brillig;
use memory::MemoryTypeError;
pub use memory::{MEMORY_ADDRESSING_BIT_SIZE, Memory, MemoryValue};
use num_bigint::BigUint;
mod arithmetic;
mod black_box;
mod cast;
mod memory;
pub type ErrorCallStack = Vec<usize>;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum FailureReason {
Trap {
revert_data_offset: usize,
revert_data_size: usize,
},
RuntimeError { message: String },
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum VMStatus<F> {
Finished {
return_data_offset: usize,
return_data_size: usize,
},
InProgress,
Failure {
reason: FailureReason,
call_stack: ErrorCallStack,
},
ForeignCallWait {
function: String,
inputs: Vec<ForeignCallParam<F>>,
},
}
pub type BrilligProfilingSamples = Vec<BrilligProfilingSample>;
pub type OpcodePosition = usize;
pub type NextOpcodePositionOrState = usize;
const FUZZING_COMPARISON_TRUE_STATE: usize = usize::MAX - 1;
const FUZZING_COMPARISON_FALSE_STATE: usize = usize::MAX;
const FUZZING_COMPARISON_LOG_RANGE_START_STATE: usize = 0;
pub type Branch = (OpcodePosition, NextOpcodePositionOrState);
pub type UniqueFeatureIndex = usize;
pub type BranchToFeatureMap = HashMap<Branch, UniqueFeatureIndex>;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct BrilligProfilingSample {
pub call_stack: Vec<usize>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct VM<'a, F, B: BlackBoxFunctionSolver<F>> {
calldata: Vec<F>,
program_counter: usize,
foreign_call_counter: usize,
foreign_call_results: Vec<ForeignCallResult<F>>,
bytecode: &'a [Opcode<F>],
status: VMStatus<F>,
memory: Memory<F>,
call_stack: Vec<usize>,
black_box_solver: &'a B,
bigint_solver: BrilligBigIntSolver,
profiling_active: bool,
profiling_samples: BrilligProfilingSamples,
fuzzing_active: bool,
fuzzer_trace: Vec<u32>,
branch_to_feature_map: BranchToFeatureMap,
}
impl<'a, F: AcirField, B: BlackBoxFunctionSolver<F>> VM<'a, F, B> {
pub fn new(
calldata: Vec<F>,
bytecode: &'a [Opcode<F>],
black_box_solver: &'a B,
profiling_active: bool,
with_branch_to_feature_map: Option<&BranchToFeatureMap>,
) -> Self {
let (fuzzing_active, fuzzer_trace, branch_to_feature_map) = match with_branch_to_feature_map
{
Some(branch_to_feature_map) => {
(true, vec![0u32; branch_to_feature_map.len()], branch_to_feature_map.clone())
}
None => (false, Vec::new(), HashMap::new()),
};
let bigint_solver =
BrilligBigIntSolver::with_pedantic_solving(black_box_solver.pedantic_solving());
Self {
calldata,
program_counter: 0,
foreign_call_counter: 0,
foreign_call_results: Vec::new(),
bytecode,
status: VMStatus::InProgress,
memory: Memory::default(),
call_stack: Vec::new(),
black_box_solver,
bigint_solver,
profiling_active,
profiling_samples: Vec::with_capacity(bytecode.len()),
fuzzing_active,
fuzzer_trace,
branch_to_feature_map,
}
}
pub fn is_profiling_active(&self) -> bool {
self.profiling_active
}
pub fn is_fuzzing_active(&self) -> bool {
self.fuzzing_active
}
pub fn take_profiling_samples(&mut self) -> BrilligProfilingSamples {
std::mem::take(&mut self.profiling_samples)
}
fn status(&mut self, status: VMStatus<F>) -> VMStatus<F> {
self.status = status.clone();
status
}
pub fn get_status(&self) -> VMStatus<F> {
self.status.clone()
}
fn finish(&mut self, return_data_offset: usize, return_data_size: usize) -> VMStatus<F> {
self.status(VMStatus::Finished { return_data_offset, return_data_size })
}
fn wait_for_foreign_call(
&mut self,
function: String,
inputs: Vec<ForeignCallParam<F>>,
) -> VMStatus<F> {
self.status(VMStatus::ForeignCallWait { function, inputs })
}
pub fn resolve_foreign_call(&mut self, foreign_call_result: ForeignCallResult<F>) {
if self.foreign_call_counter < self.foreign_call_results.len() {
panic!("No unresolved foreign calls");
}
self.foreign_call_results.push(foreign_call_result);
self.status(VMStatus::InProgress);
}
fn get_error_stack(&self) -> Vec<usize> {
let mut error_stack: Vec<_> = self.call_stack.clone();
error_stack.push(self.program_counter);
error_stack
}
fn trap(&mut self, revert_data_offset: usize, revert_data_size: usize) -> VMStatus<F> {
self.status(VMStatus::Failure {
call_stack: self.get_error_stack(),
reason: FailureReason::Trap { revert_data_offset, revert_data_size },
});
self.status.clone()
}
fn fail(&mut self, message: String) -> VMStatus<F> {
self.status(VMStatus::Failure {
call_stack: self.get_error_stack(),
reason: FailureReason::RuntimeError { message },
});
self.status.clone()
}
pub fn process_opcodes(&mut self) -> VMStatus<F> {
while !matches!(
self.process_opcode(),
VMStatus::Finished { .. } | VMStatus::Failure { .. } | VMStatus::ForeignCallWait { .. }
) {}
self.status.clone()
}
pub fn get_memory(&self) -> &[MemoryValue<F>] {
self.memory.values()
}
pub fn write_memory_at(&mut self, ptr: usize, value: MemoryValue<F>) {
self.memory.write(MemoryAddress::direct(ptr), value);
}
pub fn get_call_stack(&self) -> Vec<usize> {
self.call_stack.iter().copied().chain(std::iter::once(self.program_counter)).collect()
}
pub fn process_opcode(&mut self) -> VMStatus<F> {
if self.profiling_active {
let call_stack: Vec<usize> = self.get_call_stack();
self.profiling_samples.push(BrilligProfilingSample { call_stack });
}
self.process_opcode_internal()
}
fn fuzzing_trace_branching(&mut self, destination: NextOpcodePositionOrState) {
if !self.fuzzing_active {
return;
}
let index = self.branch_to_feature_map[&(self.program_counter, destination)];
self.fuzzer_trace[index] += 1;
}
fn fuzzing_trace_conditional_mov(&mut self, branch: bool) {
if !self.fuzzing_active {
return;
}
let index = self.branch_to_feature_map[&(
self.program_counter,
if branch { FUZZING_COMPARISON_TRUE_STATE } else { FUZZING_COMPARISON_FALSE_STATE },
)];
self.fuzzer_trace[index] += 1;
}
fn fuzzing_trace_binary_field_op_comparison(
&mut self,
op: &BinaryFieldOp,
lhs: MemoryValue<F>,
rhs: MemoryValue<F>,
result: MemoryValue<F>,
) {
if !self.fuzzing_active {
return;
}
match op {
BinaryFieldOp::Add
| BinaryFieldOp::Sub
| BinaryFieldOp::Mul
| BinaryFieldOp::Div
| BinaryFieldOp::IntegerDiv => {}
BinaryFieldOp::Equals | BinaryFieldOp::LessThan | BinaryFieldOp::LessThanEquals => {
let a = match lhs {
MemoryValue::Field(a) => a,
_ => {
return;
}
};
let b = match rhs {
MemoryValue::Field(b) => b,
_ => {
return;
}
};
let c = match result {
MemoryValue::Field(..) => {
return;
}
MemoryValue::U1(value) => value,
_ => {
return;
}
};
let approach_index = self.branch_to_feature_map[&(
self.program_counter,
FUZZING_COMPARISON_LOG_RANGE_START_STATE
+ BigUint::from_bytes_be(&(b - a).to_be_bytes()).bits() as usize,
)];
let condition_index = self.branch_to_feature_map[&(
self.program_counter,
if c { FUZZING_COMPARISON_TRUE_STATE } else { FUZZING_COMPARISON_FALSE_STATE },
)];
self.fuzzer_trace[condition_index] += 1;
self.fuzzer_trace[approach_index] += 1;
}
}
}
fn fuzzing_trace_binary_int_op_comparison(
&mut self,
op: &BinaryIntOp,
lhs: MemoryValue<F>,
rhs: MemoryValue<F>,
result: MemoryValue<F>,
) {
if !self.fuzzing_active {
return;
}
match op {
BinaryIntOp::Add
| BinaryIntOp::Sub
| BinaryIntOp::Mul
| BinaryIntOp::Div
| BinaryIntOp::And
| BinaryIntOp::Or
| BinaryIntOp::Xor
| BinaryIntOp::Shl
| BinaryIntOp::Shr => {}
BinaryIntOp::Equals | BinaryIntOp::LessThan | BinaryIntOp::LessThanEquals => {
let lhs_value = lhs.to_u128().expect("lhs is not an integer");
let rhs_value = rhs.to_u128().expect("rhs is not an integer");
let c = match result {
MemoryValue::U1(value) => value,
_ => {
return;
}
};
let approach_index = self.branch_to_feature_map[&(
self.program_counter,
FUZZING_COMPARISON_LOG_RANGE_START_STATE
+ rhs_value.abs_diff(lhs_value).checked_ilog2().map_or_else(|| 0, |x| x + 1)
as usize,
)];
let condition_index = self.branch_to_feature_map[&(
self.program_counter,
if c { FUZZING_COMPARISON_TRUE_STATE } else { FUZZING_COMPARISON_FALSE_STATE },
)];
self.fuzzer_trace[condition_index] += 1;
self.fuzzer_trace[approach_index] += 1;
}
}
}
pub fn get_fuzzing_trace(&self) -> Vec<u32> {
self.fuzzer_trace.clone()
}
fn process_opcode_internal(&mut self) -> VMStatus<F> {
let opcode = &self.bytecode[self.program_counter];
match opcode {
Opcode::BinaryFieldOp { op, lhs, rhs, destination: result } => {
if let Err(error) = self.process_binary_field_op(*op, *lhs, *rhs, *result) {
self.fail(error.to_string())
} else {
self.increment_program_counter()
}
}
Opcode::BinaryIntOp { op, bit_size, lhs, rhs, destination: result } => {
if let Err(error) = self.process_binary_int_op(*op, *bit_size, *lhs, *rhs, *result)
{
self.fail(error.to_string())
} else {
self.increment_program_counter()
}
}
Opcode::Not { destination, source, bit_size } => {
if let Err(error) = self.process_not(*source, *destination, *bit_size) {
self.fail(error.to_string())
} else {
self.increment_program_counter()
}
}
Opcode::Cast { destination: destination_address, source: source_address, bit_size } => {
let source_value = self.memory.read(*source_address);
let casted_value = cast::cast(source_value, *bit_size);
self.memory.write(*destination_address, casted_value);
self.increment_program_counter()
}
Opcode::Jump { location: destination } => self.set_program_counter(*destination),
Opcode::JumpIf { condition, location: destination } => {
let condition_value = self.memory.read(*condition);
if condition_value.expect_u1().expect("condition value is not a boolean") {
self.fuzzing_trace_branching(*destination);
return self.set_program_counter(*destination);
}
self.fuzzing_trace_branching(self.program_counter + 1);
self.increment_program_counter()
}
Opcode::JumpIfNot { condition, location: destination } => {
let condition_value = self.memory.read(*condition);
if condition_value.expect_u1().expect("condition value is not a boolean") {
self.fuzzing_trace_branching(self.program_counter + 1);
return self.increment_program_counter();
}
self.fuzzing_trace_branching(*destination);
self.set_program_counter(*destination)
}
Opcode::CalldataCopy { destination_address, size_address, offset_address } => {
let size = self.memory.read(*size_address).to_usize();
let offset = self.memory.read(*offset_address).to_usize();
let values: Vec<_> = self.calldata[offset..(offset + size)]
.iter()
.map(|value| MemoryValue::new_field(*value))
.collect();
self.memory.write_slice(*destination_address, &values);
self.increment_program_counter()
}
Opcode::Return => {
if let Some(return_location) = self.call_stack.pop() {
self.set_program_counter(return_location + 1)
} else {
self.fail("return opcode hit, but callstack already empty".to_string())
}
}
Opcode::ForeignCall {
function,
destinations,
destination_value_types,
inputs,
input_value_types,
} => {
assert!(inputs.len() == input_value_types.len());
assert!(destinations.len() == destination_value_types.len());
if self.foreign_call_counter >= self.foreign_call_results.len() {
let mut vector_length: Option<usize> = None;
let resolved_inputs = inputs
.iter()
.zip(input_value_types)
.map(|(input, input_type)| {
let mut input = self.get_memory_values(*input, input_type);
match input_type {
HeapValueType::Simple(BitSize::Integer(IntegerBitSize::U32)) => {
let ForeignCallParam::Single(length) = input else {
unreachable!("expected u32; got {input:?}");
};
vector_length = Some(length.to_u128() as usize);
}
HeapValueType::Vector { value_types } => {
if let Some(length) = vector_length {
let type_size = vector_element_size(value_types);
let mut fields = input.fields();
fields.truncate(length * type_size);
input = ForeignCallParam::Array(fields);
}
vector_length = None;
}
_ => {
vector_length = None;
}
}
input
})
.collect::<Vec<_>>();
return self.wait_for_foreign_call(function.clone(), resolved_inputs);
}
let write_result = self.write_foreign_call_result(
destinations,
destination_value_types,
self.foreign_call_counter,
);
if let Err(e) = write_result {
return self.fail(e);
}
self.foreign_call_counter += 1;
self.increment_program_counter()
}
Opcode::Mov { destination: destination_address, source: source_address } => {
let source_value = self.memory.read(*source_address);
self.memory.write(*destination_address, source_value);
self.increment_program_counter()
}
Opcode::ConditionalMov { destination, source_a, source_b, condition } => {
let condition_value = self.memory.read(*condition);
let condition_value_bool =
condition_value.expect_u1().expect("condition value is not a boolean");
if condition_value_bool {
self.memory.write(*destination, self.memory.read(*source_a));
} else {
self.memory.write(*destination, self.memory.read(*source_b));
}
self.fuzzing_trace_conditional_mov(condition_value_bool);
self.increment_program_counter()
}
Opcode::Trap { revert_data } => {
let revert_data_size = self.memory.read(revert_data.size).to_usize();
if revert_data_size > 0 {
self.trap(
self.memory.read_ref(revert_data.pointer).unwrap_direct(),
revert_data_size,
)
} else {
self.trap(0, 0)
}
}
Opcode::Stop { return_data } => {
let return_data_size = self.memory.read(return_data.size).to_usize();
if return_data_size > 0 {
self.finish(
self.memory.read_ref(return_data.pointer).unwrap_direct(),
return_data_size,
)
} else {
self.finish(0, 0)
}
}
Opcode::Load { destination: destination_address, source_pointer } => {
let source = self.memory.read_ref(*source_pointer);
let value = self.memory.read(source);
self.memory.write(*destination_address, value);
self.increment_program_counter()
}
Opcode::Store { destination_pointer, source: source_address } => {
let destination = self.memory.read_ref(*destination_pointer);
let value = self.memory.read(*source_address);
self.memory.write(destination, value);
self.increment_program_counter()
}
Opcode::Call { location } => {
self.call_stack.push(self.program_counter);
self.set_program_counter(*location)
}
Opcode::Const { destination, value, bit_size } => {
self.memory.write(*destination, MemoryValue::new_from_field(*value, *bit_size));
self.increment_program_counter()
}
Opcode::IndirectConst { destination_pointer, bit_size, value } => {
let destination = self.memory.read_ref(*destination_pointer);
self.memory.write(destination, MemoryValue::new_from_field(*value, *bit_size));
self.increment_program_counter()
}
Opcode::BlackBox(black_box_op) => {
match evaluate_black_box(
black_box_op,
self.black_box_solver,
&mut self.memory,
&mut self.bigint_solver,
) {
Ok(()) => self.increment_program_counter(),
Err(e) => self.fail(e.to_string()),
}
}
}
}
pub fn program_counter(&self) -> usize {
self.program_counter
}
fn increment_program_counter(&mut self) -> VMStatus<F> {
self.set_program_counter(self.program_counter + 1)
}
fn set_program_counter(&mut self, value: usize) -> VMStatus<F> {
assert!(self.program_counter < self.bytecode.len());
self.program_counter = value;
if self.program_counter >= self.bytecode.len() {
self.status = VMStatus::Finished { return_data_offset: 0, return_data_size: 0 };
}
self.status.clone()
}
fn get_memory_values(
&self,
input: ValueOrArray,
value_type: &HeapValueType,
) -> ForeignCallParam<F> {
match (input, value_type) {
(ValueOrArray::MemoryAddress(value_index), HeapValueType::Simple(_)) => {
ForeignCallParam::Single(self.memory.read(value_index).to_field())
}
(
ValueOrArray::HeapArray(HeapArray { pointer: pointer_index, size }),
HeapValueType::Array { value_types, size: type_size },
) if *type_size == size => {
let start = self.memory.read_ref(pointer_index);
self.read_slice_of_values_from_memory(start, size, value_types)
.into_iter()
.map(|mem_value| mem_value.to_field())
.collect::<Vec<_>>()
.into()
}
(
ValueOrArray::HeapVector(HeapVector { pointer: pointer_index, size: size_index }),
HeapValueType::Vector { value_types },
) => {
let start = self.memory.read_ref(pointer_index);
let size = self.memory.read(size_index).to_usize();
self.read_slice_of_values_from_memory(start, size, value_types)
.into_iter()
.map(|mem_value| mem_value.to_field())
.collect::<Vec<_>>()
.into()
}
_ => {
unreachable!("Unexpected value type {value_type:?} for input {input:?}");
}
}
}
fn read_slice_of_values_from_memory(
&self,
start: MemoryAddress,
size: usize,
value_types: &[HeapValueType],
) -> Vec<MemoryValue<F>> {
assert!(!start.is_relative(), "read_slice_of_values_from_memory requires direct addresses");
if HeapValueType::all_simple(value_types) {
self.memory.read_slice(start, size).to_vec()
} else {
assert!(
0 == size % value_types.len(),
"array/vector does not contain a whole number of elements"
);
let mut vector_length: Option<usize> = None;
(0..size)
.zip(value_types.iter().cycle())
.map(|(i, value_type)| {
let value_address = start.offset(i);
let values = match value_type {
HeapValueType::Simple(_) => {
vec![self.memory.read(value_address)]
}
HeapValueType::Array { value_types, size } => {
let array_address = self.memory.read_ref(value_address);
self.read_slice_of_values_from_memory(
array_address.offset(1),
*size,
value_types,
)
}
HeapValueType::Vector { value_types } => {
let vector_address = self.memory.read_ref(value_address);
let size_address =
MemoryAddress::direct(vector_address.unwrap_direct() + 1);
let items_start = vector_address.offset(2);
let vector_size = self.memory.read(size_address).to_usize();
self.read_slice_of_values_from_memory(
items_start,
vector_size,
value_types,
)
}
};
(value_type, values)
})
.flat_map(|(value_type, mut values)| {
match value_type {
HeapValueType::Simple(BitSize::Integer(IntegerBitSize::U32)) => {
vector_length = Some(values[0].to_usize());
}
HeapValueType::Vector { value_types } => {
if let Some(length) = vector_length {
let type_size = vector_element_size(value_types);
values.truncate(length * type_size);
}
vector_length = None;
}
_ => {
vector_length = None;
}
}
values
})
.collect::<Vec<_>>()
}
}
fn write_foreign_call_result(
&mut self,
destinations: &[ValueOrArray],
destination_value_types: &[HeapValueType],
foreign_call_index: usize,
) -> Result<(), String> {
let values = std::mem::take(&mut self.foreign_call_results[foreign_call_index].values);
if destinations.len() != values.len() {
return Err(format!(
"{} output values were provided as a foreign call result for {} destination slots",
values.len(),
destinations.len()
));
}
debug_assert_eq!(
destinations.len(),
destination_value_types.len(),
"Number of destinations must match number of value types",
);
debug_assert_eq!(
destinations.len(),
values.len(),
"Number of foreign call return values must match number of destinations",
);
for ((destination, value_type), output) in
destinations.iter().zip(destination_value_types).zip(&values)
{
match (destination, value_type) {
(ValueOrArray::MemoryAddress(value_index), HeapValueType::Simple(bit_size)) => {
let output_fields = output.fields();
if value_type
.flattened_size()
.is_some_and(|flattened_size| output_fields.len() != flattened_size)
{
return Err(format!(
"Foreign call return value does not match expected size. Expected {} but got {}",
value_type.flattened_size().unwrap(),
output_fields.len(),
));
}
match output {
ForeignCallParam::Single(value) => {
self.write_value_to_memory(*value_index, value, *bit_size)?;
}
_ => {
return Err(format!(
"Function result size does not match brillig bytecode. Expected 1 result but got {output:?}"
));
}
}
}
(
ValueOrArray::HeapArray(HeapArray { pointer: pointer_index, size }),
HeapValueType::Array { value_types, size: type_size },
) if size == type_size => {
let output_fields = output.fields();
if value_type
.flattened_size()
.is_some_and(|flattened_size| output_fields.len() != flattened_size)
{
return Err(format!(
"Foreign call return value does not match expected size. Expected {} but got {}",
value_type.flattened_size().unwrap(),
output_fields.len(),
));
}
if HeapValueType::all_simple(value_types) {
match output {
ForeignCallParam::Array(values) => {
if values.len() != *size {
let destination = self.memory.read_ref(*pointer_index);
let mut flatten_values_idx = 0; self.write_slice_of_values_to_memory(
destination,
&output_fields,
&mut flatten_values_idx,
value_type,
)?;
debug_assert_eq!(
flatten_values_idx,
output_fields.len(),
"Not all values were written to memory"
);
} else {
self.write_values_to_memory_slice(
*pointer_index,
values,
value_types,
)?;
}
}
_ => {
return Err(
"Function result size does not match brillig bytecode size"
.to_string(),
);
}
}
} else {
let destination = self.memory.read_ref(*pointer_index);
let return_type = value_type;
let mut flatten_values_idx = 0; self.write_slice_of_values_to_memory(
destination,
&output_fields,
&mut flatten_values_idx,
return_type,
)?;
debug_assert_eq!(
flatten_values_idx,
output_fields.len(),
"Not all values were written to memory"
);
}
}
(
ValueOrArray::HeapVector(HeapVector {
pointer: pointer_index,
size: size_index,
}),
HeapValueType::Vector { value_types },
) => {
if HeapValueType::all_simple(value_types) {
match output {
ForeignCallParam::Array(values) => {
if values.len() % value_types.len() != 0 {
return Err("Returned data does not match vector element size"
.to_string());
}
self.memory.write(*size_index, values.len().into());
self.write_values_to_memory_slice(
*pointer_index,
values,
value_types,
)?;
}
_ => {
return Err(
"Function result size does not match brillig bytecode size"
.to_string(),
);
}
}
} else {
unimplemented!("deflattening heap vectors from foreign calls");
}
}
_ => {
return Err(format!(
"Unexpected value type {value_type:?} for destination {destination:?}"
));
}
}
}
self.foreign_call_results[foreign_call_index].values = values;
Ok(())
}
fn write_value_to_memory(
&mut self,
destination: MemoryAddress,
value: &F,
value_bit_size: BitSize,
) -> Result<(), String> {
let memory_value = MemoryValue::new_checked(*value, value_bit_size);
if let Some(memory_value) = memory_value {
self.memory.write(destination, memory_value);
} else {
return Err(format!(
"Foreign call result value {value} does not fit in bit size {value_bit_size:?}"
));
}
Ok(())
}
fn write_values_to_memory_slice(
&mut self,
pointer_index: MemoryAddress,
values: &[F],
value_types: &[HeapValueType],
) -> Result<(), String> {
let bit_sizes_iterator = value_types
.iter()
.map(|typ| match typ {
HeapValueType::Simple(bit_size) => *bit_size,
_ => unreachable!("Expected simple value type"),
})
.cycle();
let destination = self.memory.read_ref(pointer_index);
let memory_values: Option<Vec<_>> = values
.iter()
.zip(bit_sizes_iterator)
.map(|(value, bit_size)| MemoryValue::new_checked(*value, bit_size))
.collect();
if let Some(memory_values) = memory_values {
self.memory.write_slice(destination, &memory_values);
} else {
return Err(format!(
"Foreign call result values {values:?} do not match expected bit sizes",
));
}
Ok(())
}
fn write_slice_of_values_to_memory(
&mut self,
destination: MemoryAddress,
values: &Vec<F>,
values_idx: &mut usize,
value_type: &HeapValueType,
) -> Result<(), String> {
assert!(
!destination.is_relative(),
"write_slice_of_values_to_memory requires direct addresses"
);
let mut current_pointer = destination;
match value_type {
HeapValueType::Simple(bit_size) => {
self.write_value_to_memory(destination, &values[*values_idx], *bit_size)?;
*values_idx += 1;
Ok(())
}
HeapValueType::Array { value_types, size } => {
for _ in 0..*size {
for typ in value_types {
match typ {
HeapValueType::Simple(len) => {
self.write_value_to_memory(
current_pointer,
&values[*values_idx],
*len,
)?;
*values_idx += 1;
current_pointer = current_pointer.offset(1);
}
HeapValueType::Array { .. } => {
let destination = self.memory.read_ref(current_pointer).offset(1);
self.write_slice_of_values_to_memory(
destination,
values,
values_idx,
typ,
)?;
current_pointer = current_pointer.offset(1);
}
HeapValueType::Vector { .. } => {
return Err(format!(
"Unsupported returned type in foreign calls {typ:?}"
));
}
}
}
}
Ok(())
}
HeapValueType::Vector { .. } => {
Err(format!("Unsupported returned type in foreign calls {value_type:?}"))
}
}
}
fn process_binary_field_op(
&mut self,
op: BinaryFieldOp,
lhs: MemoryAddress,
rhs: MemoryAddress,
result: MemoryAddress,
) -> Result<(), BrilligArithmeticError> {
let lhs_value = self.memory.read(lhs);
let rhs_value = self.memory.read(rhs);
let result_value = evaluate_binary_field_op(&op, lhs_value, rhs_value)?;
self.memory.write(result, result_value);
self.fuzzing_trace_binary_field_op_comparison(&op, lhs_value, rhs_value, result_value);
Ok(())
}
fn process_binary_int_op(
&mut self,
op: BinaryIntOp,
bit_size: IntegerBitSize,
lhs: MemoryAddress,
rhs: MemoryAddress,
result: MemoryAddress,
) -> Result<(), BrilligArithmeticError> {
let lhs_value = self.memory.read(lhs);
let rhs_value = self.memory.read(rhs);
let result_value = evaluate_binary_int_op(&op, lhs_value, rhs_value, bit_size)?;
self.memory.write(result, result_value);
self.fuzzing_trace_binary_int_op_comparison(&op, lhs_value, rhs_value, result_value);
Ok(())
}
fn process_not(
&mut self,
source: MemoryAddress,
destination: MemoryAddress,
op_bit_size: IntegerBitSize,
) -> Result<(), MemoryTypeError> {
let value = self.memory.read(source);
let negated_value = match op_bit_size {
IntegerBitSize::U1 => MemoryValue::U1(!value.expect_u1()?),
IntegerBitSize::U8 => MemoryValue::U8(!value.expect_u8()?),
IntegerBitSize::U16 => MemoryValue::U16(!value.expect_u16()?),
IntegerBitSize::U32 => MemoryValue::U32(!value.expect_u32()?),
IntegerBitSize::U64 => MemoryValue::U64(!value.expect_u64()?),
IntegerBitSize::U128 => MemoryValue::U128(!value.expect_u128()?),
};
self.memory.write(destination, negated_value);
Ok(())
}
}
fn vector_element_size(value_types: &[HeapValueType]) -> usize {
value_types
.iter()
.map(|typ| {
typ.flattened_size()
.unwrap_or_else(|| panic!("unexpected nested dynamic element type: {typ:?}"))
})
.sum()
}
#[cfg(test)]
mod tests {
use crate::memory::MEMORY_ADDRESSING_BIT_SIZE;
use acir::{AcirField, FieldElement};
use acvm_blackbox_solver::StubbedBlackBoxSolver;
use super::*;
#[test]
fn add_single_step_smoke() {
let calldata = vec![];
let opcodes = [Opcode::Const {
destination: MemoryAddress::direct(0),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(27u128),
}];
let solver = StubbedBlackBoxSolver::default();
let mut vm = VM::new(calldata, &opcodes, &solver, false, None);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 });
let VM { memory, .. } = vm;
let output_value = memory.read(MemoryAddress::direct(0));
assert_eq!(output_value.to_field(), FieldElement::from(27u128));
}
#[test]
fn jmpif_opcode() {
let mut calldata: Vec<FieldElement> = vec![];
let lhs = {
calldata.push(2u128.into());
MemoryAddress::direct(calldata.len() - 1)
};
let rhs = {
calldata.push(2u128.into());
MemoryAddress::direct(calldata.len() - 1)
};
let destination = MemoryAddress::direct(calldata.len());
let opcodes = vec![
Opcode::Const {
destination: MemoryAddress::direct(0),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(2u64),
},
Opcode::Const {
destination: MemoryAddress::direct(1),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(0u64),
},
Opcode::CalldataCopy {
destination_address: MemoryAddress::direct(0),
size_address: MemoryAddress::direct(0),
offset_address: MemoryAddress::direct(1),
},
Opcode::BinaryFieldOp { destination, op: BinaryFieldOp::Equals, lhs, rhs },
Opcode::Jump { location: 5 },
Opcode::JumpIf { condition: destination, location: 6 },
];
let solver = StubbedBlackBoxSolver::default();
let mut vm = VM::new(calldata, &opcodes, &solver, false, None);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let output_cmp_value = vm.memory.read(destination);
assert_eq!(output_cmp_value.to_field(), true.into());
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 });
}
#[test]
fn jmpifnot_opcode() {
let calldata: Vec<FieldElement> = vec![1u128.into(), 2u128.into()];
let opcodes = vec![
Opcode::Const {
destination: MemoryAddress::direct(0),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(2u64),
},
Opcode::Const {
destination: MemoryAddress::direct(1),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(0u64),
},
Opcode::CalldataCopy {
destination_address: MemoryAddress::direct(0),
size_address: MemoryAddress::direct(0),
offset_address: MemoryAddress::direct(1),
},
Opcode::Jump { location: 6 },
Opcode::Const {
destination: MemoryAddress::direct(0),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(0u64),
},
Opcode::Trap {
revert_data: HeapVector {
pointer: MemoryAddress::direct(0),
size: MemoryAddress::direct(0),
},
},
Opcode::BinaryFieldOp {
op: BinaryFieldOp::Equals,
lhs: MemoryAddress::direct(0),
rhs: MemoryAddress::direct(1),
destination: MemoryAddress::direct(2),
},
Opcode::JumpIfNot { condition: MemoryAddress::direct(2), location: 4 },
Opcode::BinaryFieldOp {
op: BinaryFieldOp::Add,
lhs: MemoryAddress::direct(0),
rhs: MemoryAddress::direct(1),
destination: MemoryAddress::direct(2),
},
];
let solver = StubbedBlackBoxSolver::default();
let mut vm = VM::new(calldata, &opcodes, &solver, false, None);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let output_cmp_value = vm.memory.read(MemoryAddress::direct(2));
assert_eq!(output_cmp_value.to_field(), false.into());
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(
status,
VMStatus::Failure {
reason: FailureReason::Trap { revert_data_offset: 0, revert_data_size: 0 },
call_stack: vec![5]
}
);
let VM { memory, .. } = vm;
let output_value = memory.read(MemoryAddress::direct(2));
assert_eq!(output_value.to_field(), false.into());
}
#[test]
fn cast_opcode() {
let calldata: Vec<FieldElement> = vec![((2_u128.pow(32)) - 1).into()];
let value_address = MemoryAddress::direct(1);
let one_usize = MemoryAddress::direct(2);
let zero_usize = MemoryAddress::direct(3);
let opcodes = &[
Opcode::Const {
destination: one_usize,
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(1u64),
},
Opcode::Const {
destination: zero_usize,
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(0u64),
},
Opcode::CalldataCopy {
destination_address: value_address,
size_address: one_usize,
offset_address: zero_usize,
},
Opcode::Cast {
destination: value_address,
source: value_address,
bit_size: BitSize::Integer(IntegerBitSize::U8),
},
Opcode::Stop {
return_data: HeapVector {
pointer: one_usize, size: one_usize,
},
},
];
let solver = StubbedBlackBoxSolver::default();
let mut vm = VM::new(calldata, opcodes, &solver, false, None);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::Finished { return_data_offset: 1, return_data_size: 1 });
let VM { memory, .. } = vm;
let casted_value = memory.read(MemoryAddress::direct(1));
assert_eq!(casted_value.to_field(), (2_u128.pow(8) - 1).into());
}
#[test]
fn not_opcode() {
let calldata: Vec<FieldElement> = vec![(1_usize).into()];
let value_address = MemoryAddress::direct(1);
let one_usize = MemoryAddress::direct(2);
let zero_usize = MemoryAddress::direct(3);
let opcodes = &[
Opcode::Const {
destination: one_usize,
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(1u64),
},
Opcode::Const {
destination: zero_usize,
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(0u64),
},
Opcode::CalldataCopy {
destination_address: value_address,
size_address: one_usize,
offset_address: zero_usize,
},
Opcode::Cast {
destination: value_address,
source: value_address,
bit_size: BitSize::Integer(IntegerBitSize::U128),
},
Opcode::Not {
destination: value_address,
source: value_address,
bit_size: IntegerBitSize::U128,
},
Opcode::Stop {
return_data: HeapVector {
pointer: one_usize, size: one_usize,
},
},
];
let solver = StubbedBlackBoxSolver::default();
let mut vm = VM::new(calldata, opcodes, &solver, false, None);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::Finished { return_data_offset: 1, return_data_size: 1 });
let VM { memory, .. } = vm;
let MemoryValue::U128(negated_value) = memory.read(MemoryAddress::direct(1)) else {
panic!("Expected integer as the output of Not");
};
assert_eq!(negated_value, !1_u128);
}
#[test]
fn mov_opcode() {
let calldata: Vec<FieldElement> = vec![(1u128).into(), (2u128).into(), (3u128).into()];
let opcodes = &[
Opcode::Const {
destination: MemoryAddress::direct(0),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(3u64),
},
Opcode::Const {
destination: MemoryAddress::direct(1),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(0u64),
},
Opcode::CalldataCopy {
destination_address: MemoryAddress::direct(0),
size_address: MemoryAddress::direct(0),
offset_address: MemoryAddress::direct(1),
},
Opcode::Mov { destination: MemoryAddress::direct(2), source: MemoryAddress::direct(0) },
];
let solver = StubbedBlackBoxSolver::default();
let mut vm = VM::new(calldata, opcodes, &solver, false, None);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 });
let VM { memory, .. } = vm;
let destination_value = memory.read(MemoryAddress::direct(2));
assert_eq!(destination_value.to_field(), (1u128).into());
let source_value = memory.read(MemoryAddress::direct(0));
assert_eq!(source_value.to_field(), (1u128).into());
}
#[test]
fn cmov_opcode() {
let calldata: Vec<FieldElement> =
vec![(0u128).into(), (1u128).into(), (2u128).into(), (3u128).into()];
let opcodes = &[
Opcode::Const {
destination: MemoryAddress::direct(0),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(4u64),
},
Opcode::Const {
destination: MemoryAddress::direct(1),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(0u64),
},
Opcode::CalldataCopy {
destination_address: MemoryAddress::direct(0),
size_address: MemoryAddress::direct(0),
offset_address: MemoryAddress::direct(1),
},
Opcode::Cast {
destination: MemoryAddress::direct(0),
source: MemoryAddress::direct(0),
bit_size: BitSize::Integer(IntegerBitSize::U1),
},
Opcode::Cast {
destination: MemoryAddress::direct(1),
source: MemoryAddress::direct(1),
bit_size: BitSize::Integer(IntegerBitSize::U1),
},
Opcode::ConditionalMov {
destination: MemoryAddress::direct(4), source_a: MemoryAddress::direct(2),
source_b: MemoryAddress::direct(3),
condition: MemoryAddress::direct(0),
},
Opcode::ConditionalMov {
destination: MemoryAddress::direct(5), source_a: MemoryAddress::direct(2),
source_b: MemoryAddress::direct(3),
condition: MemoryAddress::direct(1),
},
];
let solver = StubbedBlackBoxSolver::default();
let mut vm = VM::new(calldata, opcodes, &solver, false, None);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 });
let VM { memory, .. } = vm;
let destination_value = memory.read(MemoryAddress::direct(4));
assert_eq!(destination_value.to_field(), (3_u128).into());
let source_value = memory.read(MemoryAddress::direct(5));
assert_eq!(source_value.to_field(), (2_u128).into());
}
#[test]
fn cmp_binary_ops() {
let bit_size = MEMORY_ADDRESSING_BIT_SIZE;
let calldata: Vec<FieldElement> =
vec![(2u128).into(), (2u128).into(), (0u128).into(), (5u128).into(), (6u128).into()];
let calldata_size = calldata.len();
let calldata_copy_opcodes = vec![
Opcode::Const {
destination: MemoryAddress::direct(0),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(5u64),
},
Opcode::Const {
destination: MemoryAddress::direct(1),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(0u64),
},
Opcode::CalldataCopy {
destination_address: MemoryAddress::direct(0),
size_address: MemoryAddress::direct(0),
offset_address: MemoryAddress::direct(1),
},
];
let cast_opcodes: Vec<_> = (0..calldata_size)
.map(|index| Opcode::Cast {
destination: MemoryAddress::direct(index),
source: MemoryAddress::direct(index),
bit_size: BitSize::Integer(bit_size),
})
.collect();
let equal_opcode = Opcode::BinaryIntOp {
bit_size,
op: BinaryIntOp::Equals,
lhs: MemoryAddress::direct(0),
rhs: MemoryAddress::direct(1),
destination: MemoryAddress::direct(2),
};
let not_equal_opcode = Opcode::BinaryIntOp {
bit_size,
op: BinaryIntOp::Equals,
lhs: MemoryAddress::direct(0),
rhs: MemoryAddress::direct(3),
destination: MemoryAddress::direct(2),
};
let less_than_opcode = Opcode::BinaryIntOp {
bit_size,
op: BinaryIntOp::LessThan,
lhs: MemoryAddress::direct(3),
rhs: MemoryAddress::direct(4),
destination: MemoryAddress::direct(2),
};
let less_than_equal_opcode = Opcode::BinaryIntOp {
bit_size,
op: BinaryIntOp::LessThanEquals,
lhs: MemoryAddress::direct(3),
rhs: MemoryAddress::direct(4),
destination: MemoryAddress::direct(2),
};
let opcodes: Vec<_> = calldata_copy_opcodes
.into_iter()
.chain(cast_opcodes)
.chain([equal_opcode, not_equal_opcode, less_than_opcode, less_than_equal_opcode])
.collect();
let solver = StubbedBlackBoxSolver::default();
let mut vm = VM::new(calldata, &opcodes, &solver, false, None);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
for _ in 0..calldata_size {
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
}
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let output_eq_value = vm.memory.read(MemoryAddress::direct(2));
assert_eq!(output_eq_value, true.into());
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let output_neq_value = vm.memory.read(MemoryAddress::direct(2));
assert_eq!(output_neq_value, false.into());
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let lt_value = vm.memory.read(MemoryAddress::direct(2));
assert_eq!(lt_value, true.into());
let status = vm.process_opcode();
assert_eq!(status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 });
let lte_value = vm.memory.read(MemoryAddress::direct(2));
assert_eq!(lte_value, true.into());
}
#[test]
fn store_opcode() {
fn brillig_write_memory(item_count: usize) -> Vec<MemoryValue<FieldElement>> {
let integer_bit_size = MEMORY_ADDRESSING_BIT_SIZE;
let bit_size = BitSize::Integer(integer_bit_size);
let r_i = MemoryAddress::direct(0);
let r_len = MemoryAddress::direct(1);
let r_tmp = MemoryAddress::direct(2);
let r_pointer = MemoryAddress::direct(3);
let start: [Opcode<FieldElement>; 3] = [
Opcode::Const { destination: r_i, value: 0u128.into(), bit_size },
Opcode::Const { destination: r_len, value: item_count.into(), bit_size },
Opcode::Const { destination: r_pointer, value: 4u128.into(), bit_size },
];
let loop_body = [
Opcode::Store { destination_pointer: r_pointer, source: r_i },
Opcode::Const { destination: r_tmp, value: 1u128.into(), bit_size },
Opcode::BinaryIntOp {
destination: r_i,
lhs: r_i,
op: BinaryIntOp::Add,
rhs: r_tmp,
bit_size: integer_bit_size,
},
Opcode::BinaryIntOp {
destination: r_pointer,
lhs: r_pointer,
op: BinaryIntOp::Add,
rhs: r_tmp,
bit_size: integer_bit_size,
},
Opcode::BinaryIntOp {
destination: r_tmp,
lhs: r_i,
op: BinaryIntOp::LessThan,
rhs: r_len,
bit_size: integer_bit_size,
},
Opcode::JumpIf { condition: r_tmp, location: start.len() },
];
let opcodes = [&start[..], &loop_body[..]].concat();
let solver = StubbedBlackBoxSolver::default();
let vm = brillig_execute_and_get_vm(vec![], &opcodes, &solver);
vm.get_memory()[4..].to_vec()
}
let memory = brillig_write_memory(5);
let expected =
vec![(0u32).into(), (1u32).into(), (2u32).into(), (3u32).into(), (4u32).into()];
assert_eq!(memory, expected);
let memory = brillig_write_memory(1024);
let expected: Vec<_> = (0..1024).map(|i: u32| i.into()).collect();
assert_eq!(memory, expected);
}
#[test]
fn iconst_opcode() {
let opcodes = &[
Opcode::Const {
destination: MemoryAddress::direct(0),
bit_size: BitSize::Integer(MEMORY_ADDRESSING_BIT_SIZE),
value: FieldElement::from(8_usize),
},
Opcode::IndirectConst {
destination_pointer: MemoryAddress::direct(0),
bit_size: BitSize::Integer(MEMORY_ADDRESSING_BIT_SIZE),
value: FieldElement::from(27_usize),
},
];
let solver = StubbedBlackBoxSolver::default();
let mut vm = VM::new(vec![], opcodes, &solver, false, None);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 });
let VM { memory, .. } = vm;
let destination_value = memory.read(MemoryAddress::direct(8));
assert_eq!(destination_value.to_field(), (27_usize).into());
}
#[test]
fn load_opcode() {
fn brillig_sum_memory(memory: Vec<FieldElement>) -> FieldElement {
let bit_size = IntegerBitSize::U32;
let r_i = MemoryAddress::direct(0);
let r_len = MemoryAddress::direct(1);
let r_sum = MemoryAddress::direct(2);
let r_tmp = MemoryAddress::direct(3);
let r_pointer = MemoryAddress::direct(4);
let start = [
Opcode::Const { destination: r_sum, value: 0u128.into(), bit_size: BitSize::Field },
Opcode::Const {
destination: r_i,
value: 0u128.into(),
bit_size: BitSize::Integer(bit_size),
},
Opcode::Const {
destination: r_len,
value: memory.len().into(),
bit_size: BitSize::Integer(bit_size),
},
Opcode::Const {
destination: r_pointer,
value: 5u128.into(),
bit_size: BitSize::Integer(bit_size),
},
Opcode::Const {
destination: MemoryAddress::direct(100),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(memory.len() as u32),
},
Opcode::Const {
destination: MemoryAddress::direct(101),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(0u64),
},
Opcode::CalldataCopy {
destination_address: MemoryAddress::direct(5),
size_address: MemoryAddress::direct(100),
offset_address: MemoryAddress::direct(101),
},
];
let loop_body = [
Opcode::Load { destination: r_tmp, source_pointer: r_pointer },
Opcode::BinaryFieldOp {
destination: r_sum,
lhs: r_sum,
op: BinaryFieldOp::Add,
rhs: r_tmp,
},
Opcode::Const {
destination: r_tmp,
value: 1u128.into(),
bit_size: BitSize::Integer(bit_size),
},
Opcode::BinaryIntOp {
destination: r_i,
lhs: r_i,
op: BinaryIntOp::Add,
rhs: r_tmp,
bit_size,
},
Opcode::BinaryIntOp {
destination: r_pointer,
lhs: r_pointer,
op: BinaryIntOp::Add,
rhs: r_tmp,
bit_size,
},
Opcode::BinaryIntOp {
destination: r_tmp,
lhs: r_i,
op: BinaryIntOp::LessThan,
rhs: r_len,
bit_size,
},
Opcode::JumpIf { condition: r_tmp, location: start.len() },
];
let opcodes = [&start[..], &loop_body[..]].concat();
let solver = StubbedBlackBoxSolver::default();
let vm = brillig_execute_and_get_vm(memory, &opcodes, &solver);
vm.memory.read(r_sum).to_field()
}
assert_eq!(
brillig_sum_memory(vec![
(1u128).into(),
(2u128).into(),
(3u128).into(),
(4u128).into(),
(5u128).into(),
]),
(15u128).into()
);
assert_eq!(brillig_sum_memory(vec![(1u128).into(); 1024]), (1024u128).into());
}
#[test]
fn call_and_return_opcodes() {
fn brillig_recursive_write_memory<F: AcirField>(size: usize) -> Vec<MemoryValue<F>> {
let integer_bit_size = MEMORY_ADDRESSING_BIT_SIZE;
let bit_size = BitSize::Integer(integer_bit_size);
let r_i = MemoryAddress::direct(0);
let r_len = MemoryAddress::direct(1);
let r_tmp = MemoryAddress::direct(2);
let r_pointer = MemoryAddress::direct(3);
let start: [Opcode<F>; 5] = [
Opcode::Const { destination: r_i, value: 0u128.into(), bit_size },
Opcode::Const { destination: r_len, value: (size as u128).into(), bit_size },
Opcode::Const { destination: r_pointer, value: 4u128.into(), bit_size },
Opcode::Call {
location: 5, },
Opcode::Jump { location: 100 },
];
let recursive_fn = [
Opcode::BinaryIntOp {
destination: r_tmp,
lhs: r_len,
op: BinaryIntOp::LessThanEquals,
rhs: r_i,
bit_size: integer_bit_size,
},
Opcode::JumpIf {
condition: r_tmp,
location: start.len() + 7, },
Opcode::Store { destination_pointer: r_pointer, source: r_i },
Opcode::Const { destination: r_tmp, value: 1u128.into(), bit_size },
Opcode::BinaryIntOp {
destination: r_i,
lhs: r_i,
op: BinaryIntOp::Add,
rhs: r_tmp,
bit_size: integer_bit_size,
},
Opcode::BinaryIntOp {
destination: r_pointer,
lhs: r_pointer,
op: BinaryIntOp::Add,
rhs: r_tmp,
bit_size: integer_bit_size,
},
Opcode::Call { location: start.len() },
Opcode::Return {},
];
let opcodes = [&start[..], &recursive_fn[..]].concat();
let solver = StubbedBlackBoxSolver::default();
let vm = brillig_execute_and_get_vm(vec![], &opcodes, &solver);
vm.get_memory()[4..].to_vec()
}
let memory = brillig_recursive_write_memory::<FieldElement>(5);
let expected =
vec![(0u32).into(), (1u32).into(), (2u32).into(), (3u32).into(), (4u32).into()];
assert_eq!(memory, expected);
let memory = brillig_recursive_write_memory::<FieldElement>(1024);
let expected: Vec<_> = (0..1024).map(|i: u32| i.into()).collect();
assert_eq!(memory, expected);
}
fn brillig_execute_and_get_vm<'a, F: AcirField>(
calldata: Vec<F>,
opcodes: &'a [Opcode<F>],
solver: &'a StubbedBlackBoxSolver,
) -> VM<'a, F, StubbedBlackBoxSolver> {
let mut vm = VM::new(calldata, opcodes, solver, false, None);
brillig_execute(&mut vm);
assert!(vm.call_stack.is_empty());
vm
}
fn brillig_execute<F: AcirField>(vm: &mut VM<F, StubbedBlackBoxSolver>) {
loop {
let status = vm.process_opcode();
if matches!(status, VMStatus::Finished { .. } | VMStatus::ForeignCallWait { .. }) {
break;
}
assert_eq!(status, VMStatus::InProgress);
}
}
#[test]
fn foreign_call_opcode_simple_result() {
let r_input = MemoryAddress::direct(0);
let r_result = MemoryAddress::direct(1);
let double_program = vec![
Opcode::Const {
destination: r_input,
value: (5u128).into(),
bit_size: BitSize::Integer(MEMORY_ADDRESSING_BIT_SIZE),
},
Opcode::ForeignCall {
function: "double".into(),
destinations: vec![ValueOrArray::MemoryAddress(r_result)],
destination_value_types: vec![HeapValueType::Simple(BitSize::Integer(
MEMORY_ADDRESSING_BIT_SIZE,
))],
inputs: vec![ValueOrArray::MemoryAddress(r_input)],
input_value_types: vec![HeapValueType::Simple(BitSize::Integer(
MEMORY_ADDRESSING_BIT_SIZE,
))],
},
];
let solver = StubbedBlackBoxSolver::default();
let mut vm = brillig_execute_and_get_vm(vec![], &double_program, &solver);
assert_eq!(
vm.status,
VMStatus::ForeignCallWait {
function: "double".into(),
inputs: vec![FieldElement::from(5usize).into()]
}
);
vm.resolve_foreign_call(
FieldElement::from(10u128).into(), );
brillig_execute(&mut vm);
assert_eq!(vm.status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 });
let result_value = vm.memory.read(r_result);
assert_eq!(result_value, (10u32).into());
assert_eq!(vm.foreign_call_counter, 1);
}
#[test]
fn foreign_call_opcode_memory_result() {
let r_input = MemoryAddress::direct(0);
let r_output = MemoryAddress::direct(1);
let initial_matrix: Vec<FieldElement> =
vec![(1u128).into(), (2u128).into(), (3u128).into(), (4u128).into()];
let expected_result: Vec<FieldElement> =
vec![(1u128).into(), (3u128).into(), (2u128).into(), (4u128).into()];
let invert_program = vec![
Opcode::Const {
destination: MemoryAddress::direct(0),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(initial_matrix.len() as u32),
},
Opcode::Const {
destination: MemoryAddress::direct(1),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(0u64),
},
Opcode::CalldataCopy {
destination_address: MemoryAddress::direct(2),
size_address: MemoryAddress::direct(0),
offset_address: MemoryAddress::direct(1),
},
Opcode::Const {
destination: r_input,
value: 2_usize.into(),
bit_size: BitSize::Integer(MEMORY_ADDRESSING_BIT_SIZE),
},
Opcode::Const {
destination: r_output,
value: 2_usize.into(),
bit_size: BitSize::Integer(MEMORY_ADDRESSING_BIT_SIZE),
},
Opcode::ForeignCall {
function: "matrix_2x2_transpose".into(),
destinations: vec![ValueOrArray::HeapArray(HeapArray {
pointer: r_output,
size: initial_matrix.len(),
})],
destination_value_types: vec![HeapValueType::Array {
size: initial_matrix.len(),
value_types: vec![HeapValueType::field()],
}],
inputs: vec![ValueOrArray::HeapArray(HeapArray {
pointer: r_input,
size: initial_matrix.len(),
})],
input_value_types: vec![HeapValueType::Array {
value_types: vec![HeapValueType::field()],
size: initial_matrix.len(),
}],
},
];
let solver = StubbedBlackBoxSolver::default();
let mut vm = brillig_execute_and_get_vm(initial_matrix.clone(), &invert_program, &solver);
assert_eq!(
vm.status,
VMStatus::ForeignCallWait {
function: "matrix_2x2_transpose".into(),
inputs: vec![initial_matrix.into()]
}
);
vm.resolve_foreign_call(expected_result.clone().into());
brillig_execute(&mut vm);
assert_eq!(vm.status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 });
let result_values = vm.memory.read_slice(MemoryAddress::direct(2), 4).to_vec();
assert_eq!(
result_values.into_iter().map(|mem_value| mem_value.to_field()).collect::<Vec<_>>(),
expected_result
);
assert_eq!(vm.foreign_call_counter, 1);
}
#[test]
fn foreign_call_opcode_vector_input_and_output() {
let r_input_pointer = MemoryAddress::direct(0);
let r_input_size = MemoryAddress::direct(1);
let r_output_pointer = MemoryAddress::direct(2);
let r_output_size = MemoryAddress::direct(3);
let input_string: Vec<FieldElement> =
vec![(1u128).into(), (2u128).into(), (3u128).into(), (4u128).into()];
let mut output_string: Vec<_> =
input_string.iter().cloned().chain(input_string.clone()).collect();
output_string.reverse();
let string_double_program = vec![
Opcode::Const {
destination: MemoryAddress::direct(100),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(input_string.len() as u32),
},
Opcode::Const {
destination: MemoryAddress::direct(101),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(0u64),
},
Opcode::CalldataCopy {
destination_address: MemoryAddress::direct(4),
size_address: MemoryAddress::direct(100),
offset_address: MemoryAddress::direct(101),
},
Opcode::Const {
destination: r_input_pointer,
value: (4u128).into(),
bit_size: BitSize::Integer(MEMORY_ADDRESSING_BIT_SIZE),
},
Opcode::Const {
destination: r_input_size,
value: input_string.len().into(),
bit_size: BitSize::Integer(MEMORY_ADDRESSING_BIT_SIZE),
},
Opcode::Const {
destination: r_output_pointer,
value: (4 + input_string.len()).into(),
bit_size: BitSize::Integer(MEMORY_ADDRESSING_BIT_SIZE),
},
Opcode::Const {
destination: r_output_size,
value: (input_string.len() * 2).into(),
bit_size: BitSize::Integer(MEMORY_ADDRESSING_BIT_SIZE),
},
Opcode::ForeignCall {
function: "string_double".into(),
destinations: vec![ValueOrArray::HeapVector(HeapVector {
pointer: r_output_pointer,
size: r_output_size,
})],
destination_value_types: vec![HeapValueType::Vector {
value_types: vec![HeapValueType::field()],
}],
inputs: vec![ValueOrArray::HeapVector(HeapVector {
pointer: r_input_pointer,
size: r_input_size,
})],
input_value_types: vec![HeapValueType::Vector {
value_types: vec![HeapValueType::field()],
}],
},
];
let solver = StubbedBlackBoxSolver::default();
let mut vm =
brillig_execute_and_get_vm(input_string.clone(), &string_double_program, &solver);
assert_eq!(
vm.status,
VMStatus::ForeignCallWait {
function: "string_double".into(),
inputs: vec![input_string.clone().into()]
}
);
vm.resolve_foreign_call(ForeignCallResult {
values: vec![ForeignCallParam::Array(output_string.clone())],
});
brillig_execute(&mut vm);
assert_eq!(vm.status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 });
let result_values: Vec<_> = vm
.memory
.read_slice(MemoryAddress::direct(4 + input_string.len()), output_string.len())
.iter()
.map(|mem_val| mem_val.clone().to_field())
.collect();
assert_eq!(result_values, output_string);
assert_eq!(vm.foreign_call_counter, 1);
}
#[test]
fn foreign_call_opcode_memory_alloc_result() {
let r_input = MemoryAddress::direct(0);
let r_output = MemoryAddress::direct(1);
let initial_matrix: Vec<FieldElement> =
vec![(1u128).into(), (2u128).into(), (3u128).into(), (4u128).into()];
let expected_result: Vec<FieldElement> =
vec![(1u128).into(), (3u128).into(), (2u128).into(), (4u128).into()];
let invert_program = vec![
Opcode::Const {
destination: MemoryAddress::direct(100),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(initial_matrix.len() as u32),
},
Opcode::Const {
destination: MemoryAddress::direct(101),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(0u64),
},
Opcode::CalldataCopy {
destination_address: MemoryAddress::direct(2),
size_address: MemoryAddress::direct(100),
offset_address: MemoryAddress::direct(101),
},
Opcode::Const {
destination: r_input,
value: (2u128).into(),
bit_size: BitSize::Integer(MEMORY_ADDRESSING_BIT_SIZE),
},
Opcode::Const {
destination: r_output,
value: (6u128).into(),
bit_size: BitSize::Integer(MEMORY_ADDRESSING_BIT_SIZE),
},
Opcode::ForeignCall {
function: "matrix_2x2_transpose".into(),
destinations: vec![ValueOrArray::HeapArray(HeapArray {
pointer: r_output,
size: initial_matrix.len(),
})],
destination_value_types: vec![HeapValueType::Array {
size: initial_matrix.len(),
value_types: vec![HeapValueType::field()],
}],
inputs: vec![ValueOrArray::HeapArray(HeapArray {
pointer: r_input,
size: initial_matrix.len(),
})],
input_value_types: vec![HeapValueType::Array {
size: initial_matrix.len(),
value_types: vec![HeapValueType::field()],
}],
},
];
let solver = StubbedBlackBoxSolver::default();
let mut vm = brillig_execute_and_get_vm(initial_matrix.clone(), &invert_program, &solver);
assert_eq!(
vm.status,
VMStatus::ForeignCallWait {
function: "matrix_2x2_transpose".into(),
inputs: vec![initial_matrix.clone().into()]
}
);
vm.resolve_foreign_call(expected_result.clone().into());
brillig_execute(&mut vm);
assert_eq!(vm.status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 });
let initial_values: Vec<_> = vm
.memory
.read_slice(MemoryAddress::direct(2), 4)
.iter()
.map(|mem_val| mem_val.clone().to_field())
.collect();
assert_eq!(initial_values, initial_matrix);
let result_values: Vec<_> = vm
.memory
.read_slice(MemoryAddress::direct(6), 4)
.iter()
.map(|mem_val| mem_val.clone().to_field())
.collect();
assert_eq!(result_values, expected_result);
assert_eq!(vm.foreign_call_counter, 1);
}
#[test]
fn foreign_call_opcode_multiple_array_inputs_result() {
let r_input_a = MemoryAddress::direct(0);
let r_input_b = MemoryAddress::direct(1);
let r_output = MemoryAddress::direct(2);
let matrix_a: Vec<FieldElement> =
vec![(1u128).into(), (2u128).into(), (3u128).into(), (4u128).into()];
let matrix_b: Vec<FieldElement> =
vec![(10u128).into(), (11u128).into(), (12u128).into(), (13u128).into()];
let expected_result: Vec<FieldElement> =
vec![(34u128).into(), (37u128).into(), (78u128).into(), (85u128).into()];
let matrix_mul_program = vec![
Opcode::Const {
destination: MemoryAddress::direct(100),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(matrix_a.len() + matrix_b.len()),
},
Opcode::Const {
destination: MemoryAddress::direct(101),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(0u64),
},
Opcode::CalldataCopy {
destination_address: MemoryAddress::direct(3),
size_address: MemoryAddress::direct(100),
offset_address: MemoryAddress::direct(101),
},
Opcode::Const {
destination: r_input_a,
value: (3u128).into(),
bit_size: BitSize::Integer(MEMORY_ADDRESSING_BIT_SIZE),
},
Opcode::Const {
destination: r_input_b,
value: (7u128).into(),
bit_size: BitSize::Integer(MEMORY_ADDRESSING_BIT_SIZE),
},
Opcode::Const {
destination: r_output,
value: (0u128).into(),
bit_size: BitSize::Integer(MEMORY_ADDRESSING_BIT_SIZE),
},
Opcode::ForeignCall {
function: "matrix_2x2_transpose".into(),
destinations: vec![ValueOrArray::HeapArray(HeapArray {
pointer: r_output,
size: matrix_a.len(),
})],
destination_value_types: vec![HeapValueType::Array {
size: matrix_a.len(),
value_types: vec![HeapValueType::field()],
}],
inputs: vec![
ValueOrArray::HeapArray(HeapArray { pointer: r_input_a, size: matrix_a.len() }),
ValueOrArray::HeapArray(HeapArray { pointer: r_input_b, size: matrix_b.len() }),
],
input_value_types: vec![
HeapValueType::Array {
size: matrix_a.len(),
value_types: vec![HeapValueType::field()],
},
HeapValueType::Array {
size: matrix_b.len(),
value_types: vec![HeapValueType::field()],
},
],
},
];
let mut initial_memory = matrix_a.clone();
initial_memory.extend(matrix_b.clone());
let solver = StubbedBlackBoxSolver::default();
let mut vm = brillig_execute_and_get_vm(initial_memory, &matrix_mul_program, &solver);
assert_eq!(
vm.status,
VMStatus::ForeignCallWait {
function: "matrix_2x2_transpose".into(),
inputs: vec![matrix_a.into(), matrix_b.into()]
}
);
vm.resolve_foreign_call(expected_result.clone().into());
brillig_execute(&mut vm);
assert_eq!(vm.status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 });
let result_values: Vec<_> = vm
.memory
.read_slice(MemoryAddress::direct(0), 4)
.iter()
.map(|mem_val| mem_val.clone().to_field())
.collect();
assert_eq!(result_values, expected_result);
assert_eq!(vm.foreign_call_counter, 1);
}
#[test]
fn foreign_call_opcode_nested_arrays_and_slices_input() {
let v2: Vec<MemoryValue<FieldElement>> = vec![
MemoryValue::new_field(FieldElement::from(2u128)),
MemoryValue::new_field(FieldElement::from(3u128)),
];
let a4: Vec<MemoryValue<FieldElement>> =
vec![MemoryValue::new_field(FieldElement::from(4u128))];
let v6: Vec<MemoryValue<FieldElement>> = vec![
MemoryValue::new_field(FieldElement::from(6u128)),
MemoryValue::new_field(FieldElement::from(7u128)),
MemoryValue::new_field(FieldElement::from(8u128)),
];
let a9: Vec<MemoryValue<FieldElement>> =
vec![MemoryValue::new_field(FieldElement::from(9u128))];
let v2_ptr: usize = 0usize;
let mut memory = vec![MemoryValue::from(1_u32), v2.len().into()];
memory.extend(v2.clone());
let a4_ptr = memory.len();
memory.extend(vec![MemoryValue::from(1_u32)]);
memory.extend(a4.clone());
let v6_ptr = memory.len();
memory.extend(vec![MemoryValue::from(1_u32), v6.len().into()]);
memory.extend(v6.clone());
let a9_ptr = memory.len();
memory.extend(vec![MemoryValue::from(1_u32)]);
memory.extend(a9.clone());
memory.extend(vec![MemoryValue::from(1_u32)]);
let outer_start = memory.len();
let outer_array = vec![
MemoryValue::new_field(FieldElement::from(1u128)),
MemoryValue::from(v2.len() as u32),
MemoryValue::from(v2_ptr),
MemoryValue::from(a4_ptr),
MemoryValue::new_field(FieldElement::from(5u128)),
MemoryValue::from(v6.len() as u32),
MemoryValue::from(v6_ptr),
MemoryValue::from(a9_ptr),
];
memory.extend(outer_array.clone());
let input_array_value_types: Vec<HeapValueType> = vec![
HeapValueType::field(),
HeapValueType::Simple(BitSize::Integer(IntegerBitSize::U64)), HeapValueType::Vector { value_types: vec![HeapValueType::field()] },
HeapValueType::Array { value_types: vec![HeapValueType::field()], size: 1 },
];
let r_ptr = memory.len();
let r_input = MemoryAddress::direct(r_ptr);
let r_output = MemoryAddress::direct(r_ptr + 1);
let program: Vec<_> = vec![
Opcode::Const {
destination: MemoryAddress::direct(100),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(memory.len()),
},
Opcode::Const {
destination: MemoryAddress::direct(101),
bit_size: BitSize::Integer(IntegerBitSize::U32),
value: FieldElement::from(0u64),
},
Opcode::CalldataCopy {
destination_address: MemoryAddress::direct(0),
size_address: MemoryAddress::direct(100),
offset_address: MemoryAddress::direct(101),
},
]
.into_iter()
.chain(memory.iter().enumerate().map(|(index, mem_value)| Opcode::Cast {
destination: MemoryAddress::direct(index),
source: MemoryAddress::direct(index),
bit_size: mem_value.bit_size(),
}))
.chain(vec![
Opcode::Const {
destination: r_input,
value: (outer_start).into(),
bit_size: BitSize::Integer(IntegerBitSize::U32),
},
Opcode::ForeignCall {
function: "flat_sum".into(),
destinations: vec![ValueOrArray::MemoryAddress(r_output)],
destination_value_types: vec![HeapValueType::field()],
inputs: vec![ValueOrArray::HeapArray(HeapArray {
pointer: r_input,
size: outer_array.len(),
})],
input_value_types: vec![HeapValueType::Array {
value_types: input_array_value_types,
size: outer_array.len(),
}],
},
])
.collect();
let solver = StubbedBlackBoxSolver::default();
let mut vm = brillig_execute_and_get_vm(
memory.into_iter().map(|mem_value| mem_value.to_field()).collect(),
&program,
&solver,
);
assert_eq!(
vm.status,
VMStatus::ForeignCallWait {
function: "flat_sum".into(),
inputs: vec![ForeignCallParam::Array(vec![
(1u128).into(),
(2u128).into(), (2u128).into(),
(3u128).into(),
(4u128).into(),
(5u128).into(),
(3u128).into(), (6u128).into(),
(7u128).into(),
(8u128).into(),
(9u128).into(),
])],
}
);
vm.resolve_foreign_call(FieldElement::from(45u128).into());
brillig_execute(&mut vm);
assert_eq!(vm.status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 });
let result_value = vm.memory.read(r_output);
assert_eq!(result_value, MemoryValue::new_field(FieldElement::from(45u128)));
assert_eq!(vm.foreign_call_counter, 1);
}
#[test]
fn relative_addressing() {
let calldata = vec![];
let bit_size = BitSize::Integer(IntegerBitSize::U32);
let value = FieldElement::from(3u128);
let opcodes = [
Opcode::Const {
destination: MemoryAddress::direct(0),
bit_size,
value: FieldElement::from(27u128),
},
Opcode::Const {
destination: MemoryAddress::relative(1), bit_size,
value,
},
Opcode::Const {
destination: MemoryAddress::direct(1), bit_size,
value,
},
Opcode::BinaryIntOp {
destination: MemoryAddress::direct(1),
op: BinaryIntOp::Equals,
bit_size: IntegerBitSize::U32,
lhs: MemoryAddress::direct(1),
rhs: MemoryAddress::direct(28),
},
];
let solver = StubbedBlackBoxSolver::default();
let mut vm = VM::new(calldata, &opcodes, &solver, false, None);
vm.process_opcode();
vm.process_opcode();
vm.process_opcode();
let status = vm.process_opcode();
assert_eq!(status, VMStatus::Finished { return_data_offset: 0, return_data_size: 0 });
let VM { memory, .. } = vm;
let output_value = memory.read(MemoryAddress::direct(1));
assert_eq!(output_value.to_field(), FieldElement::from(1u128));
}
#[test]
fn field_zero_division_regression() {
let calldata: Vec<FieldElement> = vec![];
let opcodes = &[
Opcode::Const {
destination: MemoryAddress::direct(0),
bit_size: BitSize::Field,
value: FieldElement::from(1u64),
},
Opcode::Const {
destination: MemoryAddress::direct(1),
bit_size: BitSize::Field,
value: FieldElement::from(0u64),
},
Opcode::BinaryFieldOp {
destination: MemoryAddress::direct(2),
op: BinaryFieldOp::Div,
lhs: MemoryAddress::direct(0),
rhs: MemoryAddress::direct(1),
},
];
let solver = StubbedBlackBoxSolver::default();
let mut vm = VM::new(calldata, opcodes, &solver, false, None);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(status, VMStatus::InProgress);
let status = vm.process_opcode();
assert_eq!(
status,
VMStatus::Failure {
reason: FailureReason::RuntimeError {
message: "Attempted to divide by zero".into()
},
call_stack: vec![2]
}
);
}
}