-
Notifications
You must be signed in to change notification settings - Fork 2k
cast: Improve debugger when tracing on-chain transactions/calls #10596
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?
Conversation
If anyone had time to look at these, if any changes are needed. etc. 🙏 |
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.
sorry about the delay.
all of these look good, but would appreciate another look from @grandizzy @zerosnacks @DaniPopes
left some suggestions for followups
crates/cast/src/cmd/run.rs
Outdated
fn gather_trace_addresses(traces: &Traces) -> Vec<Address> { | ||
let mut addresses = HashSet::new(); | ||
for (_, trace) in traces { | ||
for node in trace.arena.nodes() { | ||
if !node.trace.address.is_zero() { | ||
addresses.insert(node.trace.address); | ||
} | ||
if !node.trace.caller.is_zero() { | ||
addresses.insert(node.trace.caller); | ||
} | ||
} | ||
} | ||
addresses.into_iter().collect() | ||
} |
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.
@zerosnacks imo we could even upstream this as a helper to revm-inspectors
but for now this seems fine here
crates/cast/src/cmd/run.rs
Outdated
for address in addresses { | ||
let code = provider.get_code_at(address).await?; | ||
if !code.is_empty() { | ||
contracts_bytecode.insert(address, code); | ||
} | ||
} |
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 fetches codes code sequentially, we could fetch concurrently
also, should this fail or should we simply skip errors here?
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 want to join_all, and add to the map if ok, otherwise warn if err
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 for join_all
, as well as warn on error (I think I did it correctly?)
crates/common/src/utils.rs
Outdated
/// Strips all __$xxx$__ placeholders from the bytecode if it's an unlinked bytecode. | ||
/// by replacing them with 20 zero bytes. | ||
/// This is useful for matching bytecodes to a contract source, and for the source map, | ||
/// in which the actual address of the placeholder isn't important. | ||
pub fn strip_bytecode_placeholders(bytecode: &BytecodeObject) -> Option<Bytes> { | ||
match &bytecode { | ||
BytecodeObject::Bytecode(bytes) => Some(bytes.clone()), | ||
BytecodeObject::Unlinked(s) => { | ||
// Replace all __$xxx$__ placeholders with 32 zero bytes | ||
let re = Regex::new(r"__\$.{34}\$__").expect("invalid regex"); | ||
let s = re.replace_all(s, "00".repeat(40)); | ||
let bytes = hex::decode(s.as_bytes()); | ||
Some(bytes.ok()?.into()) | ||
} | ||
} | ||
} |
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.
also something we could upstream to compilers @zerosnacks
crates/common/src/utils.rs
Outdated
BytecodeObject::Bytecode(bytes) => Some(bytes.clone()), | ||
BytecodeObject::Unlinked(s) => { | ||
// Replace all __$xxx$__ placeholders with 32 zero bytes | ||
let re = Regex::new(r"__\$.{34}\$__").expect("invalid regex"); |
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 we can keep this for this pr as is but when we move this, we should make this a lazy static
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
Thanks @mattsse for the review!! 🙏 Waiting on other inputs for #10596 (comment) then :) |
crates/cast/src/cmd/run.rs
Outdated
Ok(contracts_bytecode) | ||
} | ||
|
||
fn gather_trace_addresses(traces: &Traces) -> Vec<Address> { |
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 just return the hash set 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.
Done
crates/cast/src/cmd/run.rs
Outdated
pub async fn fetch_contracts_bytecode_from_trace( | ||
provider: &RootProvider<AnyNetwork>, | ||
result: &TraceResult, | ||
) -> Result<HashMap<Address, Bytes, RandomState>> { |
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 RandomState is unnecessary at all uses
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
(had an issue w/ pushing my changes 😓 I'll update this PR later tonight) |
1c8998e
to
2c806ee
Compare
Without fetching the bytecodes from the current chain, matching the contracts with `--with-local-artifacts` option only works if the matching contracts have been deployed in the trace. This is very limiting when trying to `--debug` an on-chain transaction. By fetching the contracts' bytecodes, we can increase the matching of address to source file, by thus providing the runtime bytecode.
If a contract contains some libraries, and thus has an "unlinked" bytecode object, it will never be matched against a deployed instance, and the source map will never be set. This fixes this issue by striping from the unlinked bytecode all placeholders, replacing them with the `0x00..00` address. It doesn't change anything regarding source-maps, but could change the matching of the runtime bytecode. The changes are usually minimal in this case, though.
2c806ee
to
52e3579
Compare
Ready for re-review :) |
@@ -43,6 +43,8 @@ pub struct TraceIdentifiers<'a> { | |||
pub local: Option<LocalTraceIdentifier<'a>>, | |||
/// The optional Etherscan trace identifier. | |||
pub etherscan: Option<EtherscanIdentifier>, | |||
/// The contracts bytecode. | |||
pub contracts_bytecode: Option<&'a HashMap<Address, Bytes>>, |
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 unused
Motivation
The current debugger provided in
cast
is great, but it currently cannot show the source file of local contracts incast run
(for example).It seems that it can only match contracts to source code, whether from Etherscan (but this requires both an API key, and a verified contract), or locally via
--with-local-artifacts
. However, w/--with-local-artifacts
, it only matches the contracts for which the deployment is within the same transaction, to get both the deployment bytecode, as well as the runtime one.Solution
A simple solution to this problem (first commit), is to actually fetch the bytecode of all addresses involved in a trace, to pass it along to the local identifier. This is done for both
cast run
(which I guess already has all bytecodes available, since it re-executes the tx), as well ascast call
, for which I'm unsure if it's a good default, or should it be behind a flag?Another issue arose, for contracts using libraries. For those, the bytecode isn't convertible to
bytes
, as it has placeholders. A simple solution is to replace all those placeholders w/ the0x00.00
address, which should still yield some good matching score. It's also used in the sourcemap generation, in which the actual value of the library's address isn't needed anyway.Reproduction
Clone the morpho-blue repo: https://github.com/morpho-org/morpho-blue and
cast run --with-local-artifacts -r "<NODE_URL>" --debug 0x32fbce9a55b5b13d65fc5a359d40d742812d409df6c4a7483db3a18f0b8e3d22
and go through the calls until the first call to
0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb
The tx https://etherscan.io/tx/0x32fbce9a55b5b13d65fc5a359d40d742812d409df6c4a7483db3a18f0b8e3d22 calls the Morpho Blue contract in the 5th call, which shows no matching contract on
master
, but properly shows the contract source code on this branch.Same with
PR Checklist
I haven't added any tests yet, as I wanted to hear from maintainers first on the PR's logic/structure.