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! |
Evalir
left a comment
There was a problem hiding this comment.
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.
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.
|
|
||
| /// 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.
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.
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.
Match depending of mode:
Count:countMust be strictlyactual_count, if not we should fail.NonCount:actual_countmust be at least equal tocount(therefore, if we see thatcountis 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_countAmount 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. |
| @@ -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.
can we please add a few docs here what this checks exactly checks and when this is supposed to return a revert
|
|
||
| /// 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.
this signature is a bit complex
I'm fine with making a wrapper type for this so it can be properly documented
|
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 💯 |
mattsse
left a comment
There was a problem hiding this comment.
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
expectCallcheatcode, with and without count.Behavior
expectCallwill be invoked in two different modes depending on how the cheatcode was used:NonCount. The behavior of this mode is:expectCall(...)Ntimes, will set a lower bound of expected calls ofN, but will not revert if more thanNcalls are matched. This is the legacyexpectCallbehavior.CountexpectCallcheatcode call.Example of
NonCountadditive behavior:Example of how it cannot be overwritten:
Count. The behavior of this mode is:Nbeing the amount of calls to match specified.expectCall(..., N)Mtimes will fail. It can only be called once if it has a count, even if it is 0.expectCallcheatcode call, with or without count.This essentially means that for any different calldata, there is only one
expectCallcheatcode "mode" active. It is eitherNonCountand additive, orCountand 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.