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