1use crate::{compile::PathOrContractInfo, find_metadata_start, strip_bytecode_placeholders};
4use alloy_dyn_abi::JsonAbiExt;
5use alloy_json_abi::{Event, Function, JsonAbi};
6use alloy_primitives::{Address, B256, Bytes, Selector, hex};
7use eyre::{OptionExt, Result};
8use foundry_compilers::{
9 ArtifactId, Project, ProjectCompileOutput,
10 artifacts::{
11 BytecodeObject, CompactBytecode, CompactContractBytecode, CompactContractBytecodeCow,
12 CompactDeployedBytecode, ConfigurableContractArtifact, ContractBytecodeSome, Offsets,
13 StorageLayout,
14 },
15 utils::canonicalized,
16};
17use std::{
18 collections::BTreeMap,
19 ops::Deref,
20 path::{Path, PathBuf},
21 str::FromStr,
22 sync::Arc,
23};
24
25const CALL_PROTECTION_BYTECODE_PREFIX: [u8; 21] =
30 hex!("730000000000000000000000000000000000000000");
31
32#[expect(missing_docs)]
34#[derive(Debug, Clone)]
35pub struct BytecodeData {
36 pub object: Option<BytecodeObject>,
37 pub link_references: BTreeMap<String, BTreeMap<String, Vec<Offsets>>>,
38 pub immutable_references: BTreeMap<String, Vec<Offsets>>,
39}
40
41impl BytecodeData {
42 fn bytes(&self) -> Option<&Bytes> {
43 self.object.as_ref().and_then(|b| b.as_bytes())
44 }
45}
46
47impl From<CompactBytecode> for BytecodeData {
48 fn from(bytecode: CompactBytecode) -> Self {
49 Self {
50 object: Some(bytecode.object),
51 link_references: bytecode.link_references,
52 immutable_references: BTreeMap::new(),
53 }
54 }
55}
56
57impl From<CompactDeployedBytecode> for BytecodeData {
58 fn from(bytecode: CompactDeployedBytecode) -> Self {
59 let (object, link_references) = if let Some(compact) = bytecode.bytecode {
60 (Some(compact.object), compact.link_references)
61 } else {
62 (None, BTreeMap::new())
63 };
64 Self { object, link_references, immutable_references: bytecode.immutable_references }
65 }
66}
67
68#[derive(Debug)]
70pub struct ContractData {
71 pub name: String,
73 pub abi: JsonAbi,
75 pub bytecode: Option<BytecodeData>,
77 pub deployed_bytecode: Option<BytecodeData>,
79 pub storage_layout: Option<Arc<StorageLayout>>,
81}
82
83impl ContractData {
84 pub fn bytecode(&self) -> Option<&Bytes> {
86 self.bytecode.as_ref()?.bytes().filter(|b| !b.is_empty())
87 }
88
89 pub fn deployed_bytecode(&self) -> Option<&Bytes> {
91 self.deployed_bytecode.as_ref()?.bytes().filter(|b| !b.is_empty())
92 }
93
94 pub fn bytecode_without_placeholders(&self) -> Option<Bytes> {
96 strip_bytecode_placeholders(self.bytecode.as_ref()?.object.as_ref()?)
97 }
98
99 pub fn deployed_bytecode_without_placeholders(&self) -> Option<Bytes> {
101 strip_bytecode_placeholders(self.deployed_bytecode.as_ref()?.object.as_ref()?)
102 }
103}
104
105pub struct ContractsByArtifactBuilder<'a> {
108 artifacts: BTreeMap<ArtifactId, CompactContractBytecodeCow<'a>>,
110 storage_layouts: BTreeMap<ArtifactId, StorageLayout>,
112}
113
114impl<'a> ContractsByArtifactBuilder<'a> {
115 pub fn new(
117 artifacts: impl IntoIterator<Item = (ArtifactId, CompactContractBytecodeCow<'a>)>,
118 ) -> Self {
119 Self { artifacts: artifacts.into_iter().collect(), storage_layouts: BTreeMap::new() }
120 }
121
122 pub fn with_output(self, output: &ProjectCompileOutput, base: &Path) -> Self {
124 self.with_storage_layouts(output.artifact_ids().filter_map(|(id, artifact)| {
125 artifact
126 .storage_layout
127 .as_ref()
128 .map(|layout| (id.with_stripped_file_prefixes(base), layout.clone()))
129 }))
130 }
131
132 pub fn with_storage_layouts(
134 mut self,
135 layouts: impl IntoIterator<Item = (ArtifactId, StorageLayout)>,
136 ) -> Self {
137 self.storage_layouts.extend(layouts);
138 self
139 }
140
141 pub fn build(self) -> ContractsByArtifact {
143 let map = self
144 .artifacts
145 .into_iter()
146 .filter_map(|(id, artifact)| {
147 let name = id.name.clone();
148 let CompactContractBytecodeCow { abi, bytecode, deployed_bytecode } = artifact;
149
150 Some((
151 id.clone(),
152 ContractData {
153 name,
154 abi: abi?.into_owned(),
155 bytecode: bytecode.map(|b| b.into_owned().into()),
156 deployed_bytecode: deployed_bytecode.map(|b| b.into_owned().into()),
157 storage_layout: self.storage_layouts.get(&id).map(|l| Arc::new(l.clone())),
158 },
159 ))
160 })
161 .collect();
162
163 ContractsByArtifact(Arc::new(map))
164 }
165}
166
167type ArtifactWithContractRef<'a> = (&'a ArtifactId, &'a ContractData);
168
169#[derive(Clone, Default, Debug)]
171pub struct ContractsByArtifact(Arc<BTreeMap<ArtifactId, ContractData>>);
172
173impl ContractsByArtifact {
174 pub fn new(artifacts: impl IntoIterator<Item = (ArtifactId, CompactContractBytecode)>) -> Self {
176 let map = artifacts
177 .into_iter()
178 .filter_map(|(id, artifact)| {
179 let name = id.name.clone();
180 let CompactContractBytecode { abi, bytecode, deployed_bytecode } = artifact;
181 Some((
182 id,
183 ContractData {
184 name,
185 abi: abi?,
186 bytecode: bytecode.map(Into::into),
187 deployed_bytecode: deployed_bytecode.map(Into::into),
188 storage_layout: None,
189 },
190 ))
191 })
192 .collect();
193 Self(Arc::new(map))
194 }
195
196 pub fn clear(&mut self) {
198 *self = Self::default();
199 }
200
201 pub fn find_by_creation_code(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
203 self.find_by_code(code, 0.1, true, ContractData::bytecode)
204 }
205
206 pub fn find_by_deployed_code(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
208 self.find_by_code(code, 0.15, false, ContractData::deployed_bytecode)
209 }
210
211 fn find_by_code(
214 &self,
215 code: &[u8],
216 accepted_score: f64,
217 strip_ctor_args: bool,
218 get: impl Fn(&ContractData) -> Option<&Bytes>,
219 ) -> Option<ArtifactWithContractRef<'_>> {
220 self.iter()
221 .filter_map(|(id, contract)| {
222 if let Some(deployed_bytecode) = get(contract) {
223 let mut code = code;
224 if strip_ctor_args && code.len() > deployed_bytecode.len() {
225 if let Some(constructor) = contract.abi.constructor() {
227 let constructor_args = &code[deployed_bytecode.len()..];
228 if constructor.abi_decode_input(constructor_args).is_ok() {
229 code = &code[..deployed_bytecode.len()]
232 }
233 }
234 };
235
236 let score = bytecode_diff_score(deployed_bytecode.as_ref(), code);
237 (score <= accepted_score).then_some((score, (id, contract)))
238 } else {
239 None
240 }
241 })
242 .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2))
243 .map(|(_, data)| data)
244 }
245
246 pub fn find_by_deployed_code_exact(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
249 if code.is_empty() {
251 return None;
252 }
253
254 let mut partial_match = None;
255 self.iter()
256 .find(|(id, contract)| {
257 let Some(deployed_bytecode) = &contract.deployed_bytecode else {
258 return false;
259 };
260 let Some(deployed_code) = &deployed_bytecode.object else {
261 return false;
262 };
263
264 let len = match deployed_code {
265 BytecodeObject::Bytecode(bytes) => bytes.len(),
266 BytecodeObject::Unlinked(bytes) => bytes.len() / 2,
267 };
268
269 if len != code.len() {
270 return false;
271 }
272
273 let mut ignored = deployed_bytecode
275 .immutable_references
276 .values()
277 .chain(deployed_bytecode.link_references.values().flat_map(|v| v.values()))
278 .flatten()
279 .cloned()
280 .collect::<Vec<_>>();
281
282 let has_call_protection = match deployed_code {
287 BytecodeObject::Bytecode(bytes) => {
288 bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX)
289 }
290 BytecodeObject::Unlinked(bytes) => {
291 if let Ok(bytes) =
292 Bytes::from_str(&bytes[..CALL_PROTECTION_BYTECODE_PREFIX.len() * 2])
293 {
294 bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX)
295 } else {
296 false
297 }
298 }
299 };
300
301 if has_call_protection {
302 ignored.push(Offsets { start: 1, length: 20 });
303 }
304
305 let metadata_start = find_metadata_start(code);
306
307 if let Some(metadata) = metadata_start {
308 ignored.push(Offsets {
309 start: metadata as u32,
310 length: (code.len() - metadata) as u32,
311 });
312 }
313
314 ignored.sort_by_key(|o| o.start);
315
316 let mut left = 0;
317 for offset in ignored {
318 let right = offset.start as usize;
319
320 let matched = match deployed_code {
321 BytecodeObject::Bytecode(bytes) => bytes[left..right] == code[left..right],
322 BytecodeObject::Unlinked(bytes) => {
323 if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..right * 2]) {
324 bytes == code[left..right]
325 } else {
326 false
327 }
328 }
329 };
330
331 if !matched {
332 return false;
333 }
334
335 left = right + offset.length as usize;
336 }
337
338 let is_partial = if left < code.len() {
339 match deployed_code {
340 BytecodeObject::Bytecode(bytes) => bytes[left..] == code[left..],
341 BytecodeObject::Unlinked(bytes) => {
342 if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..]) {
343 bytes == code[left..]
344 } else {
345 false
346 }
347 }
348 }
349 } else {
350 true
351 };
352
353 if !is_partial {
354 return false;
355 }
356
357 let Some(metadata) = metadata_start else { return true };
358
359 let exact_match = match deployed_code {
360 BytecodeObject::Bytecode(bytes) => bytes[metadata..] == code[metadata..],
361 BytecodeObject::Unlinked(bytes) => {
362 if let Ok(bytes) = Bytes::from_str(&bytes[metadata * 2..]) {
363 bytes == code[metadata..]
364 } else {
365 false
366 }
367 }
368 };
369
370 if exact_match {
371 true
372 } else {
373 partial_match = Some((*id, *contract));
374 false
375 }
376 })
377 .or(partial_match)
378 }
379
380 pub fn find_by_name_or_identifier(
383 &self,
384 id: &str,
385 ) -> Result<Option<ArtifactWithContractRef<'_>>> {
386 let contracts = self
387 .iter()
388 .filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id)
389 .collect::<Vec<_>>();
390
391 if contracts.len() > 1 {
392 eyre::bail!("{id} has more than one implementation.");
393 }
394
395 Ok(contracts.first().copied())
396 }
397
398 pub fn find_abi_by_name_or_identifier(&self, id: &str) -> Option<JsonAbi> {
400 self.iter()
401 .find(|(artifact, _)| {
402 artifact.name.split(".").next().unwrap() == id || artifact.identifier() == id
403 })
404 .map(|(_, contract)| contract.abi.clone())
405 }
406
407 pub fn find_abi_by_name_or_src_path(&self, name_or_path: &str) -> Option<(JsonAbi, String)> {
411 self.iter()
412 .find(|(artifact, _)| {
413 artifact.name == name_or_path || artifact.source == Path::new(name_or_path)
414 })
415 .map(|(_, contract)| (contract.abi.clone(), contract.name.clone()))
416 }
417
418 pub fn flatten(&self) -> (BTreeMap<Selector, Function>, BTreeMap<B256, Event>, JsonAbi) {
420 let mut funcs = BTreeMap::new();
421 let mut events = BTreeMap::new();
422 let mut errors_abi = JsonAbi::new();
423 for (_name, contract) in self.iter() {
424 for func in contract.abi.functions() {
425 funcs.insert(func.selector(), func.clone());
426 }
427 for event in contract.abi.events() {
428 events.insert(event.selector(), event.clone());
429 }
430 for error in contract.abi.errors() {
431 errors_abi.errors.entry(error.name.clone()).or_default().push(error.clone());
432 }
433 }
434 (funcs, events, errors_abi)
435 }
436}
437
438impl From<ProjectCompileOutput> for ContractsByArtifact {
439 fn from(value: ProjectCompileOutput) -> Self {
440 Self::new(value.into_artifacts().map(|(id, ar)| {
441 (
442 id,
443 CompactContractBytecode {
444 abi: ar.abi,
445 bytecode: ar.bytecode,
446 deployed_bytecode: ar.deployed_bytecode,
447 },
448 )
449 }))
450 }
451}
452
453impl Deref for ContractsByArtifact {
454 type Target = BTreeMap<ArtifactId, ContractData>;
455
456 fn deref(&self) -> &Self::Target {
457 &self.0
458 }
459}
460
461pub type ContractsByAddress = BTreeMap<Address, (String, JsonAbi)>;
463
464pub fn bytecode_diff_score<'a>(mut a: &'a [u8], mut b: &'a [u8]) -> f64 {
468 if a.len() < b.len() {
470 std::mem::swap(&mut a, &mut b);
471 }
472
473 let mut n_different_bytes = a.len() - b.len();
475
476 if n_different_bytes > 32 && n_different_bytes * 10 > a.len() {
481 return 1.0;
482 }
483
484 n_different_bytes += unsafe { count_different_bytes(a, b) };
487
488 n_different_bytes as f64 / a.len() as f64
489}
490
491unsafe fn count_different_bytes(a: &[u8], b: &[u8]) -> usize {
497 let a_ptr = a.as_ptr();
502 let b_ptr = b.as_ptr();
503 let len = b.len();
504
505 let mut sum = 0;
506 let mut i = 0;
507 while i < len {
508 sum += unsafe { *a_ptr.add(i) != *b_ptr.add(i) } as usize;
510 i += 1;
511 }
512 sum
513}
514
515pub fn get_contract_name(id: &str) -> &str {
534 id.rsplit(':').next().unwrap_or(id)
535}
536
537pub fn get_file_name(id: &str) -> &str {
549 id.split(':').next().unwrap_or(id)
550}
551
552pub fn compact_to_contract(contract: CompactContractBytecode) -> Result<ContractBytecodeSome> {
554 Ok(ContractBytecodeSome {
555 abi: contract.abi.ok_or_else(|| eyre::eyre!("No contract abi"))?,
556 bytecode: contract.bytecode.ok_or_else(|| eyre::eyre!("No contract bytecode"))?.into(),
557 deployed_bytecode: contract
558 .deployed_bytecode
559 .ok_or_else(|| eyre::eyre!("No contract deployed bytecode"))?
560 .into(),
561 })
562}
563
564pub fn find_target_path(project: &Project, identifier: &PathOrContractInfo) -> Result<PathBuf> {
566 match identifier {
567 PathOrContractInfo::Path(path) => Ok(canonicalized(project.root().join(path))),
568 PathOrContractInfo::ContractInfo(info) => {
569 if let Some(path) = info.path.as_ref() {
570 let path = canonicalized(project.root().join(path));
571 let sources = project.sources()?;
572 let contract_path = sources
573 .iter()
574 .find_map(|(src_path, _)| {
575 if **src_path == path {
576 return Some(src_path.clone());
577 }
578 None
579 })
580 .ok_or_else(|| {
581 eyre::eyre!(
582 "Could not find source file for contract `{}` at {}",
583 info.name,
584 path.strip_prefix(project.root()).unwrap().display()
585 )
586 })?;
587 return Ok(contract_path);
588 }
589 let path = project.find_contract_path(&info.name)?;
593 Ok(path)
594 }
595 }
596}
597
598pub fn find_matching_contract_artifact(
600 output: &mut ProjectCompileOutput,
601 target_path: &Path,
602 target_name: Option<&str>,
603) -> eyre::Result<ConfigurableContractArtifact> {
604 if let Some(name) = target_name {
605 output
606 .remove(target_path, name)
607 .ok_or_eyre(format!("Could not find artifact `{name}` in the compiled artifacts"))
608 } else {
609 let possible_targets = output
610 .artifact_ids()
611 .filter(|(id, _artifact)| id.source == target_path)
612 .collect::<Vec<_>>();
613
614 if possible_targets.is_empty() {
615 eyre::bail!(
616 "Could not find artifact linked to source `{target_path:?}` in the compiled artifacts"
617 );
618 }
619
620 let (target_id, target_artifact) = possible_targets[0].clone();
621 if possible_targets.len() == 1 {
622 return Ok(target_artifact.clone());
623 }
624
625 if !target_id.name.contains(".")
629 && possible_targets.iter().any(|(id, _)| id.name != target_id.name)
630 {
631 eyre::bail!(
632 "Multiple contracts found in the same file, please specify the target <path>:<contract> or <contract>"
633 );
634 }
635
636 let artifact = possible_targets
639 .iter()
640 .find_map(|(id, artifact)| if id.profile == "default" { Some(*artifact) } else { None })
641 .unwrap_or(target_artifact);
642
643 Ok(artifact.clone())
644 }
645}
646
647#[cfg(test)]
648mod tests {
649 use super::*;
650
651 #[test]
652 fn bytecode_diffing() {
653 assert_eq!(bytecode_diff_score(b"a", b"a"), 0.0);
654 assert_eq!(bytecode_diff_score(b"a", b"b"), 1.0);
655
656 let a_100 = &b"a".repeat(100)[..];
657 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(100)), 1.0);
658 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(99)), 1.0);
659 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(101)), 1.0);
660 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(120)), 1.0);
661 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(1000)), 1.0);
662
663 let a_99 = &b"a".repeat(99)[..];
664 assert!(bytecode_diff_score(a_100, a_99) <= 0.01);
665 }
666
667 #[test]
668 fn find_by_deployed_code_exact_with_empty_deployed() {
669 let contracts = ContractsByArtifact::new(vec![]);
670
671 assert!(contracts.find_by_deployed_code_exact(&[]).is_none());
672 }
673}