Skip to content

Commit 81a8b79

Browse files
committed
more abi mutations
1 parent ea771e9 commit 81a8b79

File tree

3 files changed

+79
-18
lines changed

3 files changed

+79
-18
lines changed

crates/evm/evm/src/executors/corpus.rs

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use foundry_config::FuzzCorpusConfig;
77
use foundry_evm_fuzz::{
88
BasicTxDetails,
99
invariant::FuzzRunIdentifiedContracts,
10-
strategies::{EvmFuzzState, fuzz_param_from_state},
10+
strategies::{EvmFuzzState, mutate_param_value},
1111
};
1212
use proptest::{
1313
prelude::{Just, Rng, Strategy},
@@ -608,31 +608,33 @@ impl CorpusManager {
608608
test_runner: &mut TestRunner,
609609
fuzz_state: &EvmFuzzState,
610610
) -> eyre::Result<()> {
611-
let rng = test_runner.rng();
612-
let mut arg_mutation_rounds = rng.random_range(0..=function.inputs.len()).max(1);
611+
// let rng = test_runner.rng();
612+
let mut arg_mutation_rounds =
613+
test_runner.rng().random_range(0..=function.inputs.len()).max(1);
613614
let round_arg_idx: Vec<usize> = if function.inputs.len() <= 1 {
614615
vec![0]
615616
} else {
616-
(0..arg_mutation_rounds).map(|_| rng.random_range(0..function.inputs.len())).collect()
617+
(0..arg_mutation_rounds)
618+
.map(|_| test_runner.rng().random_range(0..function.inputs.len()))
619+
.collect()
617620
};
618621
let mut prev_inputs = function
619622
.abi_decode_input(&tx.call_details.calldata[4..])
620623
.map_err(|err| eyre!("failed to load previous inputs: {err}"))?;
621624

622-
// For now, only new inputs are generated, no existing inputs are
623-
// mutated.
624-
let mut gen_input = |input: &alloy_json_abi::Param| {
625-
fuzz_param_from_state(&input.selector_type().parse().unwrap(), fuzz_state)
626-
.new_tree(test_runner)
627-
.expect("Could not generate case")
628-
.current()
629-
};
630-
631625
while arg_mutation_rounds > 0 {
632626
let idx = round_arg_idx[arg_mutation_rounds - 1];
633-
let input = &function.inputs.get(idx).expect("Could not get input to mutate");
634-
let new_input = gen_input(input);
635-
prev_inputs[idx] = new_input;
627+
prev_inputs[idx] = mutate_param_value(
628+
&function
629+
.inputs
630+
.get(idx)
631+
.expect("Could not get input to mutate")
632+
.selector_type()
633+
.parse()?,
634+
prev_inputs[idx].clone(),
635+
test_runner,
636+
fuzz_state,
637+
);
636638
arg_mutation_rounds -= 1;
637639
}
638640

crates/evm/fuzz/src/strategies/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ mod uint;
55
pub use uint::UintStrategy;
66

77
mod param;
8-
pub use param::{fuzz_param, fuzz_param_from_state, fuzz_param_with_fixtures};
8+
pub use param::{fuzz_param, fuzz_param_from_state, fuzz_param_with_fixtures, mutate_param_value};
99

1010
mod calldata;
1111
pub use calldata::{fuzz_calldata, fuzz_calldata_from_state};

crates/evm/fuzz/src/strategies/param.rs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::state::EvmFuzzState;
22
use alloy_dyn_abi::{DynSolType, DynSolValue};
33
use alloy_primitives::{Address, B256, I256, U256};
4-
use proptest::prelude::*;
4+
use proptest::{prelude::*, test_runner::TestRunner};
55
use rand::{SeedableRng, rngs::StdRng};
66

77
/// The max length of arrays we fuzz for is 256.
@@ -104,6 +104,65 @@ fn fuzz_param_inner(
104104
}
105105
}
106106

107+
/// Mutates the current value of a given a parameter type and value.
108+
/// TODO: add more mutations, see https://github.com/fuzzland/ityfuzz/blob/master/src/mutation_utils.rs#L449-L452
109+
/// for static args,
110+
pub fn mutate_param_value(
111+
param: &DynSolType,
112+
value: DynSolValue,
113+
test_runner: &mut TestRunner,
114+
state: &EvmFuzzState,
115+
) -> DynSolValue {
116+
let new_value = |param: &DynSolType, test_runner: &mut TestRunner| {
117+
fuzz_param_from_state(param, state)
118+
.new_tree(test_runner)
119+
.expect("Could not generate case")
120+
.current()
121+
};
122+
123+
match value {
124+
// flip boolean value
125+
DynSolValue::Bool(val) => DynSolValue::Bool(!val),
126+
// Uint: increment, decrement or generate new value from state.
127+
DynSolValue::Uint(val, size) => match test_runner.rng().random_range(0..=2) {
128+
0 => DynSolValue::Uint(val.saturating_add(U256::ONE), size),
129+
1 => DynSolValue::Uint(val.saturating_sub(U256::ONE), size),
130+
_ => new_value(param, test_runner),
131+
},
132+
// Int: increment, decrement or generate new value from state.
133+
DynSolValue::Int(val, size) => match test_runner.rng().random_range(0..=2) {
134+
0 => DynSolValue::Int(val.saturating_add(I256::ONE), size),
135+
1 => DynSolValue::Int(val.saturating_sub(I256::ONE), size),
136+
_ => new_value(param, test_runner),
137+
},
138+
DynSolValue::Array(mut values) => {
139+
if values.is_empty() {
140+
return new_value(param, test_runner);
141+
}
142+
143+
let DynSolType::Array(param_type) = param else { return new_value(param, test_runner) };
144+
145+
match test_runner.rng().random_range(0..=2) {
146+
// Decrease array size by removing a random element.
147+
0 => {
148+
values.remove(test_runner.rng().random_range(0..values.len()));
149+
}
150+
// Increase array size.
151+
1 => values.push(new_value(param_type, test_runner)),
152+
// Mutate array element.
153+
_ => {
154+
let id_to_mutate = test_runner.rng().random_range(0..values.len());
155+
let val = values.get(id_to_mutate).unwrap();
156+
let new_value = mutate_param_value(param_type, val.clone(), test_runner, state);
157+
values[id_to_mutate] = new_value;
158+
}
159+
};
160+
DynSolValue::Array(values)
161+
}
162+
_ => new_value(param, test_runner),
163+
}
164+
}
165+
107166
/// Given a parameter type, returns a strategy for generating values for that type, given some EVM
108167
/// fuzz state.
109168
///

0 commit comments

Comments
 (0)