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_src_path(&self, name_or_path: &str) -> Option<(JsonAbi, String)> {
402 self.iter()
403 .find(|(artifact, _)| {
404 artifact.name == name_or_path || artifact.source == Path::new(name_or_path)
405 })
406 .map(|(_, contract)| (contract.abi.clone(), contract.name.clone()))
407 }
408
409 pub fn flatten(&self) -> (BTreeMap<Selector, Function>, BTreeMap<B256, Event>, JsonAbi) {
411 let mut funcs = BTreeMap::new();
412 let mut events = BTreeMap::new();
413 let mut errors_abi = JsonAbi::new();
414 for (_name, contract) in self.iter() {
415 for func in contract.abi.functions() {
416 funcs.insert(func.selector(), func.clone());
417 }
418 for event in contract.abi.events() {
419 events.insert(event.selector(), event.clone());
420 }
421 for error in contract.abi.errors() {
422 errors_abi.errors.entry(error.name.clone()).or_default().push(error.clone());
423 }
424 }
425 (funcs, events, errors_abi)
426 }
427}
428
429impl From<ProjectCompileOutput> for ContractsByArtifact {
430 fn from(value: ProjectCompileOutput) -> Self {
431 Self::new(value.into_artifacts().map(|(id, ar)| {
432 (
433 id,
434 CompactContractBytecode {
435 abi: ar.abi,
436 bytecode: ar.bytecode,
437 deployed_bytecode: ar.deployed_bytecode,
438 },
439 )
440 }))
441 }
442}
443
444impl Deref for ContractsByArtifact {
445 type Target = BTreeMap<ArtifactId, ContractData>;
446
447 fn deref(&self) -> &Self::Target {
448 &self.0
449 }
450}
451
452pub type ContractsByAddress = BTreeMap<Address, (String, JsonAbi)>;
454
455pub fn bytecode_diff_score<'a>(mut a: &'a [u8], mut b: &'a [u8]) -> f64 {
459 if a.len() < b.len() {
461 std::mem::swap(&mut a, &mut b);
462 }
463
464 let mut n_different_bytes = a.len() - b.len();
466
467 if n_different_bytes > 32 && n_different_bytes * 10 > a.len() {
472 return 1.0;
473 }
474
475 n_different_bytes += unsafe { count_different_bytes(a, b) };
478
479 n_different_bytes as f64 / a.len() as f64
480}
481
482unsafe fn count_different_bytes(a: &[u8], b: &[u8]) -> usize {
488 let a_ptr = a.as_ptr();
493 let b_ptr = b.as_ptr();
494 let len = b.len();
495
496 let mut sum = 0;
497 let mut i = 0;
498 while i < len {
499 sum += unsafe { *a_ptr.add(i) != *b_ptr.add(i) } as usize;
501 i += 1;
502 }
503 sum
504}
505
506pub fn get_contract_name(id: &str) -> &str {
525 id.rsplit(':').next().unwrap_or(id)
526}
527
528pub fn get_file_name(id: &str) -> &str {
540 id.split(':').next().unwrap_or(id)
541}
542
543pub fn compact_to_contract(contract: CompactContractBytecode) -> Result<ContractBytecodeSome> {
545 Ok(ContractBytecodeSome {
546 abi: contract.abi.ok_or_else(|| eyre::eyre!("No contract abi"))?,
547 bytecode: contract.bytecode.ok_or_else(|| eyre::eyre!("No contract bytecode"))?.into(),
548 deployed_bytecode: contract
549 .deployed_bytecode
550 .ok_or_else(|| eyre::eyre!("No contract deployed bytecode"))?
551 .into(),
552 })
553}
554
555pub fn find_target_path(project: &Project, identifier: &PathOrContractInfo) -> Result<PathBuf> {
557 match identifier {
558 PathOrContractInfo::Path(path) => Ok(canonicalized(project.root().join(path))),
559 PathOrContractInfo::ContractInfo(info) => {
560 if let Some(path) = info.path.as_ref() {
561 let path = canonicalized(project.root().join(path));
562 let sources = project.sources()?;
563 let contract_path = sources
564 .iter()
565 .find_map(|(src_path, _)| {
566 if **src_path == path {
567 return Some(src_path.clone());
568 }
569 None
570 })
571 .ok_or_else(|| {
572 eyre::eyre!(
573 "Could not find source file for contract `{}` at {}",
574 info.name,
575 path.strip_prefix(project.root()).unwrap().display()
576 )
577 })?;
578 return Ok(contract_path);
579 }
580 let path = project.find_contract_path(&info.name)?;
584 Ok(path)
585 }
586 }
587}
588
589pub fn find_matching_contract_artifact(
591 output: &mut ProjectCompileOutput,
592 target_path: &Path,
593 target_name: Option<&str>,
594) -> eyre::Result<ConfigurableContractArtifact> {
595 if let Some(name) = target_name {
596 output
597 .remove(target_path, name)
598 .ok_or_eyre(format!("Could not find artifact `{name}` in the compiled artifacts"))
599 } else {
600 let possible_targets = output
601 .artifact_ids()
602 .filter(|(id, _artifact)| id.source == target_path)
603 .collect::<Vec<_>>();
604
605 if possible_targets.is_empty() {
606 eyre::bail!(
607 "Could not find artifact linked to source `{target_path:?}` in the compiled artifacts"
608 );
609 }
610
611 let (target_id, target_artifact) = possible_targets[0].clone();
612 if possible_targets.len() == 1 {
613 return Ok(target_artifact.clone());
614 }
615
616 if !target_id.name.contains(".")
620 && possible_targets.iter().any(|(id, _)| id.name != target_id.name)
621 {
622 eyre::bail!(
623 "Multiple contracts found in the same file, please specify the target <path>:<contract> or <contract>"
624 );
625 }
626
627 let artifact = possible_targets
630 .iter()
631 .find_map(|(id, artifact)| if id.profile == "default" { Some(*artifact) } else { None })
632 .unwrap_or(target_artifact);
633
634 Ok(artifact.clone())
635 }
636}
637
638#[cfg(test)]
639mod tests {
640 use super::*;
641
642 #[test]
643 fn bytecode_diffing() {
644 assert_eq!(bytecode_diff_score(b"a", b"a"), 0.0);
645 assert_eq!(bytecode_diff_score(b"a", b"b"), 1.0);
646
647 let a_100 = &b"a".repeat(100)[..];
648 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(100)), 1.0);
649 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(99)), 1.0);
650 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(101)), 1.0);
651 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(120)), 1.0);
652 assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(1000)), 1.0);
653
654 let a_99 = &b"a".repeat(99)[..];
655 assert!(bytecode_diff_score(a_100, a_99) <= 0.01);
656 }
657
658 #[test]
659 fn find_by_deployed_code_exact_with_empty_deployed() {
660 let contracts = ContractsByArtifact::new(vec![]);
661
662 assert!(contracts.find_by_deployed_code_exact(&[]).is_none());
663 }
664}