Skip to content

Commit d82f029

Browse files
grandizzyrplusq
authored andcommitted
fix(traces): identify artifacts using both deployed and creation code (foundry-rs#9050)
* Identify by creation code * Compute score for both creation and runtime code * Fallback to deployed bytecode only if min creation bytecode score is under threshold * reuse check closure, add basic test
1 parent a0236f2 commit d82f029

File tree

5 files changed

+139
-19
lines changed

5 files changed

+139
-19
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,17 +262,18 @@ impl CallTraceDecoder {
262262
pub fn trace_addresses<'a>(
263263
&'a self,
264264
arena: &'a CallTraceArena,
265-
) -> impl Iterator<Item = (&'a Address, Option<&'a [u8]>)> + Clone + 'a {
265+
) -> impl Iterator<Item = (&'a Address, Option<&'a [u8]>, Option<&'a [u8]>)> + Clone + 'a {
266266
arena
267267
.nodes()
268268
.iter()
269269
.map(|node| {
270270
(
271271
&node.trace.address,
272272
node.trace.kind.is_any_create().then_some(&node.trace.output[..]),
273+
node.trace.kind.is_any_create().then_some(&node.trace.data[..]),
273274
)
274275
})
275-
.filter(|&(address, _)| {
276+
.filter(|&(address, _, _)| {
276277
!self.labels.contains_key(address) || !self.contracts.contains_key(address)
277278
})
278279
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ impl EtherscanIdentifier {
9797
impl TraceIdentifier for EtherscanIdentifier {
9898
fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec<AddressIdentity<'_>>
9999
where
100-
A: Iterator<Item = (&'a Address, Option<&'a [u8]>)>,
100+
A: Iterator<Item = (&'a Address, Option<&'a [u8]>, Option<&'a [u8]>)>,
101101
{
102102
trace!(target: "evm::traces", "identify {:?} addresses", addresses.size_hint().1);
103103

@@ -114,7 +114,7 @@ impl TraceIdentifier for EtherscanIdentifier {
114114
Arc::clone(&self.invalid_api_key),
115115
);
116116

117-
for (addr, _) in addresses {
117+
for (addr, _, _) in addresses {
118118
if let Some(metadata) = self.contracts.get(addr) {
119119
let label = metadata.contract_name.clone();
120120
let abi = metadata.abi().ok().map(Cow::Owned);

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

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,34 @@ impl<'a> LocalTraceIdentifier<'a> {
3232
self.known_contracts
3333
}
3434

35-
/// Tries to the bytecode most similar to the given one.
36-
pub fn identify_code(&self, code: &[u8]) -> Option<(&'a ArtifactId, &'a JsonAbi)> {
37-
let len = code.len();
35+
/// Identifies the artifact based on score computed for both creation and deployed bytecodes.
36+
pub fn identify_code(
37+
&self,
38+
runtime_code: &[u8],
39+
creation_code: &[u8],
40+
) -> Option<(&'a ArtifactId, &'a JsonAbi)> {
41+
let len = runtime_code.len();
3842

3943
let mut min_score = f64::MAX;
4044
let mut min_score_id = None;
4145

42-
let mut check = |id| {
46+
let mut check = |id, is_creation, min_score: &mut f64| {
4347
let contract = self.known_contracts.get(id)?;
44-
if let Some(deployed_bytecode) = contract.deployed_bytecode() {
45-
let score = bytecode_diff_score(deployed_bytecode, code);
48+
// Select bytecodes to compare based on `is_creation` flag.
49+
let (contract_bytecode, current_bytecode) = if is_creation {
50+
(contract.bytecode(), creation_code)
51+
} else {
52+
(contract.deployed_bytecode(), runtime_code)
53+
};
54+
55+
if let Some(bytecode) = contract_bytecode {
56+
let score = bytecode_diff_score(bytecode, current_bytecode);
4657
if score == 0.0 {
4758
trace!(target: "evm::traces", "found exact match");
4859
return Some((id, &contract.abi));
4960
}
50-
if score < min_score {
51-
min_score = score;
61+
if score < *min_score {
62+
*min_score = score;
5263
min_score_id = Some((id, &contract.abi));
5364
}
5465
}
@@ -65,7 +76,7 @@ impl<'a> LocalTraceIdentifier<'a> {
6576
if len > max_len {
6677
break;
6778
}
68-
if let found @ Some(_) = check(id) {
79+
if let found @ Some(_) = check(id, true, &mut min_score) {
6980
return found;
7081
}
7182
}
@@ -75,11 +86,20 @@ impl<'a> LocalTraceIdentifier<'a> {
7586
let idx = self.find_index(min_len);
7687
for i in idx..same_length_idx {
7788
let (id, _) = self.ordered_ids[i];
78-
if let found @ Some(_) = check(id) {
89+
if let found @ Some(_) = check(id, true, &mut min_score) {
7990
return found;
8091
}
8192
}
8293

94+
// Fallback to comparing deployed code if min score greater than threshold.
95+
if min_score >= 0.85 {
96+
for (artifact, _) in &self.ordered_ids {
97+
if let found @ Some(_) = check(artifact, false, &mut min_score) {
98+
return found;
99+
}
100+
}
101+
}
102+
83103
trace!(target: "evm::traces", %min_score, "no exact match found");
84104

85105
// Note: the diff score can be inaccurate for small contracts so we're using a relatively
@@ -109,16 +129,16 @@ impl<'a> LocalTraceIdentifier<'a> {
109129
impl TraceIdentifier for LocalTraceIdentifier<'_> {
110130
fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec<AddressIdentity<'_>>
111131
where
112-
A: Iterator<Item = (&'a Address, Option<&'a [u8]>)>,
132+
A: Iterator<Item = (&'a Address, Option<&'a [u8]>, Option<&'a [u8]>)>,
113133
{
114134
trace!(target: "evm::traces", "identify {:?} addresses", addresses.size_hint().1);
115135

116136
addresses
117-
.filter_map(|(address, code)| {
137+
.filter_map(|(address, runtime_code, creation_code)| {
118138
let _span = trace_span!(target: "evm::traces", "identify", %address).entered();
119139

120140
trace!(target: "evm::traces", "identifying");
121-
let (id, abi) = self.identify_code(code?)?;
141+
let (id, abi) = self.identify_code(runtime_code?, creation_code?)?;
122142
trace!(target: "evm::traces", id=%id.identifier(), "identified");
123143

124144
Some(AddressIdentity {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ pub trait TraceIdentifier {
3535
/// Attempts to identify an address in one or more call traces.
3636
fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec<AddressIdentity<'_>>
3737
where
38-
A: Iterator<Item = (&'a Address, Option<&'a [u8]>)> + Clone;
38+
A: Iterator<Item = (&'a Address, Option<&'a [u8]>, Option<&'a [u8]>)> + Clone;
3939
}
4040

4141
/// A collection of trace identifiers.
@@ -55,7 +55,7 @@ impl Default for TraceIdentifiers<'_> {
5555
impl TraceIdentifier for TraceIdentifiers<'_> {
5656
fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec<AddressIdentity<'_>>
5757
where
58-
A: Iterator<Item = (&'a Address, Option<&'a [u8]>)> + Clone,
58+
A: Iterator<Item = (&'a Address, Option<&'a [u8]>, Option<&'a [u8]>)> + Clone,
5959
{
6060
let mut identities = Vec::new();
6161
if let Some(local) = &mut self.local {

crates/forge/tests/cli/test_cmd.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2274,3 +2274,102 @@ Logs:
22742274
...
22752275
"#]]);
22762276
});
2277+
2278+
// <https://github.com/foundry-rs/foundry/issues/8995>
2279+
forgetest_init!(metadata_bytecode_traces, |prj, cmd| {
2280+
prj.add_source(
2281+
"ParentProxy.sol",
2282+
r#"
2283+
import {Counter} from "./Counter.sol";
2284+
2285+
abstract contract ParentProxy {
2286+
Counter impl;
2287+
bytes data;
2288+
2289+
constructor(Counter _implementation, bytes memory _data) {
2290+
impl = _implementation;
2291+
data = _data;
2292+
}
2293+
}
2294+
"#,
2295+
)
2296+
.unwrap();
2297+
prj.add_source(
2298+
"Proxy.sol",
2299+
r#"
2300+
import {ParentProxy} from "./ParentProxy.sol";
2301+
import {Counter} from "./Counter.sol";
2302+
2303+
contract Proxy is ParentProxy {
2304+
constructor(Counter _implementation, bytes memory _data)
2305+
ParentProxy(_implementation, _data)
2306+
{}
2307+
}
2308+
"#,
2309+
)
2310+
.unwrap();
2311+
2312+
prj.add_test(
2313+
"MetadataTraceTest.t.sol",
2314+
r#"
2315+
import {Counter} from "src/Counter.sol";
2316+
import {Proxy} from "src/Proxy.sol";
2317+
2318+
import {Test} from "forge-std/Test.sol";
2319+
2320+
contract MetadataTraceTest is Test {
2321+
function test_proxy_trace() public {
2322+
Counter counter = new Counter();
2323+
new Proxy(counter, "");
2324+
}
2325+
}
2326+
"#,
2327+
)
2328+
.unwrap();
2329+
2330+
cmd.args(["test", "--mt", "test_proxy_trace", "-vvvv"]).assert_success().stdout_eq(str![[r#"
2331+
[COMPILING_FILES] with [SOLC_VERSION]
2332+
[SOLC_VERSION] [ELAPSED]
2333+
Compiler run successful!
2334+
2335+
Ran 1 test for test/MetadataTraceTest.t.sol:MetadataTraceTest
2336+
[PASS] test_proxy_trace() ([GAS])
2337+
Traces:
2338+
[152142] MetadataTraceTest::test_proxy_trace()
2339+
├─ [49499] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
2340+
│ └─ ← [Return] 247 bytes of code
2341+
├─ [37978] → new Proxy@0x2e234DAe75C793f67A35089C9d99245E1C58470b
2342+
│ └─ ← [Return] 63 bytes of code
2343+
└─ ← [Stop]
2344+
2345+
Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]
2346+
2347+
Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)
2348+
2349+
"#]]);
2350+
2351+
// Check consistent traces for running with no metadata.
2352+
cmd.forge_fuse()
2353+
.args(["test", "--mt", "test_proxy_trace", "-vvvv", "--no-metadata"])
2354+
.assert_success()
2355+
.stdout_eq(str![[r#"
2356+
[COMPILING_FILES] with [SOLC_VERSION]
2357+
[SOLC_VERSION] [ELAPSED]
2358+
Compiler run successful!
2359+
2360+
Ran 1 test for test/MetadataTraceTest.t.sol:MetadataTraceTest
2361+
[PASS] test_proxy_trace() ([GAS])
2362+
Traces:
2363+
[130521] MetadataTraceTest::test_proxy_trace()
2364+
├─ [38693] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
2365+
│ └─ ← [Return] 193 bytes of code
2366+
├─ [27175] → new Proxy@0x2e234DAe75C793f67A35089C9d99245E1C58470b
2367+
│ └─ ← [Return] 9 bytes of code
2368+
└─ ← [Stop]
2369+
2370+
Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]
2371+
2372+
Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)
2373+
2374+
"#]]);
2375+
});

0 commit comments

Comments
 (0)