Skip to content

Commit 7245b62

Browse files
committed
cast: Fetch bytecodes in run/call to better match contracts
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.
1 parent 4a7f84b commit 7245b62

File tree

5 files changed

+92
-10
lines changed

5 files changed

+92
-10
lines changed

crates/cast/src/cmd/call.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ use regex::Regex;
3434
use revm::context::TransactionType;
3535
use std::{str::FromStr, sync::LazyLock};
3636

37+
use super::run::fetch_contracts_bytecode_from_trace;
38+
3739
// matches override pattern <address>:<slot>:<value>
3840
// e.g. 0x123:0x1:0x1234
3941
static OVERRIDE_PATTERN: LazyLock<Regex> =
@@ -292,10 +294,12 @@ impl CallArgs {
292294
),
293295
};
294296

297+
let contracts_bytecode = fetch_contracts_bytecode_from_trace(&provider, &trace).await?;
295298
handle_traces(
296299
trace,
297300
&config,
298301
chain,
302+
&contracts_bytecode,
299303
labels,
300304
with_local_artifacts,
301305
debug,

crates/cast/src/cmd/run.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
use std::hash::RandomState;
2+
13
use alloy_consensus::Transaction;
24
use alloy_network::{AnyNetwork, TransactionResponse};
3-
use alloy_provider::Provider;
5+
use alloy_primitives::{
6+
map::{HashMap, HashSet},
7+
Address, Bytes,
8+
};
9+
use alloy_provider::{Provider, RootProvider};
410
use alloy_rpc_types::BlockTransactions;
511
use clap::Parser;
612
use eyre::{Result, WrapErr};
@@ -21,7 +27,7 @@ use foundry_config::{
2127
use foundry_evm::{
2228
executors::{EvmError, TracingExecutor},
2329
opts::EvmOpts,
24-
traces::{InternalTraceMode, TraceMode},
30+
traces::{InternalTraceMode, TraceMode, Traces},
2531
utils::configure_tx_env,
2632
Env,
2733
};
@@ -272,10 +278,12 @@ impl RunArgs {
272278
}
273279
};
274280

281+
let contracts_bytecode = fetch_contracts_bytecode_from_trace(&provider, &result).await?;
275282
handle_traces(
276283
result,
277284
&config,
278285
chain,
286+
&contracts_bytecode,
279287
self.label,
280288
self.with_local_artifacts,
281289
self.debug,
@@ -287,6 +295,38 @@ impl RunArgs {
287295
}
288296
}
289297

298+
pub async fn fetch_contracts_bytecode_from_trace(
299+
provider: &RootProvider<AnyNetwork>,
300+
result: &TraceResult,
301+
) -> Result<HashMap<Address, Bytes, RandomState>> {
302+
let mut contracts_bytecode = HashMap::new();
303+
if let Some(ref traces) = result.traces {
304+
let addresses = gather_trace_addresses(traces);
305+
for address in addresses {
306+
let code = provider.get_code_at(address).await?;
307+
if !code.is_empty() {
308+
contracts_bytecode.insert(address, code);
309+
}
310+
}
311+
}
312+
Ok(contracts_bytecode)
313+
}
314+
315+
fn gather_trace_addresses(traces: &Traces) -> Vec<Address> {
316+
let mut addresses = HashSet::new();
317+
for (_, trace) in traces {
318+
for node in trace.arena.nodes() {
319+
if !node.trace.address.is_zero() {
320+
addresses.insert(node.trace.address);
321+
}
322+
if !node.trace.caller.is_zero() {
323+
addresses.insert(node.trace.caller);
324+
}
325+
}
326+
}
327+
addresses.into_iter().collect()
328+
}
329+
290330
impl figment::Provider for RunArgs {
291331
fn metadata(&self) -> Metadata {
292332
Metadata::named("RunArgs")

crates/cli/src/utils/cmd.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use alloy_json_abi::JsonAbi;
2-
use alloy_primitives::Address;
2+
use alloy_primitives::{map::HashMap, Address, Bytes};
33
use eyre::{Result, WrapErr};
44
use foundry_common::{
55
compile::ProjectCompiler, fs, selectors::SelectorKind, shell, ContractsByArtifact,
@@ -25,6 +25,7 @@ use foundry_evm::{
2525
};
2626
use std::{
2727
fmt::Write,
28+
hash::RandomState,
2829
path::{Path, PathBuf},
2930
str::FromStr,
3031
};
@@ -332,10 +333,12 @@ impl TryFrom<Result<RawCallResult>> for TraceResult {
332333
}
333334

334335
/// labels the traces, conditionally prints them or opens the debugger
336+
#[expect(clippy::too_many_arguments)]
335337
pub async fn handle_traces(
336338
mut result: TraceResult,
337339
config: &Config,
338340
chain: Option<Chain>,
341+
contracts_bytecode: &HashMap<Address, Bytes, RandomState>,
339342
labels: Vec<String>,
340343
with_local_artifacts: bool,
341344
debug: bool,
@@ -374,7 +377,7 @@ pub async fn handle_traces(
374377
let mut identifier = TraceIdentifiers::new().with_etherscan(config, chain)?;
375378
if let Some(contracts) = &known_contracts {
376379
builder = builder.with_known_contracts(contracts);
377-
identifier = identifier.with_local(contracts);
380+
identifier = identifier.with_local_and_bytecodes(contracts, contracts_bytecode);
378381
}
379382

380383
let mut decoder = builder.build();

crates/evm/traces/src/identifier/local.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
use super::{IdentifiedAddress, TraceIdentifier};
22
use alloy_dyn_abi::JsonAbiExt;
33
use alloy_json_abi::JsonAbi;
4+
use alloy_primitives::{map::HashMap, Address, Bytes};
45
use foundry_common::contracts::{bytecode_diff_score, ContractsByArtifact};
56
use foundry_compilers::ArtifactId;
67
use revm_inspectors::tracing::types::CallTraceNode;
7-
use std::borrow::Cow;
8+
use std::{borrow::Cow, hash::RandomState};
89

910
/// A trace identifier that tries to identify addresses using local contracts.
1011
pub struct LocalTraceIdentifier<'a> {
1112
/// Known contracts to search through.
1213
known_contracts: &'a ContractsByArtifact,
1314
/// Vector of pairs of artifact ID and the runtime code length of the given artifact.
1415
ordered_ids: Vec<(&'a ArtifactId, usize)>,
16+
/// The contracts bytecode.
17+
contracts_bytecode: Option<&'a HashMap<Address, Bytes, RandomState>>,
1518
}
1619

1720
impl<'a> LocalTraceIdentifier<'a> {
@@ -24,7 +27,15 @@ impl<'a> LocalTraceIdentifier<'a> {
2427
.map(|(id, bytecode)| (id, bytecode.len()))
2528
.collect::<Vec<_>>();
2629
ordered_ids.sort_by_key(|(_, len)| *len);
27-
Self { known_contracts, ordered_ids }
30+
Self { known_contracts, ordered_ids, contracts_bytecode: None }
31+
}
32+
33+
pub fn with_bytecodes(
34+
mut self,
35+
contracts_bytecode: &'a HashMap<Address, Bytes, RandomState>,
36+
) -> Self {
37+
self.contracts_bytecode = Some(contracts_bytecode);
38+
self
2839
}
2940

3041
/// Returns the known contracts.
@@ -161,7 +172,18 @@ impl TraceIdentifier for LocalTraceIdentifier<'_> {
161172
let _span =
162173
trace_span!(target: "evm::traces::local", "identify", %address).entered();
163174

164-
let (id, abi) = self.identify_code(runtime_code?, creation_code?)?;
175+
// In order to identify the addresses, we need at least the runtime code. It can be
176+
// obtained from the trace itself (if it's a CREATE* call), or from the fetched
177+
// bytecodes.
178+
let (runtime_code, creation_code) = match (runtime_code, creation_code) {
179+
(Some(runtime_code), Some(creation_code)) => (runtime_code, creation_code),
180+
(Some(runtime_code), _) => (runtime_code, &[] as &[u8]),
181+
_ => {
182+
let code = self.contracts_bytecode?.get(&address)?;
183+
(code.as_ref(), &[] as &[u8])
184+
}
185+
};
186+
let (id, abi) = self.identify_code(runtime_code, creation_code)?;
165187
trace!(target: "evm::traces::local", id=%id.identifier(), "identified");
166188

167189
Some(IdentifiedAddress {

crates/evm/traces/src/identifier/mod.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use alloy_json_abi::JsonAbi;
2-
use alloy_primitives::Address;
2+
use alloy_primitives::{map::HashMap, Address, Bytes};
33
use foundry_common::ContractsByArtifact;
44
use foundry_compilers::ArtifactId;
55
use foundry_config::{Chain, Config};
66
use revm_inspectors::tracing::types::CallTraceNode;
7-
use std::borrow::Cow;
7+
use std::{borrow::Cow, hash::RandomState};
88

99
mod local;
1010
pub use local::LocalTraceIdentifier;
@@ -43,6 +43,8 @@ pub struct TraceIdentifiers<'a> {
4343
pub local: Option<LocalTraceIdentifier<'a>>,
4444
/// The optional Etherscan trace identifier.
4545
pub etherscan: Option<EtherscanIdentifier>,
46+
/// The contracts bytecode.
47+
pub contracts_bytecode: Option<&'a HashMap<Address, Bytes, RandomState>>,
4648
}
4749

4850
impl Default for TraceIdentifiers<'_> {
@@ -70,7 +72,7 @@ impl TraceIdentifier for TraceIdentifiers<'_> {
7072
impl<'a> TraceIdentifiers<'a> {
7173
/// Creates a new, empty instance.
7274
pub const fn new() -> Self {
73-
Self { local: None, etherscan: None }
75+
Self { local: None, etherscan: None, contracts_bytecode: None }
7476
}
7577

7678
/// Sets the local identifier.
@@ -79,6 +81,17 @@ impl<'a> TraceIdentifiers<'a> {
7981
self
8082
}
8183

84+
/// Sets the local identifier.
85+
pub fn with_local_and_bytecodes(
86+
mut self,
87+
known_contracts: &'a ContractsByArtifact,
88+
contracts_bytecode: &'a HashMap<Address, Bytes, RandomState>,
89+
) -> Self {
90+
self.local =
91+
Some(LocalTraceIdentifier::new(known_contracts).with_bytecodes(contracts_bytecode));
92+
self
93+
}
94+
8295
/// Sets the etherscan identifier.
8396
pub fn with_etherscan(mut self, config: &Config, chain: Option<Chain>) -> eyre::Result<Self> {
8497
self.etherscan = EtherscanIdentifier::new(config, chain)?;

0 commit comments

Comments
 (0)