-
Notifications
You must be signed in to change notification settings - Fork 2.4k
fix(evm): fuzzing not properly collecting data #2724
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cbb2475
95c1f10
b95b84e
96fad72
010c417
de9c728
96c9e76
7e62839
1e0a25a
ccd9d0c
94ec119
a3301e1
d379e2f
a370232
16e072f
fffc554
a952650
6648e8c
236709e
5db4bd4
f942121
7035c3a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ use ethers::{ | |
| }; | ||
| use eyre::ContextCompat; | ||
| use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; | ||
| use hashbrown::HashMap; | ||
| use parking_lot::{Mutex, RwLock}; | ||
| use proptest::{ | ||
| strategy::{BoxedStrategy, Strategy, ValueTree}, | ||
|
|
@@ -142,14 +143,10 @@ impl<'a> InvariantExecutor<'a> { | |
| .expect("could not make raw evm call"); | ||
|
|
||
| // Collect data for fuzzing from the state changeset. | ||
| let state_changeset = | ||
| let mut state_changeset = | ||
| call_result.state_changeset.to_owned().expect("to have a state changeset."); | ||
|
|
||
| collect_state_from_call( | ||
| &call_result.logs, | ||
| &state_changeset, | ||
| fuzz_state.clone(), | ||
| ); | ||
| collect_data(&mut state_changeset, sender, &call_result, fuzz_state.clone()); | ||
|
|
||
| if let Err(error) = collect_created_contracts( | ||
| &state_changeset, | ||
|
|
@@ -206,6 +203,8 @@ impl<'a> InvariantExecutor<'a> { | |
| }); | ||
| } | ||
|
|
||
| tracing::trace!(target: "forge::test::invariant::dictionary", "{:?}", fuzz_state.read().iter().map(hex::encode).collect::<Vec<_>>()); | ||
|
|
||
| let (reverts, invariants) = failures.into_inner().into_inner(); | ||
|
|
||
| Ok(Some(InvariantFuzzTestResult { invariants, cases: fuzz_cases.into_inner(), reverts })) | ||
|
|
@@ -479,6 +478,36 @@ impl<'a> InvariantExecutor<'a> { | |
| } | ||
| } | ||
|
|
||
| /// Collects data from call for fuzzing. However, it first verifies that the sender is not an EOA | ||
| /// before inserting it into the dictionary. Otherwise, we flood the dictionary with | ||
| /// randomly generated addresses. | ||
| fn collect_data( | ||
| state_changeset: &mut HashMap<Address, revm::Account>, | ||
| sender: &Address, | ||
| call_result: &RawCallResult, | ||
| fuzz_state: EvmFuzzState, | ||
| ) { | ||
| // Verify it has no code. | ||
| let mut has_code = false; | ||
| if let Some(Some(code)) = state_changeset.get(sender).map(|account| account.info.code.as_ref()) | ||
| { | ||
| has_code = !code.is_empty(); | ||
| } | ||
|
|
||
| // We keep the nonce changes to apply later. | ||
| let mut sender_changeset = None; | ||
| if !has_code { | ||
| sender_changeset = state_changeset.remove(sender); | ||
| } | ||
|
Comment on lines
+491
to
+501
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we want to do this? what if there's e.g. a smart contract wallet that would make a call and hit e.g. a
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code only prevents adding this address to the dictionary through the |
||
|
|
||
| collect_state_from_call(&call_result.logs, &*state_changeset, fuzz_state); | ||
|
|
||
| // Re-add changes | ||
| if let Some(changed) = sender_changeset { | ||
| state_changeset.insert(*sender, changed); | ||
| } | ||
| } | ||
|
|
||
| /// Verifies that the invariant run execution can continue. | ||
| fn can_continue( | ||
| invariant_contract: &InvariantContract, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,8 @@ use proptest::prelude::*; | |
| pub use proptest::test_runner::Config as FuzzConfig; | ||
| use std::sync::Arc; | ||
|
|
||
| use super::fuzz_param_from_state; | ||
|
|
||
| /// Given a target address, we generate random calldata. | ||
| pub fn override_call_strat( | ||
| fuzz_state: EvmFuzzState, | ||
|
|
@@ -75,20 +77,36 @@ fn generate_call( | |
| let senders = senders.clone(); | ||
| let fuzz_state = fuzz_state.clone(); | ||
| func.prop_flat_map(move |func| { | ||
| let sender = select_random_sender(senders.clone()); | ||
| let sender = select_random_sender(fuzz_state.clone(), senders.clone()); | ||
gakonst marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| (sender, fuzz_contract_with_calldata(fuzz_state.clone(), contract, func)) | ||
| }) | ||
| }) | ||
| .boxed() | ||
| } | ||
|
|
||
| /// Strategy to select a sender address: | ||
| /// * If `senders` is empty, then it's a completely random address. | ||
| /// * If `senders` is empty, then it's either a random address (10%) or from the dictionary (90%). | ||
| /// * If `senders` is not empty, then there's an 80% chance that one from the list is selected. The | ||
| /// remaining 20% will be random. | ||
| fn select_random_sender(senders: Vec<Address>) -> impl Strategy<Value = Address> { | ||
| let fuzz_strategy = | ||
| fuzz_param(&ParamType::Address).prop_map(move |addr| addr.into_address().unwrap()).boxed(); | ||
| /// remaining 20% will either be a random address (10%) or from the dictionary (90%). | ||
| fn select_random_sender( | ||
| fuzz_state: EvmFuzzState, | ||
| senders: Vec<Address>, | ||
| ) -> impl Strategy<Value = Address> { | ||
| let fuzz_strategy = proptest::strategy::Union::new_weighted(vec![ | ||
| ( | ||
| 10, | ||
| fuzz_param(&ParamType::Address) | ||
| .prop_map(move |addr| addr.into_address().unwrap()) | ||
| .boxed(), | ||
| ), | ||
| ( | ||
| 90, | ||
| fuzz_param_from_state(&ParamType::Address, fuzz_state) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like weights are 90/10 but the comment above this method says 80/20. Probably should be exposed as a config option at some point (not necessarily in this PR) |
||
| .prop_map(move |addr| addr.into_address().unwrap()) | ||
| .boxed(), | ||
| ), | ||
| ]) | ||
| .boxed(); | ||
|
|
||
| if !senders.is_empty() { | ||
| let selector = | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.