use std::collections::HashMap;
use acir::{
AcirField, BlackBoxFunc,
brillig::ForeignCallResult,
circuit::{
AssertionPayload, ErrorSelector, ExpressionOrMemory, Opcode, OpcodeLocation,
brillig::{BrilligBytecode, BrilligFunctionId, BrilligInputs, BrilligOutputs},
opcodes::{AcirFunctionId, BlockId, FunctionInput, InvalidInputBitSize},
},
native_types::{Expression, Witness, WitnessMap},
};
use acvm_blackbox_solver::BlackBoxResolutionError;
use brillig_vm::fuzzing::BranchToFeatureMap;
use itertools::Itertools;
use self::{arithmetic::ExpressionSolver, memory_op::MemoryOpSolver};
use crate::BlackBoxFunctionSolver;
use thiserror::Error;
pub(crate) mod arithmetic;
pub(crate) mod brillig;
pub(crate) mod blackbox;
pub(crate) mod memory_op;
pub use self::brillig::{BrilligSolver, BrilligSolverStatus};
pub use brillig::ForeignCallWaitInfo;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq)]
pub enum ACVMStatus<F> {
Solved,
InProgress,
Failure(OpcodeResolutionError<F>),
RequiresForeignCall(ForeignCallWaitInfo<F>),
RequiresAcirCall(AcirCallWaitInfo<F>),
}
impl<F> std::fmt::Display for ACVMStatus<F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ACVMStatus::Solved => write!(f, "Solved"),
ACVMStatus::InProgress => write!(f, "In progress"),
ACVMStatus::Failure(_) => write!(f, "Execution failure"),
ACVMStatus::RequiresForeignCall(_) => write!(f, "Waiting on foreign call"),
ACVMStatus::RequiresAcirCall(_) => write!(f, "Waiting on acir call"),
}
}
}
#[expect(clippy::large_enum_variant)]
pub enum StepResult<'a, F, B: BlackBoxFunctionSolver<F>> {
Status(ACVMStatus<F>),
IntoBrillig(BrilligSolver<'a, F, B>),
}
#[derive(Clone, PartialEq, Eq, Debug, Error)]
pub enum OpcodeNotSolvable<F> {
#[error("missing assignment for witness index {0}")]
MissingAssignment(u32),
#[error("Attempted to load uninitialized memory block")]
MissingMemoryBlock(u32),
#[error("expression has too many unknowns {0}")]
ExpressionHasTooManyUnknowns(Expression<F>),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum ErrorLocation {
#[default]
Unresolved,
Resolved(OpcodeLocation),
}
impl std::fmt::Display for ErrorLocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorLocation::Unresolved => write!(f, "unresolved"),
ErrorLocation::Resolved(location) => {
write!(f, "{location}")
}
}
}
}
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub struct RawAssertionPayload<F> {
pub selector: ErrorSelector,
pub data: Vec<F>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum ResolvedAssertionPayload<F> {
String(String),
Raw(RawAssertionPayload<F>),
}
#[derive(Clone, PartialEq, Eq, Debug, Error)]
pub enum OpcodeResolutionError<F> {
#[error("Cannot solve opcode: {0}")]
OpcodeNotSolvable(#[from] OpcodeNotSolvable<F>),
#[error("Cannot satisfy constraint")]
UnsatisfiedConstrain {
opcode_location: ErrorLocation,
payload: Option<ResolvedAssertionPayload<F>>,
},
#[error("Index out of bounds, array has size {array_size:?}, but index was {index:?}")]
IndexOutOfBounds { opcode_location: ErrorLocation, index: F, array_size: u32 },
#[error("Cannot solve opcode: {invalid_input_bit_size}")]
InvalidInputBitSize {
opcode_location: ErrorLocation,
invalid_input_bit_size: InvalidInputBitSize,
},
#[error("Failed to solve blackbox function: {0}, reason: {1}")]
BlackBoxFunctionFailed(BlackBoxFunc, String),
#[error("Failed to solve brillig function")]
BrilligFunctionFailed {
function_id: BrilligFunctionId,
call_stack: Vec<OpcodeLocation>,
payload: Option<ResolvedAssertionPayload<F>>,
},
#[error("Attempted to call `main` with a `Call` opcode")]
AcirMainCallAttempted { opcode_location: ErrorLocation },
#[error(
"{results_size:?} result values were provided for {outputs_size:?} call output witnesses, most likely due to bad ACIR codegen"
)]
AcirCallOutputsMismatch { opcode_location: ErrorLocation, results_size: u32, outputs_size: u32 },
#[error("(--pedantic): Predicates are expected to be 0 or 1, but found: {pred_value}")]
PredicateLargerThanOne { opcode_location: ErrorLocation, pred_value: F },
#[error("(--pedantic): Memory operations are expected to be 0 or 1, but found: {operation}")]
MemoryOperationLargerThanOne { opcode_location: ErrorLocation, operation: F },
}
impl<F> From<BlackBoxResolutionError> for OpcodeResolutionError<F> {
fn from(value: BlackBoxResolutionError) -> Self {
match value {
BlackBoxResolutionError::Failed(func, reason) => {
OpcodeResolutionError::BlackBoxFunctionFailed(func, reason)
}
BlackBoxResolutionError::AssertFailed(error) => {
OpcodeResolutionError::UnsatisfiedConstrain {
opcode_location: ErrorLocation::Unresolved,
payload: Some(ResolvedAssertionPayload::String(error)),
}
}
}
}
}
impl<F> From<InvalidInputBitSize> for OpcodeResolutionError<F> {
fn from(invalid_input_bit_size: InvalidInputBitSize) -> Self {
Self::InvalidInputBitSize {
opcode_location: ErrorLocation::Unresolved,
invalid_input_bit_size,
}
}
}
pub type ProfilingSamples = Vec<ProfilingSample>;
#[derive(Default)]
pub struct ProfilingSample {
pub call_stack: Vec<OpcodeLocation>,
pub brillig_function_id: Option<BrilligFunctionId>,
}
pub struct ACVM<'a, F: AcirField, B: BlackBoxFunctionSolver<F>> {
status: ACVMStatus<F>,
backend: &'a B,
block_solvers: HashMap<BlockId, MemoryOpSolver<F>>,
opcodes: &'a [Opcode<F>],
instruction_pointer: usize,
witness_map: WitnessMap<F>,
brillig_solver: Option<BrilligSolver<'a, F, B>>,
acir_call_counter: usize,
acir_call_results: Vec<Vec<F>>,
unconstrained_functions: &'a [BrilligBytecode<F>],
assertion_payloads: &'a [(OpcodeLocation, AssertionPayload<F>)],
profiling_active: bool,
profiling_samples: ProfilingSamples,
brillig_fuzzing_active: bool,
brillig_branch_to_feature_map: Option<&'a BranchToFeatureMap>,
brillig_fuzzing_trace: Option<Vec<u32>>,
}
impl<'a, F: AcirField, B: BlackBoxFunctionSolver<F>> ACVM<'a, F, B> {
pub fn new(
backend: &'a B,
opcodes: &'a [Opcode<F>],
initial_witness: WitnessMap<F>,
unconstrained_functions: &'a [BrilligBytecode<F>],
assertion_payloads: &'a [(OpcodeLocation, AssertionPayload<F>)],
) -> Self {
let status = if opcodes.is_empty() { ACVMStatus::Solved } else { ACVMStatus::InProgress };
ACVM {
status,
backend,
block_solvers: HashMap::default(),
opcodes,
instruction_pointer: 0,
witness_map: initial_witness,
brillig_solver: None,
acir_call_counter: 0,
acir_call_results: Vec::default(),
unconstrained_functions,
assertion_payloads,
profiling_active: false,
profiling_samples: Vec::new(),
brillig_fuzzing_active: false,
brillig_branch_to_feature_map: None,
brillig_fuzzing_trace: None,
}
}
pub fn with_profiler(&mut self, profiling_active: bool) {
self.profiling_active = profiling_active;
}
pub fn with_brillig_fuzzing(
&mut self,
brillig_branch_to_feature_map: Option<&'a BranchToFeatureMap>,
) {
self.brillig_fuzzing_active = brillig_branch_to_feature_map.is_some();
self.brillig_branch_to_feature_map = brillig_branch_to_feature_map;
}
pub fn get_brillig_fuzzing_trace(&self) -> Option<Vec<u32>> {
self.brillig_fuzzing_trace.clone()
}
pub fn witness_map(&self) -> &WitnessMap<F> {
&self.witness_map
}
pub fn overwrite_witness(&mut self, witness: Witness, value: F) -> Option<F> {
self.witness_map.insert(witness, value)
}
pub fn opcodes(&self) -> &[Opcode<F>] {
self.opcodes
}
pub fn instruction_pointer(&self) -> usize {
self.instruction_pointer
}
pub fn take_profiling_samples(&mut self) -> ProfilingSamples {
std::mem::take(&mut self.profiling_samples)
}
pub fn finalize(self) -> WitnessMap<F> {
if self.status != ACVMStatus::Solved {
panic!("ACVM execution is not complete: ({})", self.status);
}
self.witness_map
}
fn status(&mut self, status: ACVMStatus<F>) -> ACVMStatus<F> {
self.status = status.clone();
status
}
pub fn get_status(&self) -> &ACVMStatus<F> {
&self.status
}
fn fail(&mut self, error: OpcodeResolutionError<F>) -> ACVMStatus<F> {
self.status(ACVMStatus::Failure(error))
}
fn wait_for_foreign_call(&mut self, foreign_call: ForeignCallWaitInfo<F>) -> ACVMStatus<F> {
self.status(ACVMStatus::RequiresForeignCall(foreign_call))
}
pub fn get_pending_foreign_call(&self) -> Option<&ForeignCallWaitInfo<F>> {
if let ACVMStatus::RequiresForeignCall(foreign_call) = &self.status {
Some(foreign_call)
} else {
None
}
}
pub fn resolve_pending_foreign_call(&mut self, foreign_call_result: ForeignCallResult<F>) {
if !matches!(self.status, ACVMStatus::RequiresForeignCall(_)) {
panic!("ACVM is not expecting a foreign call response as no call was made");
}
let brillig_solver = self.brillig_solver.as_mut().expect("No active Brillig solver");
brillig_solver.resolve_pending_foreign_call(foreign_call_result);
self.status(ACVMStatus::InProgress);
}
fn wait_for_acir_call(&mut self, acir_call: AcirCallWaitInfo<F>) -> ACVMStatus<F> {
self.status(ACVMStatus::RequiresAcirCall(acir_call))
}
pub fn resolve_pending_acir_call(&mut self, call_result: Vec<F>) {
if !matches!(self.status, ACVMStatus::RequiresAcirCall(_)) {
panic!("ACVM is not expecting an ACIR call response as no call was made");
}
if self.acir_call_counter < self.acir_call_results.len() {
panic!("No unresolved ACIR calls");
}
self.acir_call_results.push(call_result);
self.status(ACVMStatus::InProgress);
}
pub fn solve(&mut self) -> ACVMStatus<F> {
while self.status == ACVMStatus::InProgress {
self.solve_opcode();
}
self.status.clone()
}
fn current_opcode(&self) -> &'a Opcode<F> {
&self.opcodes[self.instruction_pointer]
}
pub fn solve_opcode(&mut self) -> ACVMStatus<F> {
let resolution = match self.current_opcode() {
Opcode::AssertZero(expr) => ExpressionSolver::solve(&mut self.witness_map, expr),
Opcode::BlackBoxFuncCall(bb_func) => {
blackbox::solve(self.backend, &mut self.witness_map, bb_func)
}
Opcode::MemoryInit { block_id, init, .. } => {
MemoryOpSolver::new(init, &self.witness_map).map(|solver| {
let existing_block_id = self.block_solvers.insert(*block_id, solver);
assert!(existing_block_id.is_none(), "Memory block already initialized");
})
}
Opcode::MemoryOp { block_id, op } => {
let solver = self
.block_solvers
.get_mut(block_id)
.expect("Memory block should have been initialized before use");
solver.solve_memory_op(op, &mut self.witness_map)
}
Opcode::BrilligCall { id, inputs, outputs, predicate } => {
match self.solve_brillig_call_opcode(id, inputs, outputs, predicate) {
Ok(Some(foreign_call)) => return self.wait_for_foreign_call(foreign_call),
res => res.map(|_| ()),
}
}
Opcode::Call { id, inputs, outputs, predicate } => {
match self.solve_call_opcode(id, inputs, outputs, predicate) {
Ok(Some(input_values)) => return self.wait_for_acir_call(input_values),
res => res.map(|_| ()),
}
}
};
self.handle_opcode_resolution(resolution)
}
fn handle_opcode_resolution(
&mut self,
resolution: Result<(), OpcodeResolutionError<F>>,
) -> ACVMStatus<F> {
match resolution {
Ok(()) => {
self.instruction_pointer += 1;
if self.instruction_pointer == self.opcodes.len() {
self.status(ACVMStatus::Solved)
} else {
self.status(ACVMStatus::InProgress)
}
}
Err(mut error) => {
match &mut error {
OpcodeResolutionError::IndexOutOfBounds {
opcode_location: opcode_index,
..
} => {
*opcode_index = ErrorLocation::Resolved(OpcodeLocation::Acir(
self.instruction_pointer(),
));
}
OpcodeResolutionError::UnsatisfiedConstrain {
opcode_location: opcode_index,
payload: assertion_payload,
} => {
let location = OpcodeLocation::Acir(self.instruction_pointer());
*opcode_index = ErrorLocation::Resolved(location);
*assertion_payload = self.extract_assertion_payload(location);
}
OpcodeResolutionError::InvalidInputBitSize {
opcode_location: opcode_index,
..
} => {
let location = OpcodeLocation::Acir(self.instruction_pointer());
*opcode_index = ErrorLocation::Resolved(location);
}
_ => (),
}
self.fail(error)
}
}
}
fn extract_assertion_payload(
&self,
location: OpcodeLocation,
) -> Option<ResolvedAssertionPayload<F>> {
let (_, assertion_descriptor) =
self.assertion_payloads.iter().find(|(loc, _)| location == *loc)?;
let mut fields = Vec::new();
for expr in &assertion_descriptor.payload {
match expr {
ExpressionOrMemory::Expression(expr) => {
let value = get_value(expr, &self.witness_map).ok()?;
fields.push(value);
}
ExpressionOrMemory::Memory(block_id) => {
let memory_block = self.block_solvers.get(block_id)?;
fields.extend(&memory_block.block_value);
}
}
}
let error_selector = ErrorSelector::new(assertion_descriptor.error_selector);
Some(ResolvedAssertionPayload::Raw(RawAssertionPayload {
selector: error_selector,
data: fields,
}))
}
fn solve_brillig_call_opcode(
&mut self,
id: &BrilligFunctionId,
inputs: &'a [BrilligInputs<F>],
outputs: &[BrilligOutputs],
predicate: &Expression<F>,
) -> Result<Option<ForeignCallWaitInfo<F>>, OpcodeResolutionError<F>> {
let opcode_location =
ErrorLocation::Resolved(OpcodeLocation::Acir(self.instruction_pointer()));
if is_predicate_false(&self.witness_map, predicate, &opcode_location)? {
return BrilligSolver::<F, B>::zero_out_brillig_outputs(&mut self.witness_map, outputs)
.map(|_| None);
}
let mut solver: BrilligSolver<'_, F, B> = match self.brillig_solver.take() {
Some(solver) => solver,
None => BrilligSolver::new_call(
&self.witness_map,
&self.block_solvers,
inputs,
&self.unconstrained_functions[id.as_usize()].bytecode,
self.backend,
self.instruction_pointer,
*id,
self.profiling_active,
self.brillig_branch_to_feature_map,
)?,
};
let result = solver.solve().inspect_err(|_| {
if self.brillig_fuzzing_active {
self.brillig_fuzzing_trace = Some(solver.get_fuzzing_trace());
}
})?;
match result {
BrilligSolverStatus::ForeignCallWait(foreign_call) => {
self.brillig_solver = Some(solver);
Ok(Some(foreign_call))
}
BrilligSolverStatus::InProgress => {
unreachable!("Brillig solver still in progress")
}
BrilligSolverStatus::Finished => {
if self.brillig_fuzzing_active {
self.brillig_fuzzing_trace = Some(solver.get_fuzzing_trace());
}
if self.profiling_active {
let profiling_info =
solver.finalize_with_profiling(&mut self.witness_map, outputs)?;
profiling_info.into_iter().for_each(|sample| {
let mapped =
sample.call_stack.into_iter().map(|loc| OpcodeLocation::Brillig {
acir_index: self.instruction_pointer,
brillig_index: loc,
});
self.profiling_samples.push(ProfilingSample {
call_stack: std::iter::once(OpcodeLocation::Acir(
self.instruction_pointer,
))
.chain(mapped)
.collect(),
brillig_function_id: Some(*id),
});
});
} else {
solver.finalize(&mut self.witness_map, outputs)?;
}
Ok(None)
}
}
}
pub fn step_into_brillig(&mut self) -> StepResult<'a, F, B> {
let Opcode::BrilligCall { id, inputs, outputs, predicate } = self.current_opcode() else {
return StepResult::Status(self.solve_opcode());
};
let opcode_location =
ErrorLocation::Resolved(OpcodeLocation::Acir(self.instruction_pointer()));
let witness = &mut self.witness_map;
let should_skip = match is_predicate_false(witness, predicate, &opcode_location) {
Ok(result) => result,
Err(err) => return StepResult::Status(self.handle_opcode_resolution(Err(err))),
};
if should_skip {
let resolution = BrilligSolver::<F, B>::zero_out_brillig_outputs(witness, outputs);
return StepResult::Status(self.handle_opcode_resolution(resolution));
}
let solver = BrilligSolver::new_call(
witness,
&self.block_solvers,
inputs,
&self.unconstrained_functions[id.as_usize()].bytecode,
self.backend,
self.instruction_pointer,
*id,
self.profiling_active,
self.brillig_branch_to_feature_map,
);
match solver {
Ok(solver) => StepResult::IntoBrillig(solver),
Err(..) => StepResult::Status(self.handle_opcode_resolution(solver.map(|_| ()))),
}
}
pub fn finish_brillig_with_solver(&mut self, solver: BrilligSolver<'a, F, B>) -> ACVMStatus<F> {
if !matches!(self.current_opcode(), Opcode::BrilligCall { .. }) {
unreachable!("Not executing a Brillig/BrilligCall opcode");
}
self.brillig_solver = Some(solver);
self.solve_opcode()
}
pub fn solve_call_opcode(
&mut self,
id: &AcirFunctionId,
inputs: &[Witness],
outputs: &[Witness],
predicate: &Expression<F>,
) -> Result<Option<AcirCallWaitInfo<F>>, OpcodeResolutionError<F>> {
let opcode_location =
ErrorLocation::Resolved(OpcodeLocation::Acir(self.instruction_pointer()));
if *id == AcirFunctionId(0) {
return Err(OpcodeResolutionError::AcirMainCallAttempted { opcode_location });
}
if is_predicate_false(&self.witness_map, predicate, &opcode_location)? {
for output in outputs {
insert_value(output, F::zero(), &mut self.witness_map)?;
}
return Ok(None);
}
if self.acir_call_counter >= self.acir_call_results.len() {
let mut initial_witness = WitnessMap::default();
for (i, input_witness) in inputs.iter().enumerate() {
let input_value = *witness_to_value(&self.witness_map, *input_witness)?;
initial_witness.insert(Witness(i as u32), input_value);
}
return Ok(Some(AcirCallWaitInfo { id: *id, initial_witness }));
}
let result_values = &self.acir_call_results[self.acir_call_counter];
if outputs.len() != result_values.len() {
return Err(OpcodeResolutionError::AcirCallOutputsMismatch {
opcode_location,
results_size: result_values.len() as u32,
outputs_size: outputs.len() as u32,
});
}
for (output_witness, result_value) in outputs.iter().zip_eq(result_values) {
insert_value(output_witness, *result_value, &mut self.witness_map)?;
}
self.acir_call_counter += 1;
Ok(None)
}
}
pub fn witness_to_value<F>(
initial_witness: &WitnessMap<F>,
witness: Witness,
) -> Result<&F, OpcodeResolutionError<F>> {
match initial_witness.get(&witness) {
Some(value) => Ok(value),
None => Err(OpcodeNotSolvable::MissingAssignment(witness.0).into()),
}
}
pub fn input_to_value<F: AcirField>(
initial_witness: &WitnessMap<F>,
input: FunctionInput<F>,
) -> Result<F, OpcodeResolutionError<F>> {
match input {
FunctionInput::Witness(witness) => {
let initial_value = *witness_to_value(initial_witness, witness)?;
Ok(initial_value)
}
FunctionInput::Constant(value) => Ok(value),
}
}
pub fn check_bit_size<F: AcirField>(
value: F,
num_bits: u32,
) -> Result<(), OpcodeResolutionError<F>> {
if value.num_bits() <= num_bits {
Ok(())
} else {
let value_num_bits = value.num_bits();
let value = value.to_string();
Err(OpcodeResolutionError::InvalidInputBitSize {
opcode_location: ErrorLocation::Unresolved,
invalid_input_bit_size: InvalidInputBitSize {
value,
value_num_bits,
max_bits: num_bits,
},
})
}
}
pub fn get_value<F: AcirField>(
expr: &Expression<F>,
initial_witness: &WitnessMap<F>,
) -> Result<F, OpcodeResolutionError<F>> {
if let Some(&c) = expr.to_const() {
return Ok(c);
}
let expr = ExpressionSolver::evaluate(expr, initial_witness);
match expr.to_const() {
Some(value) => Ok(*value),
None => Err(OpcodeResolutionError::OpcodeNotSolvable(
OpcodeNotSolvable::MissingAssignment(any_witness_from_expression(&expr).unwrap().0),
)),
}
}
pub fn insert_value<F: AcirField>(
witness: &Witness,
value_to_insert: F,
initial_witness: &mut WitnessMap<F>,
) -> Result<(), OpcodeResolutionError<F>> {
use std::collections::btree_map::Entry;
match initial_witness.entry(*witness) {
Entry::Vacant(e) => {
e.insert(value_to_insert);
Ok(())
}
Entry::Occupied(e) => {
if *e.get() != value_to_insert {
Err(OpcodeResolutionError::UnsatisfiedConstrain {
opcode_location: ErrorLocation::Unresolved,
payload: None,
})
} else {
Ok(())
}
}
}
}
fn any_witness_from_expression<F>(expr: &Expression<F>) -> Option<Witness> {
if expr.linear_combinations.is_empty() {
if expr.mul_terms.is_empty() { None } else { Some(expr.mul_terms[0].1) }
} else {
Some(expr.linear_combinations[0].1)
}
}
pub(crate) fn is_predicate_false<F: AcirField>(
witness: &WitnessMap<F>,
predicate: &Expression<F>,
opcode_location: &ErrorLocation,
) -> Result<bool, OpcodeResolutionError<F>> {
let pred_value = get_value(predicate, witness)?;
let predicate_is_false = pred_value.is_zero();
if !predicate_is_false && !pred_value.is_one() {
let opcode_location = *opcode_location;
return Err(OpcodeResolutionError::PredicateLargerThanOne { opcode_location, pred_value });
}
Ok(predicate_is_false)
}
#[derive(Debug, Clone, PartialEq)]
pub struct AcirCallWaitInfo<F> {
pub id: AcirFunctionId,
pub initial_witness: WitnessMap<F>,
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use acir::{
FieldElement,
native_types::{Witness, WitnessMap},
parse_opcodes,
};
use crate::pwg::{ACVM, ACVMStatus, OpcodeResolutionError};
#[test]
fn solve_simple_circuit() {
let initial_witness = WitnessMap::from(BTreeMap::from_iter([
(Witness(1), FieldElement::from(1u128)),
(Witness(2), FieldElement::from(1u128)),
(Witness(3), FieldElement::from(2u128)),
]));
let backend = acvm_blackbox_solver::StubbedBlackBoxSolver;
let src = "
BLACKBOX::RANGE input: w1, bits: 32
BLACKBOX::RANGE input: w2, bits: 32
BLACKBOX::RANGE input: w3, bits: 32
ASSERT w4 = 2*w1 - w2
ASSERT w5 = -w2*w4 + 1
";
let opcodes = parse_opcodes(src).unwrap();
let mut acvm = ACVM::new(&backend, &opcodes, initial_witness, &[], &[]);
assert_eq!(acvm.solve(), ACVMStatus::Solved);
assert_eq!(acvm.witness_map()[&Witness(5)], FieldElement::from(0u128));
}
#[test]
fn insert_value_does_not_overwrite_on_conflict() {
use crate::pwg::insert_value;
let old_value = FieldElement::from(1u128);
let new_value = FieldElement::from(2u128);
let witness = Witness(0);
let mut witness_map = WitnessMap::new();
insert_value(&witness, old_value, &mut witness_map).expect("first insert should succeed");
let result = insert_value(&witness, new_value, &mut witness_map);
assert!(
matches!(result, Err(OpcodeResolutionError::UnsatisfiedConstrain { .. })),
"expected UnsatisfiedConstrain error on conflicting insert"
);
assert_eq!(witness_map[&witness], old_value, "map should still hold the original value");
}
#[test]
fn errors_when_calling_function_zero() {
let initial_witness =
WitnessMap::from(BTreeMap::from_iter([(Witness(1), FieldElement::from(1u128))]));
let backend = acvm_blackbox_solver::StubbedBlackBoxSolver;
let src = "
CALL func: 0, predicate: 1, inputs: [w1], outputs: [w2]
";
let opcodes = parse_opcodes(src).unwrap();
let mut acvm = ACVM::new(&backend, &opcodes, initial_witness, &[], &[]);
assert!(matches!(
acvm.solve(),
ACVMStatus::Failure(OpcodeResolutionError::AcirMainCallAttempted { .. })
));
}
}