-
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
fix(cheatcodes): Fix expectCall
behavior
#4912
Conversation
cc @PaulRBerg — you might be interested in nitpicking this to make sure we are happy with how this is implemented now! |
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.
Some helpful comments for reviewing (I will add more docs to the code nevertheless)
My hope is that we can use this same structure & adapt it for the other expect*
cheatcodes with count.
for (address, expecteds) in &self.expected_calls { | ||
for (expected, actual_count) in expecteds { | ||
let ExpectedCallData { calldata, gas, min_gas, value, count } = expected; | ||
for (address, calldatas) in &self.expected_calls { |
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.
As a note-to-self, this and all other "handlers" on the different hooks should probably be abstracted into their own function and properly documented for easy modification later.
@@ -126,7 +126,7 @@ pub struct Cheatcodes { | |||
pub mocked_calls: BTreeMap<Address, BTreeMap<MockCallDataContext, MockCallReturnData>>, | |||
|
|||
/// Expected calls | |||
pub expected_calls: BTreeMap<Address, Vec<(ExpectedCallData, u64)>>, | |||
pub expected_calls: BTreeMap<Address, BTreeMap<Bytes, (ExpectedCallData, u64)>>, |
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.
This is now storing this information
BTreeMap<Address, // -> Target
BTreeMap<Bytes, // -> Calldata. Selectors are NOT deduped, so partial matches and full matches can be applied.
(ExpectedCallData, // -> The expected call data to match. Note that the calldata is now held as the key instead of inside this struct.
u64) // -> The actual amount of times this call was seen
>
>
}) { | ||
*count += 1; | ||
{ | ||
*actual_count += 1; |
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.
For any of the two modes, the discrimination of how to interpret the actual count is done later—our only job here is detecting if all values properly match for the current call.
InstructionResult::Revert, | ||
remaining_gas, | ||
format!("Expected at least one call to {address:?} with {expected_values}, but got none") | ||
match call_type { |
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.
Match depending of mode:
Count
:count
Must be strictlyactual_count
, if not we should fail.NonCount
:actual_count
must be at least equal tocount
(therefore, if we see thatcount
is bigger thanactual_count
, we should fail as we didn't see enough calls).
For reference:
count
: Amount of times a call must be seen.actual_count
Amount of times a call was actually seen.
Hey @Evalir, thanks very much for this PR, as well as the helpful clarifications. The proposal looks great; it is a good idea to have two separate modes for
By "cannot", do you mean Foundry will revert, or will it silently ignore the attempt to override the calldata's expect? |
Hey @PaulRBerg — Foundry will 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.
@@ -220,6 +230,54 @@ fn expect_safe_memory(state: &mut Cheatcodes, start: u64, end: u64, depth: u64) | |||
Ok(Bytes::new()) | |||
} | |||
|
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 please add a few docs here what this checks exactly checks and when this is supposed to return a 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.
done in ce54d4b
@@ -126,7 +126,7 @@ pub struct Cheatcodes { | |||
pub mocked_calls: BTreeMap<Address, BTreeMap<MockCallDataContext, MockCallReturnData>>, | |||
|
|||
/// Expected calls | |||
pub expected_calls: BTreeMap<Address, Vec<(ExpectedCallData, u64)>>, | |||
pub expected_calls: BTreeMap<Address, BTreeMap<Vec<u8>, (ExpectedCallData, u64)>>, |
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.
this signature is a bit complex
I'm fine with making a wrapper type for this so it can be properly documented
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.
done in ce54d4b
This is great, thanks a lot @Evalir! Let's also get the book updated with the info from the PR description which is really thorough and helpful 💯 |
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, holding off on book changes so we have these before we publish.
This is a breaking change.
Closes #4879.
Implements the intended behavior for the
expectCall
cheatcode, with and without count.Behavior
expectCall
will be invoked in two different modes depending on how the cheatcode was used:NonCount
. The behavior of this mode is:expectCall(...)
N
times, will set a lower bound of expected calls ofN
, but will not revert if more thanN
calls are matched. This is the legacyexpectCall
behavior.Count
expectCall
cheatcode call.Example of
NonCount
additive behavior:Example of how it cannot be overwritten:
Count
. The behavior of this mode is:N
being the amount of calls to match specified.expectCall(..., N)
M
times will fail. It can only be called once if it has a count, even if it is 0.expectCall
cheatcode call, with or without count.This essentially means that for any different calldata, there is only one
expectCall
cheatcode "mode" active. It is eitherNonCount
and additive, orCount
and non-additive. Note that a partial match and a full match is considered different calldata, so you could additively match partial-matches and strictly match full-matches. Tests have been added for each of these cases.