-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
feature(invariant) - persist and replay failure #7899
feature(invariant) - persist and replay failure #7899
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.
really love the tests!
only have one pedantic nit
crates/config/src/lib.rs
Outdated
macro_rules! remove_test_cache { | ||
($cache_dir:expr) => { | ||
if let Some(test_cache) = $cache_dir { | ||
let path = project.root().join(test_cache); | ||
if path.exists() { | ||
std::fs::remove_dir_all(&path).map_err(|e| SolcIoError::new(e, path))?; | ||
} | ||
} | ||
}; | ||
} |
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.
can we convert this into either a private function or closure instead?
not a fan of using private macros for this
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.
changed in 9b22080 I opted not to throw the SolcIoError anymore (as is not really solc related) and just ignore, can readd it if you think it should be there
crates/forge/src/runner.rs
Outdated
if let Ok(data) = fs::read_to_string(failure_file) { | ||
if let Ok(call_sequence) = serde_json::from_str::<Vec<BaseCounterExample>>(&data) { |
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.
I believe there's a foundry common fs function for reading json, perhaps we can use this here and get rid of one scope?
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.
oh, yes, indeed, I used them for both read and write in 9b22080
- replace test cache rm macro with closure - use commons for load / persist failure sequence
This is great!
In this case, are we only running the first 50 steps of the sequence which results in it being passed? I think we may still want the full 100 step sequence to run to avoid thinking my test is now passing when it reality it doesn't
Two small comments on this path:
|
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.
LGTM
I think there can be potential issues with fail_on_revert
set to true. If user changes setUp routines, then persisted sequence might become invalid because contract addresses will change and cached calldata might be invalid for a given address.
For example, if persisted sequence is
- call
foo()
on contractA
- call
bar()
on contractB
then adding more deployments to setUp will cause addresses of contract A
/B
change due to a deployer nonce shift, and sequence above might start calling bar()
on A
or vice versa.
Not sure how we can address that, perhaps display some kind of warning when persisted sequence is reverting rather than failing an invariant?
Yes, that's correct, there'll only be first 50 runs resulting in test pass. That was done to avoid situations like the one described here crytic/echidna#1231 where changes in configs are not applied on failure reply. Happy to change it if you think should be.
Yeah, I meant test suite (that is the test contract)
Rn we don't support params in invariant functions, is something that was requested in #4834 but no resolution for it yet |
Thanks, I left a comment there to better understand
Oh right, forgot about that 😅 |
good point, a simpler scenario would be renaming handler functions, like |
…erts before checking invariant
let (sender, (addr, bytes)) = &calls[call_index]; | ||
let call_result = | ||
executor.call_raw_committing(*sender, *addr, bytes.clone(), U256::ZERO)?; | ||
if call_result.reverted && failed_case.fail_on_revert { | ||
if call_result.reverted && fail_on_revert { | ||
// Candidate sequence fails test. | ||
// We don't have to apply remaining calls to check sequence. | ||
sequence_failed = true; | ||
break; |
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.
can't we just return here instead of managing sequence_failed
flag and counting calls for replayed_entirely
?
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.
yep yep, nice cleanup! 7686c03
)) | ||
} else { | ||
Some(format!( | ||
"{} persisted failure revert", |
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.
yep, that should explain what actually happened
@mds1 I think I am on the same page as Rappie mentioned in crytic/echidna#1231 (comment) |
From rappie in that issue:
That does make sense to me, I'm onboard 👌 |
Motivation
Partially address #2552 - persist and replay shrinked failed invariant sequence CC @mds1
PROJ_ROOT/cache/invariant/failures/{TEST_SUITE_NAME}/{INVARIANT_NAME}
, layout ofcache
dir becomingfoundry.toml
or by using inline config
Solution
cache/invariant
forge clean
forge clean