-
Notifications
You must be signed in to change notification settings - Fork 405
Expand file tree
/
Copy pathlib.rs
More file actions
104 lines (87 loc) · 3.69 KB
/
Copy pathlib.rs
File metadata and controls
104 lines (87 loc) · 3.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
//! This module has been adapted from Foundry's fuzzing implementation for the EVM.
//! https://github.com/foundry-rs/foundry/blob/6a85dbaa62f1c305f31cab37781232913055ae28/crates/evm/evm/src/executors/fuzz/mod.rs#L40
//!
//! Code is used under the MIT license.
use acvm::{
acir::{
circuit::Program,
native_types::{WitnessMap, WitnessStack},
},
FieldElement,
};
use dictionary::build_dictionary_from_program;
use noirc_abi::InputMap;
use proptest::test_runner::{TestCaseError, TestError, TestRunner};
mod dictionary;
mod strategies;
mod types;
use types::{CaseOutcome, CounterExampleOutcome, FuzzOutcome, FuzzTestResult};
use noirc_artifacts::program::ProgramArtifact;
/// An executor for Noir programs which provides fuzzing support using [`proptest`].
///
/// After instantiation, calling `fuzz` will proceed to hammer the program with
/// inputs, until it finds a counterexample. The provided [`TestRunner`] contains all the
/// configuration which can be overridden via [environment variables](proptest::test_runner::Config)
pub struct FuzzedExecutor<E> {
/// The program to be fuzzed
program: ProgramArtifact,
/// A function which executes the programs with a given set of inputs
executor: E,
/// The fuzzer
runner: TestRunner,
}
impl<E> FuzzedExecutor<E>
where
E: Fn(
&Program<FieldElement>,
WitnessMap<FieldElement>,
) -> Result<WitnessStack<FieldElement>, String>,
{
/// Instantiates a fuzzed executor given a [TestRunner].
pub fn new(program: ProgramArtifact, executor: E, runner: TestRunner) -> Self {
Self { program, executor, runner }
}
/// Fuzzes the provided program.
pub fn fuzz(&self) -> FuzzTestResult {
let dictionary = build_dictionary_from_program(&self.program.bytecode);
let strategy = strategies::arb_input_map(&self.program.abi, &dictionary);
let run_result: Result<(), TestError<InputMap>> =
self.runner.clone().run(&strategy, |input_map| {
let fuzz_res = self.single_fuzz(input_map)?;
match fuzz_res {
FuzzOutcome::Case(_) => Ok(()),
FuzzOutcome::CounterExample(CounterExampleOutcome {
exit_reason: status,
..
}) => Err(TestCaseError::fail(status)),
}
});
match run_result {
Ok(()) => FuzzTestResult { success: true, reason: None, counterexample: None },
Err(TestError::Abort(reason)) => FuzzTestResult {
success: false,
reason: Some(reason.to_string()),
counterexample: None,
},
Err(TestError::Fail(reason, counterexample)) => {
let reason = reason.to_string();
let reason = if reason.is_empty() { None } else { Some(reason) };
FuzzTestResult { success: false, reason, counterexample: Some(counterexample) }
}
}
}
/// Granular and single-step function that runs only one fuzz and returns either a `CaseOutcome`
/// or a `CounterExampleOutcome`
pub fn single_fuzz(&self, input_map: InputMap) -> Result<FuzzOutcome, TestCaseError> {
let initial_witness = self.program.abi.encode(&input_map, None).unwrap();
let result = (self.executor)(&self.program.bytecode, initial_witness);
// TODO: Add handling for `vm.assume` equivalent
match result {
Ok(_) => Ok(FuzzOutcome::Case(CaseOutcome { case: input_map })),
Err(err) => Ok(FuzzOutcome::CounterExample(CounterExampleOutcome {
exit_reason: err,
counterexample: input_map,
})),
}
}
}