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
9/// A trace identifier that tries to identify addresses using local contracts.
10pub struct LocalTraceIdentifier<'a> {
11    /// Known contracts to search through.
12    known_contracts: &'a ContractsByArtifact,
13    /// Vector of pairs of artifact ID and the runtime code length of the given artifact.
14    ordered_ids: Vec<(&'a ArtifactId, usize)>,
15}
16
17impl<'a> LocalTraceIdentifier<'a> {
18    /// Creates a new local trace identifier.
19    #[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    /// Returns the known contracts.
31    #[inline]
32    pub fn contracts(&self) -> &'a ContractsByArtifact {
33        self.known_contracts
34    }
35
36    /// Identifies the artifact based on score computed for both creation and deployed bytecodes.
37    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            // Select bytecodes to compare based on `is_creation` flag.
50            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                    // Try to decode ctor args with contract abi.
60                    if let Some(constructor) = contract.abi.constructor() {
61                        let constructor_args = &current_bytecode[bytecode.len()..];
62                        if constructor.abi_decode_input(constructor_args).is_ok() {
63                            // If we can decode args with current abi then remove args from
64                            // code to compare.
65                            current_bytecode = &current_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        // Check `[len * 0.9, ..., len * 1.1]`.
84        let max_len = (len * 11) / 10;
85
86        // Start at artifacts with the same code length: `len..len*1.1`.
87        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        // Iterate over the remaining artifacts with less code length: `len*0.9..len`.
99        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        // Fallback to comparing deployed code if min score greater than threshold.
109        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        // Note: the diff score can be inaccurate for small contracts so we're using a relatively
120        // high threshold here to avoid filtering out too many contracts.
121        if min_score < 0.85 {
122            min_score_id
123        } else {
124            None
125        }
126    }
127
128    /// Returns the index of the artifact with the given code length, or the index of the first
129    /// artifact with a greater code length if the exact code length is not found.
130    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        // In case of multiple artifacts with the same code length, we need to find the first one.
135        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}