Skip to main content

foundry_common/
contracts.rs

1//! Commonly used contract types and functions.
2
3use 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, address, 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
25/// Libraries' runtime code always starts with the following instruction:
26/// `PUSH20 0x0000000000000000000000000000000000000000`
27///
28/// See: <https://docs.soliditylang.org/en/latest/contracts.html#call-protection-for-libraries>
29const CALL_PROTECTION_BYTECODE_PREFIX: [u8; 21] =
30    hex!("730000000000000000000000000000000000000000");
31
32/// Isolated account used to deploy libraries needed only while executing locally.
33///
34/// `address(uint160(uint256(keccak256("foundry library deployer"))))`
35pub const LIBRARY_DEPLOYER: Address = address!("0x1F95D37F27EA0dEA9C252FC09D5A6eaA97647353");
36
37/// Subset of [CompactBytecode] excluding sourcemaps.
38#[expect(missing_docs)]
39#[derive(Debug, Clone)]
40pub struct BytecodeData {
41    pub object: Option<BytecodeObject>,
42    pub link_references: BTreeMap<String, BTreeMap<String, Vec<Offsets>>>,
43    pub immutable_references: BTreeMap<String, Vec<Offsets>>,
44}
45
46impl BytecodeData {
47    fn bytes(&self) -> Option<&Bytes> {
48        self.object.as_ref().and_then(|b| b.as_bytes())
49    }
50}
51
52/// Sorts and merges overlapping or adjacent byte ranges.
53fn normalize_offsets(offsets: &mut Vec<Offsets>) {
54    offsets.sort_by_key(|o| o.start);
55
56    let mut merged: Vec<Offsets> = Vec::with_capacity(offsets.len());
57    for offset in offsets.drain(..) {
58        if let Some(last) = merged.last_mut() {
59            let last_end = last.start as u64 + last.length as u64;
60            if offset.start as u64 <= last_end {
61                let this_end = offset.start as u64 + offset.length as u64;
62                if this_end > last_end {
63                    last.length = (this_end - last.start as u64) as u32;
64                }
65                continue;
66            }
67        }
68        merged.push(offset);
69    }
70    *offsets = merged;
71}
72
73impl From<CompactBytecode> for BytecodeData {
74    fn from(bytecode: CompactBytecode) -> Self {
75        Self {
76            object: Some(bytecode.object),
77            link_references: bytecode.link_references,
78            immutable_references: BTreeMap::new(),
79        }
80    }
81}
82
83impl From<CompactDeployedBytecode> for BytecodeData {
84    fn from(bytecode: CompactDeployedBytecode) -> Self {
85        let (object, link_references) = if let Some(compact) = bytecode.bytecode {
86            (Some(compact.object), compact.link_references)
87        } else {
88            (None, BTreeMap::new())
89        };
90        Self { object, link_references, immutable_references: bytecode.immutable_references }
91    }
92}
93
94/// Container for commonly used contract data.
95#[derive(Debug)]
96pub struct ContractData {
97    /// Contract name.
98    pub name: String,
99    /// Contract ABI.
100    pub abi: JsonAbi,
101    /// Contract creation code.
102    pub bytecode: Option<BytecodeData>,
103    /// Contract runtime code.
104    pub deployed_bytecode: Option<BytecodeData>,
105    /// Contract storage layout, if available.
106    pub storage_layout: Option<Arc<StorageLayout>>,
107}
108
109impl ContractData {
110    /// Returns reference to bytes of contract creation code, if present.
111    pub fn bytecode(&self) -> Option<&Bytes> {
112        self.bytecode.as_ref()?.bytes().filter(|b| !b.is_empty())
113    }
114
115    /// Returns reference to bytes of contract deployed code, if present.
116    pub fn deployed_bytecode(&self) -> Option<&Bytes> {
117        self.deployed_bytecode.as_ref()?.bytes().filter(|b| !b.is_empty())
118    }
119
120    /// Returns the bytecode without placeholders, if present.
121    pub fn bytecode_without_placeholders(&self) -> Option<Bytes> {
122        strip_bytecode_placeholders(self.bytecode.as_ref()?.object.as_ref()?)
123    }
124
125    /// Returns the deployed bytecode without placeholders, if present.
126    pub fn deployed_bytecode_without_placeholders(&self) -> Option<Bytes> {
127        strip_bytecode_placeholders(self.deployed_bytecode.as_ref()?.object.as_ref()?)
128    }
129}
130
131/// Builder for creating a `ContractsByArtifact` instance, optionally including storage layouts
132/// from project compile output.
133pub struct ContractsByArtifactBuilder<'a> {
134    /// All compiled artifact bytecodes (borrowed).
135    artifacts: BTreeMap<ArtifactId, CompactContractBytecodeCow<'a>>,
136    /// Optionally collected storage layouts for matching artifact IDs.
137    storage_layouts: BTreeMap<ArtifactId, StorageLayout>,
138}
139
140impl<'a> ContractsByArtifactBuilder<'a> {
141    /// Creates a new builder from artifacts with present bytecode iterator.
142    pub fn new(
143        artifacts: impl IntoIterator<Item = (ArtifactId, CompactContractBytecodeCow<'a>)>,
144    ) -> Self {
145        Self { artifacts: artifacts.into_iter().collect(), storage_layouts: BTreeMap::new() }
146    }
147
148    /// Add storage layouts from the given `ProjectCompileOutput` to known artifacts.
149    pub fn with_output(self, output: &ProjectCompileOutput, base: &Path) -> Self {
150        self.with_storage_layouts(output.artifact_ids().filter_map(|(id, artifact)| {
151            artifact
152                .storage_layout
153                .as_ref()
154                .map(|layout| (id.with_stripped_file_prefixes(base), layout.clone()))
155        }))
156    }
157
158    /// Add storage layouts.
159    pub fn with_storage_layouts(
160        mut self,
161        layouts: impl IntoIterator<Item = (ArtifactId, StorageLayout)>,
162    ) -> Self {
163        self.storage_layouts.extend(layouts);
164        self
165    }
166
167    /// Builds `ContractsByArtifact`.
168    pub fn build(self) -> ContractsByArtifact {
169        let map = self
170            .artifacts
171            .into_iter()
172            .filter_map(|(id, artifact)| {
173                let name = id.name.clone();
174                let CompactContractBytecodeCow { abi, bytecode, deployed_bytecode } = artifact;
175
176                Some((
177                    id.clone(),
178                    ContractData {
179                        name,
180                        abi: abi?.into_owned(),
181                        bytecode: bytecode.map(|b| b.into_owned().into()),
182                        deployed_bytecode: deployed_bytecode.map(|b| b.into_owned().into()),
183                        storage_layout: self.storage_layouts.get(&id).map(|l| Arc::new(l.clone())),
184                    },
185                ))
186            })
187            .collect();
188
189        ContractsByArtifact(Arc::new(map))
190    }
191}
192
193type ArtifactWithContractRef<'a> = (&'a ArtifactId, &'a ContractData);
194
195/// Wrapper type that maps an artifact to a contract ABI and bytecode.
196#[derive(Clone, Default, Debug)]
197pub struct ContractsByArtifact(Arc<BTreeMap<ArtifactId, ContractData>>);
198
199impl ContractsByArtifact {
200    /// Creates a new instance by collecting all artifacts with present bytecode from an iterator.
201    pub fn new(artifacts: impl IntoIterator<Item = (ArtifactId, CompactContractBytecode)>) -> Self {
202        let map = artifacts
203            .into_iter()
204            .filter_map(|(id, artifact)| {
205                let name = id.name.clone();
206                let CompactContractBytecode { abi, bytecode, deployed_bytecode } = artifact;
207                Some((
208                    id,
209                    ContractData {
210                        name,
211                        abi: abi?,
212                        bytecode: bytecode.map(Into::into),
213                        deployed_bytecode: deployed_bytecode.map(Into::into),
214                        storage_layout: None,
215                    },
216                ))
217            })
218            .collect();
219        Self(Arc::new(map))
220    }
221
222    /// Clears all contracts.
223    pub fn clear(&mut self) {
224        *self = Self::default();
225    }
226
227    /// Finds a contract which has a similar bytecode as `code`.
228    pub fn find_by_creation_code(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
229        self.find_by_code(code, 0.1, true, ContractData::bytecode)
230    }
231
232    /// Finds a contract which has a similar deployed bytecode as `code`.
233    pub fn find_by_deployed_code(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
234        self.find_by_code(code, 0.15, false, ContractData::deployed_bytecode)
235    }
236
237    /// Finds a contract based on provided bytecode and accepted match score.
238    /// If strip constructor args flag is true then removes args from bytecode to compare.
239    fn find_by_code(
240        &self,
241        code: &[u8],
242        accepted_score: f64,
243        strip_ctor_args: bool,
244        get: impl Fn(&ContractData) -> Option<&Bytes>,
245    ) -> Option<ArtifactWithContractRef<'_>> {
246        self.iter()
247            .filter_map(|(id, contract)| {
248                if let Some(deployed_bytecode) = get(contract) {
249                    let mut code = code;
250                    if strip_ctor_args && code.len() > deployed_bytecode.len() {
251                        // Try to decode ctor args with contract abi.
252                        if let Some(constructor) = contract.abi.constructor() {
253                            let constructor_args = &code[deployed_bytecode.len()..];
254                            if constructor.abi_decode_input(constructor_args).is_ok() {
255                                // If we can decode args with current abi then remove args from
256                                // code to compare.
257                                code = &code[..deployed_bytecode.len()]
258                            }
259                        }
260                    };
261
262                    let score = bytecode_diff_score(deployed_bytecode.as_ref(), code);
263                    (score <= accepted_score).then_some((score, (id, contract)))
264                } else {
265                    None
266                }
267            })
268            .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2))
269            .map(|(_, data)| data)
270    }
271
272    /// Finds a contract which deployed bytecode exactly matches the given code. Accounts for link
273    /// references and immutables.
274    pub fn find_by_deployed_code_exact(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
275        self.find_by_deployed_code_exact_inner(code, false)
276    }
277
278    /// Finds the only contract whose deployed bytecode exactly matches the given code.
279    pub fn find_by_deployed_code_exact_unique(
280        &self,
281        code: &[u8],
282    ) -> Option<ArtifactWithContractRef<'_>> {
283        self.find_by_deployed_code_exact_inner(code, true)
284    }
285
286    fn find_by_deployed_code_exact_inner(
287        &self,
288        code: &[u8],
289        unique: bool,
290    ) -> Option<ArtifactWithContractRef<'_>> {
291        // Immediately return None if the code is empty.
292        if code.is_empty() {
293            return None;
294        }
295
296        let mut partial_match = None;
297        let mut unique_match = None;
298        let matched = self.iter().find(|(id, contract)| {
299            let Some(deployed_bytecode) = &contract.deployed_bytecode else {
300                return false;
301            };
302            let Some(deployed_code) = &deployed_bytecode.object else {
303                return false;
304            };
305
306            let len = match deployed_code {
307                BytecodeObject::Bytecode(bytes) => bytes.len(),
308                BytecodeObject::Unlinked(bytes) => bytes.len() / 2,
309            };
310
311            if len != code.len() {
312                return false;
313            }
314
315            // Collect ignored offsets by chaining link and immutable references.
316            let mut ignored = deployed_bytecode
317                .immutable_references
318                .values()
319                .chain(deployed_bytecode.link_references.values().flat_map(|v| v.values()))
320                .flatten()
321                .cloned()
322                .collect::<Vec<_>>();
323
324            // For libraries solidity adds a call protection prefix to the bytecode. We need to
325            // ignore it as it includes library address determined at runtime.
326            // See https://docs.soliditylang.org/en/latest/contracts.html#call-protection-for-libraries and
327            // https://github.com/NomicFoundation/hardhat/blob/af7807cf38842a4f56e7f4b966b806e39631568a/packages/hardhat-verify/src/internal/solc/bytecode.ts#L172
328            let has_call_protection = match deployed_code {
329                BytecodeObject::Bytecode(bytes) => {
330                    bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX)
331                }
332                BytecodeObject::Unlinked(bytes) => {
333                    if let Ok(bytes) =
334                        Bytes::from_str(&bytes[..CALL_PROTECTION_BYTECODE_PREFIX.len() * 2])
335                    {
336                        bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX)
337                    } else {
338                        false
339                    }
340                }
341            };
342
343            if has_call_protection {
344                ignored.push(Offsets { start: 1, length: 20 });
345            }
346
347            let metadata_start = find_metadata_start(code);
348
349            if let Some(metadata) = metadata_start {
350                ignored.push(Offsets {
351                    start: metadata as u32,
352                    length: (code.len() - metadata) as u32,
353                });
354            }
355
356            // Merge ranges from independent sources before slicing between them.
357            normalize_offsets(&mut ignored);
358
359            let mut left = 0;
360            for offset in ignored {
361                let right = offset.start as usize;
362
363                let matched = match deployed_code {
364                    BytecodeObject::Bytecode(bytes) => bytes[left..right] == code[left..right],
365                    BytecodeObject::Unlinked(bytes) => {
366                        if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..right * 2]) {
367                            bytes == code[left..right]
368                        } else {
369                            false
370                        }
371                    }
372                };
373
374                if !matched {
375                    return false;
376                }
377
378                left = right + offset.length as usize;
379            }
380
381            let is_partial = if left < code.len() {
382                match deployed_code {
383                    BytecodeObject::Bytecode(bytes) => bytes[left..] == code[left..],
384                    BytecodeObject::Unlinked(bytes) => {
385                        if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..]) {
386                            bytes == code[left..]
387                        } else {
388                            false
389                        }
390                    }
391                }
392            } else {
393                true
394            };
395
396            if !is_partial {
397                return false;
398            }
399
400            let Some(metadata) = metadata_start else {
401                if unique && unique_match.is_none() {
402                    unique_match = Some((*id, *contract));
403                    return false;
404                }
405                return true;
406            };
407
408            let exact_match = match deployed_code {
409                BytecodeObject::Bytecode(bytes) => bytes[metadata..] == code[metadata..],
410                BytecodeObject::Unlinked(bytes) => {
411                    if let Ok(bytes) = Bytes::from_str(&bytes[metadata * 2..]) {
412                        bytes == code[metadata..]
413                    } else {
414                        false
415                    }
416                }
417            };
418
419            if exact_match {
420                if unique && unique_match.is_none() {
421                    unique_match = Some((*id, *contract));
422                    false
423                } else {
424                    true
425                }
426            } else {
427                partial_match = Some((*id, *contract));
428                false
429            }
430        });
431
432        if unique {
433            matched.is_none().then_some(unique_match).flatten()
434        } else {
435            matched.or(partial_match)
436        }
437    }
438
439    /// Finds a contract which has the same contract name or identifier as `id`. If more than one is
440    /// found, return error.
441    pub fn find_by_name_or_identifier(
442        &self,
443        id: &str,
444    ) -> Result<Option<ArtifactWithContractRef<'_>>> {
445        let mut iter =
446            self.iter().filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id);
447        let first = iter.next();
448        if first.is_some() && iter.next().is_some() {
449            eyre::bail!("{id} has more than one implementation.");
450        }
451
452        Ok(first)
453    }
454
455    /// Finds abi by name or source path
456    ///
457    /// Returns the abi and the contract name.
458    pub fn find_abi_by_name_or_src_path(&self, name_or_path: &str) -> Option<(JsonAbi, String)> {
459        self.iter()
460            .find(|(artifact, _)| {
461                artifact.name == name_or_path || artifact.source == Path::new(name_or_path)
462            })
463            .map(|(_, contract)| (contract.abi.clone(), contract.name.clone()))
464    }
465
466    /// Flattens the contracts into functions, events and errors.
467    pub fn flatten(&self) -> (BTreeMap<Selector, Function>, BTreeMap<B256, Event>, JsonAbi) {
468        let mut funcs = BTreeMap::new();
469        let mut events = BTreeMap::new();
470        let mut errors_abi = JsonAbi::new();
471        for contract in self.values() {
472            for func in contract.abi.functions() {
473                funcs.insert(func.selector(), func.clone());
474            }
475            for event in contract.abi.events() {
476                events.insert(event.selector(), event.clone());
477            }
478            for error in contract.abi.errors() {
479                errors_abi.errors.entry(error.name.clone()).or_default().push(error.clone());
480            }
481        }
482        (funcs, events, errors_abi)
483    }
484}
485
486impl From<ProjectCompileOutput> for ContractsByArtifact {
487    fn from(value: ProjectCompileOutput) -> Self {
488        Self::new(value.into_artifacts().map(|(id, ar)| {
489            (
490                id,
491                CompactContractBytecode {
492                    abi: ar.abi,
493                    bytecode: ar.bytecode,
494                    deployed_bytecode: ar.deployed_bytecode,
495                },
496            )
497        }))
498    }
499}
500
501impl Deref for ContractsByArtifact {
502    type Target = BTreeMap<ArtifactId, ContractData>;
503
504    fn deref(&self) -> &Self::Target {
505        &self.0
506    }
507}
508
509/// Wrapper type that maps an address to a contract identifier and contract ABI.
510pub type ContractsByAddress = BTreeMap<Address, (String, JsonAbi)>;
511
512/// Very simple fuzzy matching of contract bytecode.
513///
514/// Returns a value between `0.0` (identical) and `1.0` (completely different).
515/// Returns whether `creation_code` is exactly this contract's linked creation bytecode followed by
516/// a complete, canonically encoded constructor argument tuple.
517pub fn matches_contract_creation(contract: &ContractData, creation_code: &[u8]) -> bool {
518    let Some(bytecode) = contract.bytecode() else { return false };
519    let Some(arguments) = creation_code.strip_prefix(bytecode.as_ref()) else { return false };
520    match contract.abi.constructor() {
521        Some(constructor) => constructor
522            .abi_decode_input(arguments)
523            .ok()
524            .and_then(|values| constructor.abi_encode_input(&values).ok())
525            .is_some_and(|encoded| encoded == arguments),
526        None => arguments.is_empty(),
527    }
528}
529
530pub fn bytecode_diff_score<'a>(mut a: &'a [u8], mut b: &'a [u8]) -> f64 {
531    // Make sure `a` is the longer one.
532    if a.len() < b.len() {
533        std::mem::swap(&mut a, &mut b);
534    }
535
536    // Account for different lengths.
537    let mut n_different_bytes = a.len() - b.len();
538
539    // If the difference is more than 32 bytes and more than 10% of the total length,
540    // we assume the bytecodes are completely different.
541    // This is a simple heuristic to avoid checking every byte when the lengths are very different.
542    // 32 is chosen to be a reasonable minimum as it's the size of metadata hashes and one EVM word.
543    if n_different_bytes > 32 && n_different_bytes * 10 > a.len() {
544        return 1.0;
545    }
546
547    // Count different bytes.
548    // SAFETY: `a` is longer than `b`.
549    n_different_bytes += unsafe { count_different_bytes(a, b) };
550
551    n_different_bytes as f64 / a.len() as f64
552}
553
554/// Returns the amount of different bytes between two slices.
555///
556/// # Safety
557///
558/// `a` must be at least as long as `b`.
559const unsafe fn count_different_bytes(a: &[u8], b: &[u8]) -> usize {
560    // This could've been written as `std::iter::zip(a, b).filter(|(x, y)| x != y).count()`,
561    // however this function is very hot, and has been written to be as primitive as
562    // possible for lower optimization levels.
563
564    let a_ptr = a.as_ptr();
565    let b_ptr = b.as_ptr();
566    let len = b.len();
567
568    let mut sum = 0;
569    let mut i = 0;
570    while i < len {
571        // SAFETY: `a` is at least as long as `b`, and `i` is in bound of `b`.
572        sum += unsafe { *a_ptr.add(i) != *b_ptr.add(i) } as usize;
573        i += 1;
574    }
575    sum
576}
577
578/// Returns contract name for a given contract identifier.
579///
580/// Artifact/Contract identifier can take the following form:
581/// `<artifact file name>:<contract name>`, the `artifact file name` is the name of the json file of
582/// the contract's artifact and the contract name is the name of the solidity contract, like
583/// `SafeTransferLibTest.json:SafeTransferLibTest`
584///
585/// This returns the `contract name` part
586///
587/// # Example
588///
589/// ```
590/// use foundry_common::*;
591/// assert_eq!(
592///     "SafeTransferLibTest",
593///     get_contract_name("SafeTransferLibTest.json:SafeTransferLibTest")
594/// );
595/// ```
596pub fn get_contract_name(id: &str) -> &str {
597    id.rsplit(':').next().unwrap_or(id)
598}
599
600/// This returns the `file name` part, See [`get_contract_name`]
601///
602/// # Example
603///
604/// ```
605/// use foundry_common::*;
606/// assert_eq!(
607///     "SafeTransferLibTest.json",
608///     get_file_name("SafeTransferLibTest.json:SafeTransferLibTest")
609/// );
610/// ```
611pub fn get_file_name(id: &str) -> &str {
612    id.split(':').next().unwrap_or(id)
613}
614
615/// Helper function to convert CompactContractBytecode ~> ContractBytecodeSome
616pub fn compact_to_contract(contract: CompactContractBytecode) -> Result<ContractBytecodeSome> {
617    Ok(ContractBytecodeSome {
618        abi: contract.abi.ok_or_else(|| eyre::eyre!("No contract abi"))?,
619        bytecode: contract.bytecode.ok_or_else(|| eyre::eyre!("No contract bytecode"))?.into(),
620        deployed_bytecode: contract
621            .deployed_bytecode
622            .ok_or_else(|| eyre::eyre!("No contract deployed bytecode"))?
623            .into(),
624    })
625}
626
627/// Returns the canonicalized target path for the given identifier.
628pub fn find_target_path(project: &Project, identifier: &PathOrContractInfo) -> Result<PathBuf> {
629    match identifier {
630        PathOrContractInfo::Path(path) => Ok(canonicalized(project.root().join(path))),
631        PathOrContractInfo::ContractInfo(info) => {
632            if let Some(path) = info.path.as_ref() {
633                let path = canonicalized(project.root().join(path));
634                if !path.is_file() {
635                    eyre::bail!(
636                        "Could not find source file for contract `{}` at {}",
637                        info.name,
638                        path.strip_prefix(project.root()).unwrap_or(&path).display()
639                    );
640                }
641                return Ok(path);
642            }
643            // If ContractInfo.path hasn't been provided we try to find the contract using the name.
644            // This will fail if projects have multiple contracts with the same name. In that case,
645            // path must be specified.
646            let path = project.find_contract_path(&info.name)?;
647            Ok(path)
648        }
649    }
650}
651
652/// Returns the target artifact given the path and name.
653pub fn find_matching_contract_artifact(
654    output: &mut ProjectCompileOutput,
655    target_path: &Path,
656    target_name: Option<&str>,
657) -> eyre::Result<ConfigurableContractArtifact> {
658    if let Some(name) = target_name {
659        if let Some(artifact) = output.remove(target_path, name) {
660            return Ok(artifact);
661        }
662
663        let target_path = canonicalized(target_path);
664        let matching_source = output.artifact_ids().find_map(|(id, _artifact)| {
665            (id.name == name && canonicalized(&id.source) == target_path).then(|| id.source.clone())
666        });
667
668        matching_source
669            .and_then(|source| output.remove(&source, name))
670            .ok_or_eyre(format!("Could not find artifact `{name}` in the compiled artifacts"))
671    } else {
672        let possible_targets = output
673            .artifact_ids()
674            .filter(|(id, _artifact)| id.source == target_path)
675            .collect::<Vec<_>>();
676
677        if possible_targets.is_empty() {
678            eyre::bail!(
679                "Could not find artifact linked to source `{target_path:?}` in the compiled artifacts"
680            );
681        }
682
683        let (target_id, target_artifact) = possible_targets[0].clone();
684        if possible_targets.len() == 1 {
685            return Ok(target_artifact.clone());
686        }
687
688        // If all artifact_ids in `possible_targets` have the same name (without ".", indicates
689        // additional compiler profiles), it means that there are multiple contracts in the
690        // same file.
691        if !target_id.name.contains('.')
692            && possible_targets.iter().any(|(id, _)| id.name != target_id.name)
693        {
694            eyre::bail!(
695                "Multiple contracts found in the same file, please specify the target <path>:<contract> or <contract>"
696            );
697        }
698
699        // Otherwise, we're dealing with additional compiler profiles wherein `id.source` is the
700        // same but `id.path` is different.
701        let artifact = possible_targets
702            .iter()
703            .find_map(|(id, artifact)| (id.profile == "default").then_some(*artifact))
704            .unwrap_or(target_artifact);
705
706        Ok(artifact.clone())
707    }
708}
709
710#[cfg(test)]
711mod tests {
712    use super::*;
713    use alloy_dyn_abi::DynSolValue;
714    use alloy_primitives::U256;
715    use semver::Version;
716
717    fn deployed_artifact(name: &str, code: Bytes) -> (ArtifactId, CompactContractBytecode) {
718        deployed_artifact_with_immutables(name, code, Default::default())
719    }
720
721    fn deployed_artifact_with_immutables(
722        name: &str,
723        code: Bytes,
724        immutable_references: BTreeMap<String, Vec<Offsets>>,
725    ) -> (ArtifactId, CompactContractBytecode) {
726        (
727            ArtifactId {
728                path: format!("out/{name}.json").into(),
729                name: name.to_owned(),
730                source: format!("src/{name}.sol").into(),
731                version: Version::new(0, 8, 30),
732                build_id: String::new(),
733                profile: "default".to_owned(),
734            },
735            CompactContractBytecode {
736                abi: Some(Default::default()),
737                bytecode: None,
738                deployed_bytecode: Some(CompactDeployedBytecode {
739                    bytecode: Some(CompactBytecode {
740                        object: BytecodeObject::Bytecode(code),
741                        source_map: None,
742                        link_references: Default::default(),
743                    }),
744                    immutable_references,
745                }),
746            },
747        )
748    }
749
750    #[test]
751    fn exact_creation_match_requires_canonical_constructor_suffix() {
752        let abi = JsonAbi::parse(["constructor(uint256 value)"]).unwrap();
753        let bytecode = Bytes::from_static(&[0x60, 0x00]);
754        let contract = ContractData {
755            name: "C".to_owned(),
756            abi,
757            bytecode: Some(BytecodeData {
758                object: Some(BytecodeObject::Bytecode(bytecode.clone())),
759                link_references: BTreeMap::new(),
760                immutable_references: BTreeMap::new(),
761            }),
762            deployed_bytecode: None,
763            storage_layout: None,
764        };
765        let arguments = contract
766            .abi
767            .constructor()
768            .unwrap()
769            .abi_encode_input(&[DynSolValue::Uint(U256::from(1), 256)])
770            .unwrap();
771        let creation = [bytecode.as_ref(), &arguments].concat();
772
773        assert!(matches_contract_creation(&contract, &creation));
774        assert!(!matches_contract_creation(&contract, &creation[..creation.len() - 1]));
775        assert!(!matches_contract_creation(&contract, &[creation, vec![0]].concat()));
776    }
777
778    #[test]
779    fn bytecode_diffing() {
780        assert_eq!(bytecode_diff_score(b"a", b"a"), 0.0);
781        assert_eq!(bytecode_diff_score(b"a", b"b"), 1.0);
782
783        let a_100 = &b"a".repeat(100)[..];
784        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(100)), 1.0);
785        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(99)), 1.0);
786        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(101)), 1.0);
787        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(120)), 1.0);
788        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(1000)), 1.0);
789
790        let a_99 = &b"a".repeat(99)[..];
791        assert!(bytecode_diff_score(a_100, a_99) <= 0.01);
792    }
793
794    #[test]
795    fn find_by_deployed_code_exact_with_empty_deployed() {
796        let contracts = ContractsByArtifact::new(vec![]);
797
798        assert!(contracts.find_by_deployed_code_exact(&[]).is_none());
799    }
800
801    #[test]
802    fn find_by_deployed_code_exact_unique_rejects_ambiguity() {
803        let code = Bytes::from_static(&[0x60, 0x00]);
804        let contracts = ContractsByArtifact::new([
805            deployed_artifact("A", code.clone()),
806            deployed_artifact("B", code.clone()),
807        ]);
808
809        assert!(contracts.find_by_deployed_code_exact_unique(&code).is_none());
810
811        let contracts = ContractsByArtifact::new([deployed_artifact("A", code.clone())]);
812        assert_eq!(contracts.find_by_deployed_code_exact_unique(&code).unwrap().0.name, "A");
813    }
814
815    #[test]
816    fn find_by_deployed_code_exact_unique_rejects_partial_metadata_match() {
817        let artifact_code = Bytes::from_static(&[0x60, 0x00, 0xa0, 0x00, 0x01]);
818        let deployed_code = Bytes::from_static(&[0x60, 0x00, 0xf6, 0x00, 0x01]);
819        let contracts = ContractsByArtifact::new([deployed_artifact("A", artifact_code)]);
820
821        assert!(contracts.find_by_deployed_code_exact(&deployed_code).is_some());
822        assert!(contracts.find_by_deployed_code_exact_unique(&deployed_code).is_none());
823    }
824
825    /// Tests an immutable reference within a library call-protection prefix.
826    #[test]
827    fn find_by_deployed_code_exact_handles_overlapping_ignored_ranges() {
828        // Call-protection prefix covering [1, 21).
829        let mut code = vec![0x73u8];
830        code.extend(std::iter::repeat_n(0u8, 20));
831        // Bytes matched after the ignored range.
832        code.extend_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD]);
833        let code = Bytes::from(code);
834
835        // Immutable reference [5, 8) lies within [1, 21).
836        let immutable_references =
837            BTreeMap::from([("someImmutable".to_string(), vec![Offsets { start: 5, length: 3 }])]);
838
839        let contracts = ContractsByArtifact::new([deployed_artifact_with_immutables(
840            "LibWithEarlyImmutable",
841            code.clone(),
842            immutable_references,
843        )]);
844
845        // The overlap must not panic or prevent a match.
846        assert!(contracts.find_by_deployed_code_exact(&code).is_some());
847    }
848
849    #[test]
850    fn normalize_offsets_merges_overlaps_and_adjacency() {
851        // Contained overlap.
852        let mut offsets = vec![Offsets { start: 1, length: 20 }, Offsets { start: 5, length: 3 }];
853        normalize_offsets(&mut offsets);
854        assert_eq!(offsets, vec![Offsets { start: 1, length: 20 }]);
855
856        // Extending overlap.
857        let mut offsets = vec![Offsets { start: 1, length: 20 }, Offsets { start: 15, length: 15 }];
858        normalize_offsets(&mut offsets);
859        assert_eq!(offsets, vec![Offsets { start: 1, length: 29 }]);
860
861        // Adjacent ranges.
862        let mut offsets = vec![Offsets { start: 1, length: 20 }, Offsets { start: 21, length: 4 }];
863        normalize_offsets(&mut offsets);
864        assert_eq!(offsets, vec![Offsets { start: 1, length: 24 }]);
865
866        // Disjoint ranges.
867        let mut offsets = vec![Offsets { start: 1, length: 5 }, Offsets { start: 10, length: 5 }];
868        normalize_offsets(&mut offsets);
869        assert_eq!(
870            offsets,
871            vec![Offsets { start: 1, length: 5 }, Offsets { start: 10, length: 5 }]
872        );
873
874        // Unsorted ranges.
875        let mut offsets = vec![Offsets { start: 10, length: 5 }, Offsets { start: 1, length: 5 }];
876        normalize_offsets(&mut offsets);
877        assert_eq!(
878            offsets,
879            vec![Offsets { start: 1, length: 5 }, Offsets { start: 10, length: 5 }]
880        );
881    }
882}