1use crate::compile::PathOrContractInfo;
4use alloy_dyn_abi::JsonAbiExt;
5use alloy_json_abi::{Event, Function, JsonAbi};
6use alloy_primitives::{hex, Address, Bytes, Selector, B256};
7use eyre::{OptionExt, Result};
8use foundry_compilers::{
9 artifacts::{
10 BytecodeObject, CompactBytecode, CompactContractBytecode, CompactDeployedBytecode,
11 ConfigurableContractArtifact, ContractBytecodeSome, Offsets,
12 },
13 utils::canonicalized,
14 ArtifactId, Project, ProjectCompileOutput,
15};
16use std::{
17 collections::BTreeMap,
18 ops::Deref,
19 path::{Path, PathBuf},
20 str::FromStr,
21 sync::Arc,
22};
23
24const CALL_PROTECTION_BYTECODE_PREFIX: [u8; 21] =
29 hex!("730000000000000000000000000000000000000000");
30
31#[expect(missing_docs)]
33#[derive(Debug, Clone)]
34pub struct BytecodeData {
35 pub object: Option<BytecodeObject>,
36 pub link_references: BTreeMap<String, BTreeMap<String, Vec<Offsets>>>,
37 pub immutable_references: BTreeMap<String, Vec<Offsets>>,
38}
39
40impl BytecodeData {
41 fn bytes(&self) -> Option<&Bytes> {
42 self.object.as_ref().and_then(|b| b.as_bytes())
43 }
44}
45
46impl From<CompactBytecode> for BytecodeData {
47 fn from(bytecode: CompactBytecode) -> Self {
48 Self {
49 object: Some(bytecode.object),
50 link_references: bytecode.link_references,
51 immutable_references: BTreeMap::new(),
52 }
53 }
54}
55
56impl From<CompactDeployedBytecode> for BytecodeData {
57 fn from(bytecode: CompactDeployedBytecode) -> Self {
58 let (object, link_references) = if let Some(compact) = bytecode.bytecode {
59 (Some(compact.object), compact.link_references)
60 } else {
61 (None, BTreeMap::new())
62 };
63 Self { object, link_references, immutable_references: bytecode.immutable_references }
64 }
65}
66
67#[derive(Debug)]
69pub struct ContractData {
70 pub name: String,
72 pub abi: JsonAbi,
74 pub bytecode: Option<BytecodeData>,
76 pub deployed_bytecode: Option<BytecodeData>,
78}
79
80impl ContractData {
81 pub fn bytecode(&self) -> Option<&Bytes> {
83 self.bytecode.as_ref()?.bytes().filter(|b| !b.is_empty())
84 }
85
86 pub fn deployed_bytecode(&self) -> Option<&Bytes> {
88 self.deployed_bytecode.as_ref()?.bytes().filter(|b| !b.is_empty())
89 }
90}
91
92type ArtifactWithContractRef<'a> = (&'a ArtifactId, &'a ContractData);
93
94#[derive(Clone, Default, Debug)]
96pub struct ContractsByArtifact(Arc<BTreeMap<ArtifactId, ContractData>>);
97
98impl ContractsByArtifact {
99 pub fn new(artifacts: impl IntoIterator<Item = (ArtifactId, CompactContractBytecode)>) -> Self {
101 let map = artifacts
102 .into_iter()
103 .filter_map(|(id, artifact)| {
104 let name = id.name.clone();
105 let CompactContractBytecode { abi, bytecode, deployed_bytecode } = artifact;
106 Some((
107 id,
108 ContractData {
109 name,
110 abi: abi?,
111 bytecode: bytecode.map(Into::into),
112 deployed_bytecode: deployed_bytecode.map(Into::into),
113 },
114 ))
115 })
116 .collect();
117 Self(Arc::new(map))
118 }
119
120 pub fn clear(&mut self) {
122 *self = Self::default();
123 }
124
125 pub fn find_by_creation_code(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
127 self.find_by_code(code, 0.1, true, ContractData::bytecode)
128 }
129
130 pub fn find_by_deployed_code(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
132 self.find_by_code(code, 0.15, false, ContractData::deployed_bytecode)
133 }
134
135 fn find_by_code(
138 &self,
139 code: &[u8],
140 accepted_score: f64,
141 strip_ctor_args: bool,
142 get: impl Fn(&ContractData) -> Option<&Bytes>,
143 ) -> Option<ArtifactWithContractRef<'_>> {
144 self.iter()
145 .filter_map(|(id, contract)| {
146 if let Some(deployed_bytecode) = get(contract) {
147 let mut code = code;
148 if strip_ctor_args && code.len() > deployed_bytecode.len() {
149 if let Some(constructor) = contract.abi.constructor() {
151 let constructor_args = &code[deployed_bytecode.len()..];
152 if constructor.abi_decode_input(constructor_args, false).is_ok() {
153 code = &code[..deployed_bytecode.len()]
156 }
157 }
158 };
159
160 let score = bytecode_diff_score(deployed_bytecode.as_ref(), code);
161 (score <= accepted_score).then_some((score, (id, contract)))
162 } else {
163 None
164 }
165 })
166 .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2))
167 .map(|(_, data)| data)
168 }
169
170 pub fn find_by_deployed_code_exact(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
173 if code.is_empty() {
175 return None;
176 }
177
178 self.iter().find(|(_, contract)| {
179 let Some(deployed_bytecode) = &contract.deployed_bytecode else {
180 return false;
181 };
182 let Some(deployed_code) = &deployed_bytecode.object else {
183 return false;
184 };
185
186 let len = match deployed_code {
187 BytecodeObject::Bytecode(ref bytes) => bytes.len(),
188 BytecodeObject::Unlinked(ref bytes) => bytes.len() / 2,
189 };
190
191 if len != code.len() {
192 return false;
193 }
194
195 let mut ignored = deployed_bytecode
197 .immutable_references
198 .values()
199 .chain(deployed_bytecode.link_references.values().flat_map(|v| v.values()))
200 .flatten()
201 .cloned()
202 .collect::<Vec<_>>();
203
204 let has_call_protection = match deployed_code {
209 BytecodeObject::Bytecode(ref bytes) => {
210 bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX)
211 }
212 BytecodeObject::Unlinked(ref bytes) => {
213 if let Ok(bytes) =
214 Bytes::from_str(&bytes[..CALL_PROTECTION_BYTECODE_PREFIX.len() * 2])
215 {
216 bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX)
217 } else {
218 false
219 }
220 }
221 };
222
223 if has_call_protection {
224 ignored.push(Offsets { start: 1, length: 20 });
225 }
226
227 ignored.sort_by_key(|o| o.start);
228
229 let mut left = 0;
230 for offset in ignored {
231 let right = offset.start as usize;
232
233 let matched = match deployed_code {
234 BytecodeObject::Bytecode(ref bytes) => bytes[left..right] == code[left..right],
235 BytecodeObject::Unlinked(ref bytes) => {
236 if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..right * 2]) {
237 bytes == code[left..right]
238 } else {
239 false
240 }
241 }
242 };
243
244 if !matched {
245 return false;
246 }
247
248 left = right + offset.length as usize;
249 }
250
251 if left < code.len() {
252 match deployed_code {
253 BytecodeObject::Bytecode(ref bytes) => bytes[left..] == code[left..],
254 BytecodeObject::Unlinked(ref bytes) => {
255 if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..]) {
256 bytes == code[left..]
257 } else {
258 false
259 }
260 }
261 }
262 } else {
263 true
264 }
265 })
266 }
267
268 pub fn find_by_name_or_identifier(
271 &self,
272 id: &str,
273 ) -> Result<Option<ArtifactWithContractRef<'_>>> {
274 let contracts = self
275 .iter()
276 .filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id)
277 .collect::<Vec<_>>();
278
279 if contracts.len() > 1 {
280 eyre::bail!("{id} has more than one implementation.");
281 }
282
283 Ok(contracts.first().cloned())
284 }
285
286 pub fn find_abi_by_name_or_identifier(&self, id: &str) -> Option<JsonAbi> {
288 self.iter()
289 .find(|(artifact, _)| {
290 artifact.name.split(".").next().unwrap() == id || artifact.identifier() == id
291 })
292 .map(|(_, contract)| contract.abi.clone())
293 }
294
295 pub fn find_abi_by_name_or_src_path(&self, name_or_path: &str) -> Option<(JsonAbi, String)> {
299 self.iter()
300 .find(|(artifact, _)| {
301 artifact.name == name_or_path || artifact.source == PathBuf::from(name_or_path)
302 })
303 .map(|(_, contract)| (contract.abi.clone(), contract.name.clone()))
304 }
305
306 pub fn flatten(&self) -> (BTreeMap<Selector, Function>, BTreeMap<B256, Event>, JsonAbi) {
308 let mut funcs = BTreeMap::new();
309 let mut events = BTreeMap::new();
310 let mut errors_abi = JsonAbi::new();
311 for (_name, contract) in self.iter() {
312 for func in contract.abi.functions() {
313 funcs.insert(func.selector(), func.clone());
314 }
315 for event in contract.abi.events() {
316 events.insert(event.selector(), event.clone());
317 }
318 for error in contract.abi.errors() {
319 errors_abi.errors.entry(error.name.clone()).or_default().push(error.clone());
320 }
321 }
322 (funcs, events, errors_abi)
323 }
324}
325
326impl From<ProjectCompileOutput> for ContractsByArtifact {
327 fn from(value: ProjectCompileOutput) -> Self {
328 Self::new(value.into_artifacts().map(|(id, ar)| {
329 (
330 id,
331 CompactContractBytecode {
332 abi: ar.abi,
333 bytecode: ar.bytecode,
334 deployed_bytecode: ar.deployed_bytecode,
335 },
336 )
337 }))
338 }
339}
340
341impl Deref for ContractsByArtifact {
342 type Target = BTreeMap<ArtifactId, ContractData>;
343
344 fn deref(&self) -> &Self::Target {
345 &self.0
346 }
347}
348
349pub type ContractsByAddress = BTreeMap<Address, (String, JsonAbi)>;
351
352pub fn bytecode_diff_score<'a>(mut a: &'a [u8], mut b: &'a [u8]) -> f64 {
356 if a.len() < b.len() {
358 std::mem::swap(&mut a, &mut b);
359 }
360
361 let mut n_different_bytes = a.len() - b.len();
363
364 if n_different_bytes > 32 && n_different_bytes * 10 > a.len() {
369 return 1.0;
370 }
371
372 n_different_bytes += unsafe { count_different_bytes(a, b) };
375
376 n_different_bytes as f64 / a.len() as f64
377}
378
379unsafe fn count_different_bytes(a: &[u8], b: &[u8]) -> usize {
385 let a_ptr = a.as_ptr();
390 let b_ptr = b.as_ptr();
391 let len = b.len();
392
393 let mut sum = 0;
394 let mut i = 0;
395 while i < len {
396 sum += unsafe { *a_ptr.add(i) != *b_ptr.add(i) } as usize;
398 i += 1;
399 }
400 sum
401}
402
403pub fn get_contract_name(id: &str) -> &str {
422 id.rsplit(':').next().unwrap_or(id)
423}
424
425pub fn get_file_name(id: &str) -> &str {
437 id.split(':').next().unwrap_or(id)
438}
439
440pub fn compact_to_contract(contract: CompactContractBytecode) -> Result<ContractBytecodeSome> {
442 Ok(ContractBytecodeSome {
443 abi: contract.abi.ok_or_else(|| eyre::eyre!("No contract abi"))?,
444 bytecode: contract.bytecode.ok_or_else(|| eyre::eyre!("No contract bytecode"))?.into(),
445 deployed_bytecode: contract
446 .deployed_bytecode
447 .ok_or_else(|| eyre::eyre!("No contract deployed bytecode"))?
448 .into(),
449 })
450}
451
452pub fn find_target_path(project: &Project, identifier: &PathOrContractInfo) -> Result<PathBuf> {
454 match identifier {
455 PathOrContractInfo::Path(path) => Ok(canonicalized(project.root().join(path))),
456 PathOrContractInfo::ContractInfo(info) => {
457 let path = project.find_contract_path(&info.name)?;
458 Ok(path)
459 }
460 }
461}
462
463pub fn find_matching_contract_artifact(
465 output: &mut ProjectCompileOutput,
466 target_path: &Path,
467 target_name: Option<&str>,
468) -> eyre::Result<ConfigurableContractArtifact> {
469 if let Some(name) = target_name {
470 output
471 .remove(target_path, name)
472 .ok_or_eyre(format!("Could not find artifact `{name}` in the compiled artifacts"))
473 } else {
474 let possible_targets = output
475 .artifact_ids()
476 .filter(|(id, _artifact)| id.source == target_path)
477 .collect::<Vec<_>>();
478
479 if possible_targets.is_empty() {
480 eyre::bail!("Could not find artifact linked to source `{target_path:?}` in the compiled artifacts");
481 }
482
483 let (target_id, target_artifact) = possible_targets[0].clone();
484 if possible_targets.len() == 1 {
485 return Ok(target_artifact.clone());
486 }
487
488 if !target_id.name.contains(".") &&
492 possible_targets.iter().any(|(id, _)| id.name != target_id.name)
493 {
494 eyre::bail!("Multiple contracts found in the same file, please specify the target <path>:<contract> or <contract>");
495 }
496
497 let artifact = possible_targets
500 .iter()
501 .find_map(|(id, artifact)| if id.profile == "default" { Some(*artifact) } else { None })
502 .unwrap_or(target_artifact);
503
504 Ok(artifact.clone())
505 }
506}
507#[cfg(test)]
508mod tests {
509 use super::*;
510
511 #[test]
512 fn bytecode_diffing() {
513 assert_eq!(bytecode_diff_score(b"a", b"a"), 0.0);
514 assert_eq!(bytecode_diff_score(b"a", b"b"), 1.0);
515
516 let a_100 = &b"a".repeat(100)[..];
517 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(100)), 1.0);
518 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(99)), 1.0);
519 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(101)), 1.0);
520 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(120)), 1.0);
521 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(1000)), 1.0);
522
523 let a_99 = &b"a".repeat(99)[..];
524 assert!(bytecode_diff_score(a_100, a_99) <= 0.01);
525 }
526
527 #[test]
528 fn find_by_deployed_code_exact_with_empty_deployed() {
529 let contracts = ContractsByArtifact::new(vec![]);
530
531 assert!(contracts.find_by_deployed_code_exact(&[]).is_none());
532 }
533}