-
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(evm): add shrinking to invariant testing #2745
Conversation
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.
sounds reasonable,
wdyt @onbjerg?
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.
should we add a unit test which ensures that the number of calls actually is shrinked?
evm/src/fuzz/invariant/error.rs
Outdated
// Checks the invariant. | ||
if let Some(func) = &self.func { |
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.
Nit - can pull this outside of the loop and return false
early, before iterating?
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.
Slightly modified it, but the goal here is to check that the invariant has, in fact, been broken after a call.
So pulling this outside of the loop is not what we want
evm/src/fuzz/invariant/error.rs
Outdated
} | ||
Some(element) | ||
}) | ||
.collect::<Vec<_>>(); |
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.
nit: you don't need to collect here given you'll iterate over this immutably again in fails_successfully
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.
the calls for invariant tests should be a proptest ValueTree
/Strategy
. the shrinking logic is in ValueTree::complicate
(more calls?) and ValueTree::simplify
. i think looking at how the value trees for a Vec<T>
works would suffice. generally this looks ok but I'd prefer we use the "standard" approaches if possible so it's easier to migrate later/maintain as opposed to it being a totally separate thing
proptest will already rerun the failing case and call complicate
/simplify
to try and shrink the example, so a lot of this logic essentially duplicates that behavior
We are currently using Might be my inexperience with the library, but at the time of the invariant implementation I hit some limitations, which lead me to go around its standard use. Example, for a run of depth 15, I want to be able to generate 15 values lazily (as we fill the dictionary with more in-context values), instead of getting all 15 values straightaway. Which would disregard any newly generated contracts, for example. Context: foundry/evm/src/fuzz/invariant/executor.rs Lines 107 to 110 in fb9bc90
from inside foundry/evm/src/fuzz/invariant/executor.rs Lines 185 to 193 in fb9bc90
This however, made it impossible to use the shrink functionality, since |
@joshieDo That's not entirely correct - proptest entirely discards the |
Talked async w @joshieDo, I don't have enough of an overview of how the primary strategy works for us to meaningfully progress on possibly using proptest for the shrinking, so you can ignore my comments on the PR - we can look at it later |
* add shrinking * collect modified sequence once * add test for shrinking
Motivation
Traces from invariant failures can really be long. Especially with a high
invariant_depth
value. This aims to reduce the sequence to its minimum representation.Solution
Add some basic shrinking:
Is there a need for it to be optional?