Skip to content

Commit

Permalink
Lookup function from identified target abi.
Browse files Browse the repository at this point in the history
  • Loading branch information
grandizzy committed May 15, 2024
1 parent e91dcfa commit 481971a
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 71 deletions.
15 changes: 5 additions & 10 deletions crates/evm/evm/src/executors/invariant/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ impl<'a> InvariantExecutor<'a> {
let call_result = executor
.call_raw_committing(
tx.sender,
tx.call_details.address,
tx.call_details.target,
tx.call_details.calldata.clone(),
U256::ZERO,
)
Expand Down Expand Up @@ -673,16 +673,11 @@ fn collect_data(
sender_changeset = state_changeset.remove(&tx.sender);
}

let target_abi = fuzzed_contracts
.targets
.lock()
.get(&tx.call_details.address)
.map(|(_, abi, _)| abi.clone())
.unwrap_or_default();

// Collect values from fuzzed call result and add them to fuzz dictionary.
let (fuzzed_contract_abi, fuzzed_function) = fuzzed_contracts.fuzzed_artifacts(tx);
fuzz_state.collect_values_from_call(
&tx.call_details.function,
&target_abi,
fuzzed_contract_abi.as_ref(),
fuzzed_function.as_ref(),
&call_result.result,
&call_result.logs,
&*state_changeset,
Expand Down
8 changes: 5 additions & 3 deletions crates/evm/evm/src/executors/invariant/replay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub fn replay_run(
for tx in inputs.iter() {
let call_result = executor.call_raw_committing(
tx.sender,
tx.call_details.address,
tx.call_details.target,
tx.call_details.calldata.clone(),
U256::ZERO,
)?;
Expand All @@ -60,8 +60,10 @@ pub fn replay_run(
));

// Create counter example to be used in failed case.
counterexample_sequence.push(BaseCounterExample::from_tx_details(
tx,
counterexample_sequence.push(BaseCounterExample::create(
tx.sender,
tx.call_details.target,
&tx.call_details.calldata,
&ided_contracts,
call_result.traces,
));
Expand Down
2 changes: 1 addition & 1 deletion crates/evm/evm/src/executors/invariant/shrink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ fn check_sequence(
let tx = &calls[call_index];
let call_result = executor.call_raw_committing(
tx.sender,
tx.call_details.address,
tx.call_details.target,
tx.call_details.calldata.clone(),
U256::ZERO,
)?;
Expand Down
6 changes: 3 additions & 3 deletions crates/evm/fuzz/src/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ impl Fuzzer {
if let Some(tx) = call_generator.next(call.context.caller, call.contract) {
*call.input = tx.call_details.calldata.0;
call.context.caller = tx.sender;
call.contract = tx.call_details.address;
call.contract = tx.call_details.target;

// TODO: in what scenarios can the following be problematic
call.context.code_address = tx.call_details.address;
call.context.address = tx.call_details.address;
call.context.code_address = tx.call_details.target;
call.context.address = tx.call_details.target;

call_generator.used = true;
}
Expand Down
33 changes: 16 additions & 17 deletions crates/evm/fuzz/src/invariant/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use alloy_dyn_abi::{DynSolValue, JsonAbiExt};
use alloy_json_abi::{Function, JsonAbi};
use alloy_primitives::{Address, Bytes};
use parking_lot::Mutex;
Expand Down Expand Up @@ -27,6 +26,18 @@ impl FuzzRunIdentifiedContracts {
pub fn new(targets: TargetedContracts, is_updatable: bool) -> Self {
Self { targets: Arc::new(Mutex::new(targets)), is_updatable }
}

/// Returns fuzzed contract abi and fuzzed function from address and provided calldata.
/// Used to decode return values and logs in order to add values into fuzz dictionary.
pub fn fuzzed_artifacts(&self, tx: &BasicTxDetails) -> (Option<JsonAbi>, Option<Function>) {
match self.targets.lock().get(&tx.call_details.target) {
Some((_, abi, _)) => (
Some(abi.to_owned()),
abi.functions().find(|f| f.selector() == tx.call_details.calldata[..4]).cloned(),
),
None => (None, None),
}
}
}

/// Details of a transaction generated by invariant strategy for fuzzing a target.
Expand All @@ -47,27 +58,15 @@ impl BasicTxDetails {
/// Call details of a transaction generated to fuzz invariant target.
#[derive(Clone, Debug)]
pub struct CallDetails {
// Target contract address.
pub address: Address,
// Address of target contract.
pub target: Address,
// The data of the transaction.
pub calldata: Bytes,
// Target function, used to decode values from result and to create counterexample.
pub function: Function,
}

impl CallDetails {
pub fn new(address: Address, calldata: Bytes, function: Function) -> Self {
Self { address, calldata, function }
}

// Returns args of the call (decoded from calldata).
pub fn args(&self) -> Vec<DynSolValue> {
// Skip the function selector when decoding.
if let Ok(args) = self.function.abi_decode_input(&self.calldata[4..], false) {
args
} else {
vec![]
}
pub fn new(target: Address, calldata: Bytes) -> Self {
Self { target, calldata }
}
}

Expand Down
42 changes: 27 additions & 15 deletions crates/evm/fuzz/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#[macro_use]
extern crate tracing;

use alloy_dyn_abi::DynSolValue;
use alloy_dyn_abi::{DynSolValue, JsonAbiExt};
use alloy_primitives::{Address, Bytes, Log};
use foundry_common::{calc, contracts::ContractsByAddress};
use foundry_evm_coverage::HitMaps;
Expand All @@ -25,7 +25,6 @@ pub mod invariant;
pub mod strategies;

mod inspector;
use crate::invariant::BasicTxDetails;
pub use inspector::Fuzzer;

#[derive(Clone, Debug, Serialize, Deserialize)]
Expand Down Expand Up @@ -56,25 +55,38 @@ pub struct BaseCounterExample {
}

impl BaseCounterExample {
pub fn from_tx_details(
tx: &BasicTxDetails,
pub fn create(
sender: Address,
addr: Address,
bytes: &Bytes,
contracts: &ContractsByAddress,
traces: Option<CallTraceArena>,
) -> Self {
let contract_name = if let Some((name, _)) = &contracts.get(&tx.call_details.address) {
Some(name.clone())
} else {
None
};
if let Some((name, abi)) = &contracts.get(&addr) {
if let Some(func) = abi.functions().find(|f| f.selector() == bytes[..4]) {
// skip the function selector when decoding
if let Ok(args) = func.abi_decode_input(&bytes[4..], false) {
return BaseCounterExample {
sender: Some(sender),
addr: Some(addr),
calldata: bytes.clone(),
signature: Some(func.signature()),
contract_name: Some(name.clone()),
traces,
args,
};
}
}
}

BaseCounterExample {
sender: Some(tx.sender),
addr: Some(tx.call_details.address),
calldata: tx.call_details.calldata.clone(),
signature: Some(tx.call_details.function.signature()),
contract_name,
sender: Some(sender),
addr: Some(addr),
calldata: bytes.clone(),
signature: None,
contract_name: None,
traces,
args: tx.call_details.args(),
args: vec![],
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions crates/evm/fuzz/src/strategies/invariants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ fn select_random_function(
pub fn fuzz_contract_with_calldata(
fuzz_state: &EvmFuzzState,
fuzz_fixtures: &FuzzFixtures,
contract: Address,
target: Address,
func: Function,
) -> impl Strategy<Value = CallDetails> {
// We need to compose all the strategies generated for each parameter in all possible
Expand All @@ -176,10 +176,10 @@ pub fn fuzz_contract_with_calldata(
#[allow(clippy::arc_with_non_send_sync)]
prop_oneof![
60 => fuzz_calldata(func.clone(), fuzz_fixtures),
40 => fuzz_calldata_from_state(func.clone(), fuzz_state),
40 => fuzz_calldata_from_state(func, fuzz_state),
]
.prop_map(move |calldata| {
trace!(input=?calldata);
CallDetails::new(contract, calldata, func.clone())
CallDetails { target, calldata }
})
}
45 changes: 26 additions & 19 deletions crates/evm/fuzz/src/strategies/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,16 @@ impl EvmFuzzState {
/// the given [FuzzDictionaryConfig].
pub fn collect_values_from_call(
&self,
function: &Function,
abi: &JsonAbi,
target_abi: Option<&JsonAbi>,
target_function: Option<&Function>,
result: &Bytes,
logs: &[Log],
state_changeset: &StateChangeset,
run_depth: u32,
) {
let mut dict = self.inner.write();
dict.insert_result_values(function, result, run_depth);
dict.insert_logs_values(abi, logs, run_depth);
dict.insert_result_values(target_function, result, run_depth);
dict.insert_logs_values(target_abi, logs, run_depth);
dict.insert_state_values(state_changeset);
}

Expand Down Expand Up @@ -128,31 +128,37 @@ impl FuzzDictionary {
}

/// Insert values collected from call result into fuzz dictionary.
fn insert_result_values(&mut self, function: &Function, result: &Bytes, run_depth: u32) {
let mut samples = Vec::new();
if !function.outputs.is_empty() {
// Decode result and collect samples to be used in subsequent fuzz runs.
if let Ok(decoded_result) = function.abi_decode_output(result, false) {
samples.extend(decoded_result);
fn insert_result_values(
&mut self,
function: Option<&Function>,
result: &Bytes,
run_depth: u32,
) {
if let Some(function) = function {
if !function.outputs.is_empty() {
// Decode result and collect samples to be used in subsequent fuzz runs.
if let Ok(decoded_result) = function.abi_decode_output(result, false) {
self.insert_sample_values(decoded_result, run_depth);
}
}
}
self.insert_sample_values(samples, run_depth);
}

/// Insert values from call log topics and data into fuzz dictionary.
/// These values are removed at the end of current run.
fn insert_logs_values(&mut self, abi: &JsonAbi, logs: &[Log], run_depth: u32) {
fn insert_logs_values(&mut self, abi: Option<&JsonAbi>, logs: &[Log], run_depth: u32) {
let mut samples = Vec::new();
// Decode logs with known events and collect samples from indexed fields and event body.
for log in logs {
let mut log_decoded = false;
// Try to decode log with events from contract abi.
for event in abi.events() {
if let Ok(decoded_event) = event.decode_log(log, false) {
samples.extend(decoded_event.indexed);
samples.extend(decoded_event.body);
log_decoded = true;
break;
if let Some(abi) = abi {
for event in abi.events() {
if let Ok(decoded_event) = event.decode_log(log, false) {
samples.extend(decoded_event.indexed);
samples.extend(decoded_event.body);
log_decoded = true;
break;
}
}
}

Expand Down Expand Up @@ -252,6 +258,7 @@ impl FuzzDictionary {

/// Insert sample values that are reused across multiple runs.
/// The number of samples is limited to invariant run depth.
/// If collected samples limit is reached then values are inserted as regular values.
pub fn insert_sample_values(&mut self, sample_values: Vec<DynSolValue>, limit: u32) {
for sample in sample_values {
let sample_type = sample.as_type().unwrap();
Expand Down

0 comments on commit 481971a

Please sign in to comment.