foundry_evm_traces/identifier/
local.rs
1use super::{IdentifiedAddress, TraceIdentifier};
2use alloy_dyn_abi::JsonAbiExt;
3use alloy_json_abi::JsonAbi;
4use foundry_common::contracts::{bytecode_diff_score, ContractsByArtifact};
5use foundry_compilers::ArtifactId;
6use revm_inspectors::tracing::types::CallTraceNode;
7use std::borrow::Cow;
8
9pub struct LocalTraceIdentifier<'a> {
11 known_contracts: &'a ContractsByArtifact,
13 ordered_ids: Vec<(&'a ArtifactId, usize)>,
15}
16
17impl<'a> LocalTraceIdentifier<'a> {
18 #[inline]
20 pub fn new(known_contracts: &'a ContractsByArtifact) -> Self {
21 let mut ordered_ids = known_contracts
22 .iter()
23 .filter_map(|(id, contract)| Some((id, contract.deployed_bytecode()?)))
24 .map(|(id, bytecode)| (id, bytecode.len()))
25 .collect::<Vec<_>>();
26 ordered_ids.sort_by_key(|(_, len)| *len);
27 Self { known_contracts, ordered_ids }
28 }
29
30 #[inline]
32 pub fn contracts(&self) -> &'a ContractsByArtifact {
33 self.known_contracts
34 }
35
36 pub fn identify_code(
38 &self,
39 runtime_code: &[u8],
40 creation_code: &[u8],
41 ) -> Option<(&'a ArtifactId, &'a JsonAbi)> {
42 let len = runtime_code.len();
43
44 let mut min_score = f64::MAX;
45 let mut min_score_id = None;
46
47 let mut check = |id, is_creation, min_score: &mut f64| {
48 let contract = self.known_contracts.get(id)?;
49 let (contract_bytecode, current_bytecode) = if is_creation {
51 (contract.bytecode(), creation_code)
52 } else {
53 (contract.deployed_bytecode(), runtime_code)
54 };
55
56 if let Some(bytecode) = contract_bytecode {
57 let mut current_bytecode = current_bytecode;
58 if is_creation && current_bytecode.len() > bytecode.len() {
59 if let Some(constructor) = contract.abi.constructor() {
61 let constructor_args = ¤t_bytecode[bytecode.len()..];
62 if constructor.abi_decode_input(constructor_args).is_ok() {
63 current_bytecode = ¤t_bytecode[..bytecode.len()]
66 }
67 }
68 }
69
70 let score = bytecode_diff_score(bytecode, current_bytecode);
71 if score == 0.0 {
72 trace!(target: "evm::traces::local", "found exact match");
73 return Some((id, &contract.abi));
74 }
75 if score < *min_score {
76 *min_score = score;
77 min_score_id = Some((id, &contract.abi));
78 }
79 }
80 None
81 };
82
83 let max_len = (len * 11) / 10;
85
86 let same_length_idx = self.find_index(len);
88 for idx in same_length_idx..self.ordered_ids.len() {
89 let (id, len) = self.ordered_ids[idx];
90 if len > max_len {
91 break;
92 }
93 if let found @ Some(_) = check(id, true, &mut min_score) {
94 return found;
95 }
96 }
97
98 let min_len = (len * 9) / 10;
100 let idx = self.find_index(min_len);
101 for i in idx..same_length_idx {
102 let (id, _) = self.ordered_ids[i];
103 if let found @ Some(_) = check(id, true, &mut min_score) {
104 return found;
105 }
106 }
107
108 if min_score >= 0.85 {
110 for (artifact, _) in &self.ordered_ids {
111 if let found @ Some(_) = check(artifact, false, &mut min_score) {
112 return found;
113 }
114 }
115 }
116
117 trace!(target: "evm::traces::local", %min_score, "no exact match found");
118
119 if min_score < 0.85 {
122 min_score_id
123 } else {
124 None
125 }
126 }
127
128 fn find_index(&self, len: usize) -> usize {
131 let (Ok(mut idx) | Err(mut idx)) =
132 self.ordered_ids.binary_search_by_key(&len, |(_, probe)| *probe);
133
134 while idx > 0 && self.ordered_ids[idx - 1].1 == len {
136 idx -= 1;
137 }
138
139 idx
140 }
141}
142
143impl TraceIdentifier for LocalTraceIdentifier<'_> {
144 fn identify_addresses(&mut self, nodes: &[&CallTraceNode]) -> Vec<IdentifiedAddress<'_>> {
145 if nodes.is_empty() {
146 return Vec::new();
147 }
148
149 trace!(target: "evm::traces::local", "identify {} addresses", nodes.len());
150
151 nodes
152 .iter()
153 .map(|&node| {
154 (
155 node.trace.address,
156 node.trace.kind.is_any_create().then_some(&node.trace.output[..]),
157 node.trace.kind.is_any_create().then_some(&node.trace.data[..]),
158 )
159 })
160 .filter_map(|(address, runtime_code, creation_code)| {
161 let _span =
162 trace_span!(target: "evm::traces::local", "identify", %address).entered();
163
164 let (id, abi) = self.identify_code(runtime_code?, creation_code?)?;
165 trace!(target: "evm::traces::local", id=%id.identifier(), "identified");
166
167 Some(IdentifiedAddress {
168 address,
169 contract: Some(id.identifier()),
170 label: Some(id.name.clone()),
171 abi: Some(Cow::Borrowed(abi)),
172 artifact_id: Some(id.clone()),
173 })
174 })
175 .collect()
176 }
177}