-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Description
Component
Forge
Have you ensured that all of these are up to date?
- Foundry
- Foundryup
What version of Foundry are you on?
forge Version: 0.3.1-dev Commit SHA: 28e6ff1
What version of Foundryup are you on?
No response
What command(s) is the bug in?
forge test
Operating System
Linux
Describe the bug
While generating an invariant strategy in fuzz_param_from_state, the values are generated from the dictionary state:
pub fn fuzz_param_from_state(
param: &DynSolType,
state: &EvmFuzzState,
) -> BoxedStrategy<DynSolValue> {
// Value strategy that uses the state.
let value = || {
let state = state.clone();
let param = param.clone();
// Generate a bias and use it to pick samples or non-persistent values (50 / 50).
// Use `Index` instead of `Selector` when selecting a value to avoid iterating over the
// entire dictionary.
any::<(bool, prop::sample::Index)>().prop_map(move |(bias, index)| {
let state = state.dictionary_read();
let values = if bias { state.samples(¶m) } else { None }
.unwrap_or_else(|| state.values())
.as_slice();
values[index.index(values.len())]
})
};In case of addresses, we are filter mapping them (correctly) if they are pre-deployed library addresses and we filter them with None:
// Convert the value based on the parameter type
match *param {
DynSolType::Address => {
let deployed_libs = state.deployed_libs.clone();
value()
.prop_filter_map("filter address fuzzed from state", move |value| {
let fuzzed_addr = Address::from_word(value);
// Do not use addresses of deployed libraries as fuzz input.
// See <https://github.com/foundry-rs/foundry/issues/8639>.
if !deployed_libs.contains(&fuzzed_addr) {
Some(DynSolValue::Address(fuzzed_addr))
} else {
None
}
})
.boxed()
}During invariant test execution, we update the dictionary state via collect_data - this has a side-effect of invalidating the rng state for the proptest case generator.
The case tree is generated when the run is invoked. The proptest library expects from here on that case.current() be deterministic - which is computed from the dictionary_state established above. However for tests like invariant_fork_handler_block where the tests are meant to fail, the proptest library tries to construct the TestError while using case.current().
This becomes problematic as the state has now been updated, so case.current() will always return a different input than what was used for the test. In an extreme case if the address just happens to be a pre-deployed library, the computation will yield a None and subsequently panic. Note that the values in a filter_map are only rejected when a new tree is constructed, but this call is actually the result of proptest trying to retrieve case.current() - which will return None and subsequently fail here
Solutions
- If possible we should avoid changing the rng state during test execution.
- Patch
proptestto no longer rely oncase.current()after test execution assuming it can be non-deterministic.
Happy to file a PR once we decide on a way forward.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status