-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
feat(fuzz): ability to declare fuzz test fixtures #7428
Changes from 7 commits
ef15bab
3c48666
e1f8a95
254017c
8586bf4
14c869a
d0442de
7e4b14c
972d993
708e4db
f93254a
9276893
a5e8da0
14273fa
09ee66e
342eaf7
4785f93
0b7a230
f9adb66
fb86084
6874ead
ca4d44b
acdf92d
12115fa
e76fe8e
3ac9e33
7e95208
4cf19cb
53c64d3
997c5d4
a127780
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 |
---|---|---|
|
@@ -14,7 +14,7 @@ use foundry_evm_coverage::HitMaps; | |
use foundry_evm_traces::CallTraceArena; | ||
use itertools::Itertools; | ||
use serde::{Deserialize, Serialize}; | ||
use std::{collections::HashMap, fmt}; | ||
use std::{collections::HashMap, fmt, sync::Arc}; | ||
|
||
pub use proptest::test_runner::{Config as FuzzConfig, Reason}; | ||
|
||
|
@@ -272,3 +272,27 @@ impl FuzzedCases { | |
self.lowest().map(|c| c.gas).unwrap_or_default() | ||
} | ||
} | ||
|
||
/// Fixtures to be used for fuzz tests. | ||
/// The key represents name of the fuzzed parameter, value holds possible fuzzed values. | ||
/// For example, for a fixture function declared as | ||
/// `function fixture_sender() external returns (address[] memory senders)` | ||
/// the fuzz fixtures will contain `sender` key with `senders` array as value | ||
#[derive(Clone, Default, Debug)] | ||
pub struct FuzzFixtures { | ||
inner: Arc<HashMap<String, DynSolValue>>, | ||
} | ||
|
||
impl FuzzFixtures { | ||
pub fn new(fixtures: HashMap<String, DynSolValue>) -> FuzzFixtures { | ||
Self { inner: Arc::new(fixtures) } | ||
} | ||
|
||
pub fn param_fixtures(&self, param_name: &String) -> Option<&[DynSolValue]> { | ||
if let Some(param_fixtures) = self.inner.get(param_name) { | ||
param_fixtures.as_array() | ||
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 will now only parse dynamic arrays, let's support fixed-sized arrays as well 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. added with ca4d44b |
||
} else { | ||
None | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,53 @@ | ||||||||
use alloy_dyn_abi::DynSolValue; | ||||||||
use alloy_primitives::Address; | ||||||||
use proptest::{ | ||||||||
arbitrary::any, | ||||||||
prelude::{prop, BoxedStrategy}, | ||||||||
strategy::Strategy, | ||||||||
}; | ||||||||
|
||||||||
/// The address strategy combines 2 different strategies: | ||||||||
/// 1. A random addresses strategy if no fixture defined for current parameter. | ||||||||
/// 2. A fixture based strategy if configured values for current parameters. | ||||||||
/// If fixture is not a valid type then an error is raised and test suite will continue to execute | ||||||||
/// with random address. | ||||||||
/// | ||||||||
/// | ||||||||
/// For example: | ||||||||
/// To define fixture for `owner` fuzzed parameter, return an array of possible values from | ||||||||
/// `function fixture_owner() public returns (address[] memory)`. | ||||||||
/// Use `owner` named parameter in fuzzed test in order to create a custom strategy | ||||||||
/// `function testFuzz_ownerAddress(address owner, uint amount)`. | ||||||||
#[derive(Debug)] | ||||||||
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.
Suggested change
|
||||||||
pub struct AddressStrategy {} | ||||||||
|
||||||||
impl AddressStrategy { | ||||||||
/// Create a new address strategy. | ||||||||
pub fn init(fixtures: Option<&[DynSolValue]>) -> BoxedStrategy<DynSolValue> { | ||||||||
if let Some(fixtures) = fixtures { | ||||||||
let address_fixtures: Vec<DynSolValue> = | ||||||||
fixtures.iter().enumerate().map(|(_, value)| value.to_owned()).collect(); | ||||||||
let address_fixtures_len = address_fixtures.len(); | ||||||||
any::<prop::sample::Index>() | ||||||||
.prop_map(move |index| { | ||||||||
// Generate value tree from fixture. | ||||||||
// If fixture is not a valid address, raise error and generate random value. | ||||||||
let index = index.index(address_fixtures_len); | ||||||||
if let Some(addr_fixture) = address_fixtures.get(index) { | ||||||||
if let Some(addr_fixture) = addr_fixture.as_address() { | ||||||||
return DynSolValue::Address(addr_fixture); | ||||||||
} | ||||||||
} | ||||||||
error!( | ||||||||
"{:?} is not a valid address fixture, generate random value", | ||||||||
address_fixtures.get(index) | ||||||||
); | ||||||||
DynSolValue::Address(Address::random()) | ||||||||
}) | ||||||||
.boxed() | ||||||||
} else { | ||||||||
// If no config for addresses dictionary then create unbounded addresses strategy. | ||||||||
any::<Address>().prop_map(DynSolValue::Address).boxed() | ||||||||
} | ||||||||
} | ||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needs oneline doc