foundry_evm_traces/identifier/
local.rs

1use super::{AddressIdentity, TraceIdentifier};
2use alloy_json_abi::JsonAbi;
3use alloy_primitives::Address;
4use foundry_common::contracts::{bytecode_diff_score, ContractsByArtifact};
5use foundry_compilers::ArtifactId;
6use std::borrow::Cow;
7
8/// A trace identifier that tries to identify addresses using local contracts.
9pub struct LocalTraceIdentifier<'a> {
10    /// Known contracts to search through.
11    known_contracts: &'a ContractsByArtifact,
12    /// Vector of pairs of artifact ID and the runtime code length of the given artifact.
13    ordered_ids: Vec<(&'a ArtifactId, usize)>,
14}
15
16impl<'a> LocalTraceIdentifier<'a> {
17    /// Creates a new local trace identifier.
18    #[inline]
19    pub fn new(known_contracts: &'a ContractsByArtifact) -> Self {
20        let mut ordered_ids = known_contracts
21            .iter()
22            .filter_map(|(id, contract)| Some((id, contract.deployed_bytecode()?)))
23            .map(|(id, bytecode)| (id, bytecode.len()))
24            .collect::<Vec<_>>();
25        ordered_ids.sort_by_key(|(_, len)| *len);
26        Self { known_contracts, ordered_ids }
27    }
28
29    /// Returns the known contracts.
30    #[inline]
31    pub fn contracts(&self) -> &'a ContractsByArtifact {
32        self.known_contracts
33    }
34
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();
42
43        let mut min_score = f64::MAX;
44        let mut min_score_id = None;
45
46        let mut check = |id, is_creation, min_score: &mut f64| {
47            let contract = self.known_contracts.get(id)?;
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);
57                if score == 0.0 {
58                    trace!(target: "evm::traces", "found exact match");
59                    return Some((id, &contract.abi));
60                }
61                if score < *min_score {
62                    *min_score = score;
63                    min_score_id = Some((id, &contract.abi));
64                }
65            }
66            None
67        };
68
69        // Check `[len * 0.9, ..., len * 1.1]`.
70        let max_len = (len * 11) / 10;
71
72        // Start at artifacts with the same code length: `len..len*1.1`.
73        let same_length_idx = self.find_index(len);
74        for idx in same_length_idx..self.ordered_ids.len() {
75            let (id, len) = self.ordered_ids[idx];
76            if len > max_len {
77                break;
78            }
79            if let found @ Some(_) = check(id, true, &mut min_score) {
80                return found;
81            }
82        }
83
84        // Iterate over the remaining artifacts with less code length: `len*0.9..len`.
85        let min_len = (len * 9) / 10;
86        let idx = self.find_index(min_len);
87        for i in idx..same_length_idx {
88            let (id, _) = self.ordered_ids[i];
89            if let found @ Some(_) = check(id, true, &mut min_score) {
90                return found;
91            }
92        }
93
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
103        trace!(target: "evm::traces", %min_score, "no exact match found");
104
105        // Note: the diff score can be inaccurate for small contracts so we're using a relatively
106        // high threshold here to avoid filtering out too many contracts.
107        if min_score < 0.85 {
108            min_score_id
109        } else {
110            None
111        }
112    }
113
114    /// Returns the index of the artifact with the given code length, or the index of the first
115    /// artifact with a greater code length if the exact code length is not found.
116    fn find_index(&self, len: usize) -> usize {
117        let (Ok(mut idx) | Err(mut idx)) =
118            self.ordered_ids.binary_search_by_key(&len, |(_, probe)| *probe);
119
120        // In case of multiple artifacts with the same code length, we need to find the first one.
121        while idx > 0 && self.ordered_ids[idx - 1].1 == len {
122            idx -= 1;
123        }
124
125        idx
126    }
127}
128
129impl TraceIdentifier for LocalTraceIdentifier<'_> {
130    fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec<AddressIdentity<'_>>
131    where
132        A: Iterator<Item = (&'a Address, Option<&'a [u8]>, Option<&'a [u8]>)>,
133    {
134        trace!(target: "evm::traces", "identify {:?} addresses", addresses.size_hint().1);
135
136        addresses
137            .filter_map(|(address, runtime_code, creation_code)| {
138                let _span = trace_span!(target: "evm::traces", "identify", %address).entered();
139
140                trace!(target: "evm::traces", "identifying");
141                let (id, abi) = self.identify_code(runtime_code?, creation_code?)?;
142                trace!(target: "evm::traces", id=%id.identifier(), "identified");
143
144                Some(AddressIdentity {
145                    address: *address,
146                    contract: Some(id.identifier()),
147                    label: Some(id.name.clone()),
148                    abi: Some(Cow::Borrowed(abi)),
149                    artifact_id: Some(id.clone()),
150                })
151            })
152            .collect()
153    }
154}