Skip to main content

noir_artifact_cli/
execution.rs

1use std::path::Path;
2
3use acir::{AcirField, FieldElement, native_types::WitnessStack};
4use acvm::BlackBoxFunctionSolver;
5use nargo::{NargoError, foreign_calls::ForeignCallExecutor};
6use noirc_abi::{AbiType, Sign, input_parser::InputValue};
7use noirc_artifacts::debug::DebugArtifact;
8use noirc_driver::CompiledProgram;
9use noirc_printable_type::format_field_string;
10
11use crate::{
12    errors::CliError,
13    fs::{inputs::read_inputs_from_file, witness::save_witness_to_dir},
14};
15
16/// Results of a circuit execution.
17#[derive(Clone, Debug)]
18pub struct ExecutionResults {
19    pub witness_stack: WitnessStack<FieldElement>,
20    pub return_values: ReturnValues,
21}
22
23/// The decoded `return` witnesses.
24#[derive(Clone, Debug)]
25pub struct ReturnValues {
26    /// The `return` value from the `Prover.toml` file, if present.
27    pub expected_return: Option<InputValue>,
28    /// The `return` value from the circuit execution.
29    pub actual_return: Option<InputValue>,
30}
31
32/// Execute a circuit and return the output witnesses.
33pub fn execute<B, E>(
34    circuit: &CompiledProgram,
35    blackbox_solver: &B,
36    foreign_call_executor: &mut E,
37    prover_file: &Path,
38) -> Result<ExecutionResults, CliError>
39where
40    B: BlackBoxFunctionSolver<FieldElement>,
41    E: ForeignCallExecutor<FieldElement>,
42{
43    let (input_map, expected_return) = read_inputs_from_file(prover_file, &circuit.abi)?;
44
45    let initial_witness = circuit.abi.encode(&input_map, None)?;
46
47    let witness_stack = nargo::ops::execute_program(
48        &circuit.program,
49        initial_witness,
50        blackbox_solver,
51        foreign_call_executor,
52    )?;
53
54    let main_witness =
55        &witness_stack.peek().expect("Should have at least one witness on the stack").witness;
56
57    let (_, actual_return) = circuit.abi.decode(main_witness)?;
58
59    Ok(ExecutionResults {
60        witness_stack,
61        return_values: ReturnValues { actual_return, expected_return },
62    })
63}
64
65/// Print an error stack trace, if possible.
66pub fn show_diagnostic(circuit: &CompiledProgram, err: &NargoError<FieldElement>) {
67    if let Some(diagnostic) =
68        nargo::errors::try_to_diagnose_runtime_error(err, &circuit.abi, &circuit.debug)
69    {
70        let debug_artifact = DebugArtifact {
71            debug_symbols: circuit.debug.clone(),
72            file_map: circuit.file_map.clone(),
73        };
74
75        diagnostic.report(&debug_artifact, false);
76    }
77}
78
79/// Print some information and save the witness if an output directory is specified,
80/// then checks if the expected return values were the ones we expected.
81pub fn save_and_check_witness(
82    circuit: &CompiledProgram,
83    results: ExecutionResults,
84    circuit_name: &str,
85    witness_dir: Option<&Path>,
86    witness_name: Option<&str>,
87) -> Result<(), CliError> {
88    noirc_errors::println_to_stdout!("[{circuit_name}] Circuit witness successfully solved");
89    // Save first, so that we can potentially look at the output if the expectations fail.
90    if let Some(witness_dir) = witness_dir {
91        save_witness(&results.witness_stack, circuit_name, witness_dir, witness_name)?;
92    }
93    if let Some(ref return_value) = results.return_values.actual_return {
94        let abi_type = &circuit.abi.return_type.as_ref().unwrap().abi_type;
95        let output_string = input_value_to_string(return_value, abi_type);
96        noirc_errors::println_to_stdout!("[{circuit_name}] Circuit output: {output_string}");
97    }
98    check_witness(circuit, results.return_values)
99}
100
101/// Save the witness stack to a file.
102pub fn save_witness(
103    witness_stack: &WitnessStack<FieldElement>,
104    circuit_name: &str,
105    witness_dir: &Path,
106    witness_name: Option<&str>,
107) -> Result<(), CliError> {
108    let witness_name = witness_name.unwrap_or(circuit_name);
109    let mut witness_path = save_witness_to_dir(witness_stack, witness_name, witness_dir)?;
110
111    // See if we can make the file path a bit shorter/easier to read if it starts with the current directory
112    if let Ok(current_dir) = std::env::current_dir() {
113        if let Ok(name_without_prefix) = witness_path.strip_prefix(current_dir) {
114            witness_path = name_without_prefix.to_path_buf();
115        }
116    }
117
118    noirc_errors::println_to_stdout!(
119        "[{}] Witness saved to {}",
120        circuit_name,
121        witness_path.display()
122    );
123    Ok(())
124}
125
126/// Compare return values to expectations, returning errors if something unexpected was returned.
127pub fn check_witness(
128    circuit: &CompiledProgram,
129    return_values: ReturnValues,
130) -> Result<(), CliError> {
131    // Check that the circuit returned a non-empty result if the ABI expects a return value.
132    if let Some(ref expected) = circuit.abi.return_type {
133        if return_values.actual_return.is_none() {
134            return Err(CliError::MissingReturn { expected: expected.clone() });
135        }
136    }
137
138    // Check that if the prover file contained a `return` entry then that's what we got.
139    if let Some(expected) = return_values.expected_return {
140        match return_values.actual_return {
141            None => {
142                return Err(CliError::UnexpectedReturn { expected, actual: None });
143            }
144            Some(actual) => {
145                if actual != expected {
146                    return Err(CliError::UnexpectedReturn { expected, actual: Some(actual) });
147                }
148            }
149        }
150    }
151
152    Ok(())
153}
154
155pub fn input_value_to_string(input_value: &InputValue, abi_type: &AbiType) -> String {
156    let mut string = String::new();
157    append_input_value_to_string(input_value, abi_type, &mut string);
158    string
159}
160
161fn append_input_value_to_string(input_value: &InputValue, abi_type: &AbiType, string: &mut String) {
162    match (abi_type, input_value) {
163        (AbiType::Field, InputValue::Field(field_element)) => {
164            string.push_str(&format_field_string(*field_element));
165        }
166        (AbiType::Array { length: _, typ }, InputValue::Vec(input_values)) => {
167            string.push('[');
168            for (index, input_value) in input_values.iter().enumerate() {
169                if index != 0 {
170                    string.push_str(", ");
171                }
172                append_input_value_to_string(input_value, typ, string);
173            }
174            string.push(']');
175        }
176        (AbiType::Integer { sign, width: bit_size }, InputValue::Field(f)) => match sign {
177            Sign::Unsigned => {
178                string.push_str(&f.to_string());
179            }
180            Sign::Signed => {
181                let bit_size = *bit_size;
182                let max =
183                    if bit_size == 128 { i128::MAX as u128 } else { (1 << (bit_size - 1)) - 1 };
184                if f.num_bits() > 128 || f.to_u128() > max {
185                    string.push('-');
186                    let f = FieldElement::from(2u32).pow(&bit_size.into()) - *f;
187                    string.push_str(&f.to_string());
188                } else {
189                    string.push_str(&f.to_string());
190                }
191            }
192        },
193        (AbiType::Boolean, InputValue::Field(field_element)) => {
194            if field_element.is_zero() {
195                string.push_str("false");
196            } else {
197                string.push_str("true");
198            }
199        }
200        (AbiType::Struct { path, fields: field_types }, InputValue::Struct(field_values)) => {
201            string.push_str(path);
202            string.push_str(" { ");
203            for (index, (field_name, field_value)) in field_values.iter().enumerate() {
204                if index != 0 {
205                    string.push_str(", ");
206                }
207                string.push_str(field_name);
208                string.push_str(": ");
209                let typ = &field_types.iter().find(|(name, _)| name == field_name).unwrap().1;
210                append_input_value_to_string(field_value, typ, string);
211            }
212            string.push_str(" }");
213        }
214        (AbiType::Tuple { fields }, InputValue::Vec(input_values)) => {
215            assert_eq!(fields.len(), input_values.len());
216
217            string.push('(');
218            for (index, (input_value, field_type)) in input_values.iter().zip(fields).enumerate() {
219                if index != 0 {
220                    string.push_str(", ");
221                }
222                append_input_value_to_string(input_value, field_type, string);
223            }
224            if input_values.len() == 1 {
225                string.push(',');
226            }
227            string.push(')');
228        }
229        (AbiType::String { .. }, InputValue::String(value)) => {
230            string.push_str(&format!("{value:?}"));
231        }
232        (_, _) => {
233            panic!("Unexpected InputValue-AbiType combination: {input_value:?} - {abi_type:?}");
234        }
235    }
236}