-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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: identify internal function invocations in traces #8222
Conversation
3b2b1fe
to
9fd779a
Compare
Adds `Step` variant for `LogCallOrder` enum and renames it to `TraceMemberOrder`. This is useful for printing logic which relies on execution steps as well, e.g. foundry-rs/foundry#8222
5f643a4
to
216e9da
Compare
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.
only briefly skimmed parts of it.
I think this makes sense, I'd appreciate a few more docs, and I'll take a closer look
if self.tracer.is_none() && yes || | ||
!self.tracer.as_ref().map_or(false, |t| t.config().record_steps) && debug |
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 a bit hard to follow,
I wonder if we can encapsulate these two bools into an enum TracingKid
or smth, because debug also implies tracing, right?
- Adds `record_returndata_snapshots` flag to config which enables snapshots of `interpreter.return_data_buffer` - Adds `record_opcodes_filter` parameter which allows to only record specific opcodes. ref foundry-rs/foundry#8222 (comment) - Adds `gas_used` field for `CallTraceStep` This should be enough to migrate foundry's debugger to using `TracingInspector` from here, I will open PR for this later today.
Current approach relies on solc source map keys of jump type. For JUMPs we are sometimes provided with info on whether this is a jump in or out of the function, and by reading source code you can determine the name, location, input and output types of the function. However, with optimizations those source maps are getting messed up and you are getting a lot of mismatched ins and outs. Currently I've been mostly focusing on correctness of the identification, thus we currently only identify a match if we see an explicit JUMP in and JUMP out of the same function. This currently doesn't really work for REVERTs and RETURNs done in low-level assembly, because there are no JUMPs out, just a frame execution end. We're still ending up with a stack of potentially correct internal fns in those cases, but when I tested this for some random cases this stack usually contained some invalid data which I wouldn't want to display. So currently there is definitely space for improvement of identification, likely through more "guessing"-approach relying on multiple factors and guided by AST, source maps and bytecode analysis.
While I was reading source maps I've seen that solc marks JUMPs into
IMO all of this is basically a better/more readable UX for the debugger, which can already be used to check the exact line of code where the revert occured |
ref foundry-rs/foundry#8222 ref foundry-rs/foundry#8198 Adds structs and extends `TraceWriter` to support formatting of decoded trace steps. Currently two decoding formats are supported: - Internal calls. Similar to a decoded call trace, decoded internal function invocation which spans over multiple steps. Kept as decoded function name, inputs, outputs and index of the last step. - Arbitrary strings. This might be useful for formatting decoded opcodes (e.g. adding `├─ [sload] <slot>` to trace. It might make sense to extend it to something more configurable once we start implementing 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.
lgtm
pending @DaniPopes
Updated |
Yeah same problem as in the debugger caused by memory snapshots |
Maybe we can disable memory decoding by default to avoid the memory consumption issue? |
Yeah, I though about disabling memory tracking if more than one test matched filters. Though not sure how to make this intuitive Should it be two separate flags, one of which does not require the test function filter? |
Updated
|
Hey @klkvr, great work on this PR! Just wanted to follow up on your comment about source maps getting messed up by the optimization step: do you mean that they're actually broken and it'd be useful to open an issue in solc or just that optimization fundamentally obfuscates the origin of an opcode as they could be reduced to fewer operations? |
@Philogy it's hard to tell, I've definitely seen situations in which source maps would point to completely unrelated chunck of code for some of the instructions after optimizations. However, I wouldn't be surprised if this has a reasonable explanation related to how inlining works internally. Sourcemaps are documented very briefly so it's hard to tell how they should behave and what we should consider a bug, and whether we can trust them after certain number of optimizations at all |
Motivation
Introduces
--decode-internal
flag forforge test
,cast run
andcast call --trace
which enables decoding of internal functions in tracesExample
Example trace of random Uniswap V3 swap:
![image](https://private-user-images.githubusercontent.com/62447812/341601362-12188043-fc72-44f7-a08c-5c1cd47ff6da.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjE1MTMxODQsIm5iZiI6MTcyMTUxMjg4NCwicGF0aCI6Ii82MjQ0NzgxMi8zNDE2MDEzNjItMTIxODgwNDMtZmM3Mi00NGY3LWEwOGMtNWMxY2Q0N2ZmNmRhLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA3MjAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwNzIwVDIyMDEyNFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTVmZGZjNTZlNGFlODE4NDhiMDg5YWQyYWUwNWJiNjliMzE1OTFhZDNjMWYyYzYzNTYxMjFmNGFkN2ZkMTdiYjgmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.8zAZ6XSpdn0MGF7W3TU5E9YYjNm9l4K6ro1Ch4qTSok)
Solution
To determine when we are jumping in/out of functions we are using source map
Jump
key. However, it is not really reliable, especially after optimizations. Almost in all cases there are mismatches between number of "in"s and "out"s, so we need additional processing to correctly display subset of functions which are correctly reported.Main implementation of this tracing is in
DebugTraceIdentifier
: https://github.com/foundry-rs/foundry/blob/216e9da8a28fcc57bcba1c6c4986aa5353472cc5/crates/evm/traces/src/debug/mod.rsThe only issue with this approach is that we are losing data about entire stack of internal functions which were joined before revert
I've used default tracer from revm-inspectors instead of traces collected by
Debugger
to allow easier integration into printing logic. Using it required a small patch to inspectors: paradigmxyz/revm-inspectors#150This approach is enough to implement flamegraphs in a similar way, and can probably be extended to smarter tracking of stack/memory/calldata to also resolve input and output parameters of internal functions
Printing logic is a bit ugly at the moment
Closes: #3999 + Closes: #4351