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