Skip to main content

foundry_evm_traces/identifier/
local.rs

1use 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
10/// A trace identifier that tries to identify addresses using local contracts.
11pub struct LocalTraceIdentifier<'a> {
12    /// Known contracts to search through.
13    known_contracts: &'a ContractsByArtifact,
14    /// Vector of pairs of artifact ID and the runtime code length of the given artifact.
15    ordered_ids: Vec<(&'a ArtifactId, usize)>,
16    /// The contracts bytecode.
17    contracts_bytecode: Option<&'a AddressHashMap<Bytes>>,
18}
19
20impl<'a> LocalTraceIdentifier<'a> {
21    /// Creates a new local trace identifier.
22    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    /// Returns the known contracts.
38    #[inline]
39    pub const fn contracts(&self) -> &'a ContractsByArtifact {
40        self.known_contracts
41    }
42
43    /// Identifies the artifact based on score computed for both creation and deployed bytecodes.
44    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            // Select bytecodes to compare based on `is_creation` flag.
57            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                    // Try to decode ctor args with contract abi.
68                    if let Some(constructor) = contract.abi.constructor() {
69                        let constructor_args = &current_bytecode[bytecode.len()..];
70                        if constructor.abi_decode_input(constructor_args).is_ok() {
71                            // If we can decode args with current abi then remove args from
72                            // code to compare.
73                            current_bytecode = &current_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            // Check `[len * 0.9, ..., len * 1.1]`.
94            let max_len = (len * 11) / 10;
95
96            // Start at artifacts with the same code length: `len..len*1.1`.
97            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            // Iterate over the remaining artifacts with less code length: `len*0.9..len`.
109            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        // Fallback to comparing deployed code if min score greater than threshold.
120        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        // Note: the diff score can be inaccurate for small contracts so we're using a relatively
131        // high threshold here to avoid filtering out too many contracts.
132        if min_score < 0.85 { min_score_id } else { None }
133    }
134
135    /// Returns the index of the artifact with the given code length, or the index of the first
136    /// artifact with a greater code length if the exact code length is not found.
137    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        // In case of multiple artifacts with the same code length, we need to find the first one.
142        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                // In order to identify the addresses, we need at least the runtime code. It can be
172                // obtained from the trace itself (if it's a CREATE* call), or from the fetched
173                // bytecodes.
174                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}