-
Notifications
You must be signed in to change notification settings - Fork 2k
chore(forge): revert diagnostic inspector #10446
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
base: master
Are you sure you want to change the base?
chore(forge): revert diagnostic inspector #10446
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.
IMO on the right track, @klkvr pls share your thoughts re the approach.
left some minor comments / nits.
To note that such default option could add perf penalty for invariant tests running with fail_on_revert = false
but probably bearable.
} | ||
|
||
impl RevertDiagnostic { | ||
fn is_delegatecall(&self, scheme: CallScheme) -> bool { |
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.
these can be all helper functions, not RevertDiagnostic specific
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.
would u keep them in the inspector file? or should i place them somewhere else (like utils or similar)?
return None; | ||
} | ||
|
||
let reason = match self.non_contract_call { |
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 use self.non_contract_call.map
@@ -61,6 +61,8 @@ pub struct InspectorStackBuilder { | |||
pub wallets: Option<Wallets>, | |||
/// The CREATE2 deployer address. | |||
pub create2_deployer: Address, | |||
/// Whether to provide diagnostics for EVM reverts. | |||
pub revert_diag: Option<bool>, |
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.
it makes sense to always provide such, wdyt?
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.
just wanted to keep the existing pattern, but i'd do the same 👍
|
||
Failing tests: | ||
Encountered 1 failing test in test/NonContractCallRevertTest.t.sol:NonContractCallRevertTest | ||
[FAIL: EvmError: call to non-contract address `0xdEADBEeF00000000000000000000000000000000`] test_non_contract_call_failure() ([GAS]) |
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.
nice!
outcome: CallOutcome, | ||
) -> CallOutcome { | ||
if outcome.result.result == InstructionResult::Revert { | ||
self.reverted = true |
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.
maybe here we could directly set the reason? we can also do more checks if target address is actually a contract, (like checking if selector available, etc. some other similar hh checks https://github.com/NomicFoundation/hardhat/blob/67f1e95e1f3904f7b2e8a5560115c1551e899f64/packages/hardhat-core/src/internal/hardhat-network/stack-traces/solidity-errors.ts#L218-L341)
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 thought that storing a bool could be more efficient + fn reason()
would allow us to isolate the logic, but totally fine for me to do it directly
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 think we need some smarter strategy here to ensure that call we've caught in call
hook is indeed the one that caused revert (e.g compare depth, ensure there were no calls after it)
crates/forge/tests/cli/test_cmd.rs
Outdated
interface ICounter { | ||
function number() external returns (uint256); | ||
} |
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.
It seems that in cases of calling functions returning data Solidity's approach is to revert if data is empty.
However, if method does not return anything (e.g like increment()
) then Solidity would firstly check before the call whether the address has any code, and revert if it's not which I believe is not being caught by our inspector rn
addresses the PR feedback by checking the call stack depth + tracking EXTCODESIZE calls to empty addresses (confirmed that the initial impl wouldn't diagnose those). the only downside is extra performance overhead, but i tried to make the |
i also enhanced the trace decoder to identify function calls for a selector that is not supported by the called contract (if the tracer has access to its abi) |
if EXTCODESIZE == interpreter.current_opcode() { | ||
if let Ok(word) = interpreter.stack().peek(0) { | ||
let addr = Address::from_word(word.into()); | ||
if let Ok(state) = ctx.code(addr) { |
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 warms the account (load_account
) hence the gas changes in tests, we should probably just record these addresses and their depth and check for the code in call_end, if call reverted
PoC for a revert diagnostic inspector that would tackle:
the idea is that this inspector could be enhanced to track other contextual information, and provide support for other "undiagnosed" reverts. For the time being though, it has been kept quite minimal (just to target the linked issue).
design
created a new inspector:
and a new enum:
which provide extra content to the call traces, so that
RevertDecoder
can provide extra context when it would otherwise reportEvmError: Revert
.output
these would be the new error messages that users would see:
despite the nature of these error messages is of last-resort, maybe we can't know for certain that they were the root cause, as the calls themselves don't fail.
an alternative approach would be to add them as call ctx regardless of the test outcome, so that users are aware that their tests had a call (with calldata) to a non-contract address.