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_preferred(code, |_| true)
276    }
277
278    /// Same as [`Self::find_by_deployed_code_exact`], preferring artifacts matching `preferred`
279    /// only among equally strong matches. Metadata-exact matches always take precedence.
280    pub fn find_by_deployed_code_exact_preferred(
281        &self,
282        code: &[u8],
283        preferred: impl Fn(&ArtifactId) -> bool,
284    ) -> Option<ArtifactWithContractRef<'_>> {
285        self.find_by_deployed_code_exact_inner(code, false, preferred).0
286    }
287
288    /// Finds the only contract whose deployed bytecode exactly matches the given code.
289    pub fn find_by_deployed_code_exact_unique(
290        &self,
291        code: &[u8],
292    ) -> Option<ArtifactWithContractRef<'_>> {
293        self.find_by_deployed_code_exact_inner(code, true, |_| false).0
294    }
295
296    /// Finds a deployed-code match, preferring exact metadata matches and rejecting ambiguous
297    /// partial metadata matches. A single partial match is accepted.
298    pub fn find_by_deployed_code_exact_unambiguous(
299        &self,
300        code: &[u8],
301    ) -> Result<Option<ArtifactWithContractRef<'_>>> {
302        let (matched, ambiguous_partial) =
303            self.find_by_deployed_code_exact_inner(code, false, |_| true);
304        if ambiguous_partial {
305            eyre::bail!(
306                "Multiple local contracts match the deployed bytecode with different metadata. Specify the contract as <path>:<contract>"
307            );
308        }
309        Ok(matched)
310    }
311
312    fn find_by_deployed_code_exact_inner(
313        &self,
314        code: &[u8],
315        unique: bool,
316        preferred: impl Fn(&ArtifactId) -> bool,
317    ) -> (Option<ArtifactWithContractRef<'_>>, bool) {
318        // Immediately return None if the code is empty.
319        if code.is_empty() {
320            return (None, false);
321        }
322
323        let mut partial_match = None;
324        let mut exact_match = None;
325        let mut ambiguous_partial = false;
326        'contracts: for (id, contract) in self.iter() {
327            let Some(deployed_bytecode) = &contract.deployed_bytecode else {
328                continue;
329            };
330            let Some(deployed_code) = &deployed_bytecode.object else {
331                continue;
332            };
333
334            let len = match deployed_code {
335                BytecodeObject::Bytecode(bytes) => bytes.len(),
336                BytecodeObject::Unlinked(bytes) => bytes.len() / 2,
337            };
338
339            // Vyper artifacts contain the complete runtime, but omit the immutable data
340            // appended on deployment. Compare that runtime in full, without treating any of
341            // its bytes (or the immutable suffix) as Solidity metadata.
342            let is_vyper = id.source.extension().is_some_and(|extension| extension == "vy");
343            let code = if is_vyper {
344                let Some(runtime) = code.get(..len) else { continue };
345                runtime
346            } else {
347                code
348            };
349            if len == 0 || len != code.len() {
350                continue;
351            }
352
353            // Collect ignored offsets by chaining link and immutable references.
354            let mut ignored = deployed_bytecode
355                .immutable_references
356                .values()
357                .chain(deployed_bytecode.link_references.values().flat_map(|v| v.values()))
358                .flatten()
359                .cloned()
360                .collect::<Vec<_>>();
361
362            // For libraries solidity adds a call protection prefix to the bytecode. We need to
363            // ignore it as it includes library address determined at runtime.
364            // See https://docs.soliditylang.org/en/latest/contracts.html#call-protection-for-libraries and
365            // https://github.com/NomicFoundation/hardhat/blob/af7807cf38842a4f56e7f4b966b806e39631568a/packages/hardhat-verify/src/internal/solc/bytecode.ts#L172
366            let has_call_protection = !is_vyper
367                && match deployed_code {
368                    BytecodeObject::Bytecode(bytes) => {
369                        bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX)
370                    }
371                    BytecodeObject::Unlinked(bytes) => {
372                        if let Ok(bytes) =
373                            Bytes::from_str(&bytes[..CALL_PROTECTION_BYTECODE_PREFIX.len() * 2])
374                        {
375                            bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX)
376                        } else {
377                            false
378                        }
379                    }
380                };
381
382            if has_call_protection {
383                ignored.push(Offsets { start: 1, length: 20 });
384            }
385
386            let metadata_start = if is_vyper { None } else { find_metadata_start(code) };
387
388            if let Some(metadata) = metadata_start {
389                ignored.push(Offsets {
390                    start: metadata as u32,
391                    length: (code.len() - metadata) as u32,
392                });
393            }
394
395            // Merge ranges from independent sources before slicing between them.
396            normalize_offsets(&mut ignored);
397
398            let mut left = 0;
399            for offset in ignored {
400                let right = offset.start as usize;
401
402                let matched = match deployed_code {
403                    BytecodeObject::Bytecode(bytes) => bytes[left..right] == code[left..right],
404                    BytecodeObject::Unlinked(bytes) => {
405                        if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..right * 2]) {
406                            bytes == code[left..right]
407                        } else {
408                            false
409                        }
410                    }
411                };
412
413                if !matched {
414                    continue 'contracts;
415                }
416
417                left = right + offset.length as usize;
418            }
419
420            let is_partial = if left < code.len() {
421                match deployed_code {
422                    BytecodeObject::Bytecode(bytes) => bytes[left..] == code[left..],
423                    BytecodeObject::Unlinked(bytes) => {
424                        if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..]) {
425                            bytes == code[left..]
426                        } else {
427                            false
428                        }
429                    }
430                }
431            } else {
432                true
433            };
434
435            if !is_partial {
436                continue;
437            }
438
439            let matches_metadata = metadata_start.is_none_or(|metadata| match deployed_code {
440                BytecodeObject::Bytecode(bytes) => bytes[metadata..] == code[metadata..],
441                BytecodeObject::Unlinked(bytes) => Bytes::from_str(&bytes[metadata * 2..])
442                    .is_ok_and(|bytes| bytes == code[metadata..]),
443            });
444
445            if matches_metadata {
446                if unique && exact_match.is_some() {
447                    return (None, false);
448                }
449                if exact_match.is_none() || preferred(id) {
450                    exact_match = Some((id, contract));
451                }
452                if !unique && preferred(id) {
453                    return (exact_match, false);
454                }
455            } else {
456                ambiguous_partial |= partial_match.is_some();
457                if preferred(id) || partial_match.is_none_or(|(id, _)| !preferred(id)) {
458                    partial_match = Some((id, contract));
459                }
460            }
461        }
462
463        let ambiguous_partial = exact_match.is_none() && ambiguous_partial;
464        (if unique { exact_match } else { exact_match.or(partial_match) }, ambiguous_partial)
465    }
466
467    /// Finds a contract which has the same contract name or identifier as `id`. If more than one is
468    /// found, return error.
469    pub fn find_by_name_or_identifier(
470        &self,
471        id: &str,
472    ) -> Result<Option<ArtifactWithContractRef<'_>>> {
473        let mut iter =
474            self.iter().filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id);
475        let first = iter.next();
476        if first.is_some() && iter.next().is_some() {
477            eyre::bail!("{id} has more than one implementation.");
478        }
479
480        Ok(first)
481    }
482
483    /// Finds abi by name or source path
484    ///
485    /// Returns the abi and the contract name.
486    pub fn find_abi_by_name_or_src_path(&self, name_or_path: &str) -> Option<(JsonAbi, String)> {
487        self.iter()
488            .find(|(artifact, _)| {
489                artifact.name == name_or_path || artifact.source == Path::new(name_or_path)
490            })
491            .map(|(_, contract)| (contract.abi.clone(), contract.name.clone()))
492    }
493
494    /// Flattens the contracts into functions, events and errors.
495    pub fn flatten(&self) -> (BTreeMap<Selector, Function>, BTreeMap<B256, Event>, JsonAbi) {
496        let mut funcs = BTreeMap::new();
497        let mut events = BTreeMap::new();
498        let mut errors_abi = JsonAbi::new();
499        for contract in self.values() {
500            for func in contract.abi.functions() {
501                funcs.insert(func.selector(), func.clone());
502            }
503            for event in contract.abi.events() {
504                events.insert(event.selector(), event.clone());
505            }
506            for error in contract.abi.errors() {
507                errors_abi.errors.entry(error.name.clone()).or_default().push(error.clone());
508            }
509        }
510        (funcs, events, errors_abi)
511    }
512}
513
514impl From<ProjectCompileOutput> for ContractsByArtifact {
515    fn from(value: ProjectCompileOutput) -> Self {
516        Self::new(value.into_artifacts().map(|(id, ar)| {
517            (
518                id,
519                CompactContractBytecode {
520                    abi: ar.abi,
521                    bytecode: ar.bytecode,
522                    deployed_bytecode: ar.deployed_bytecode,
523                },
524            )
525        }))
526    }
527}
528
529impl Deref for ContractsByArtifact {
530    type Target = BTreeMap<ArtifactId, ContractData>;
531
532    fn deref(&self) -> &Self::Target {
533        &self.0
534    }
535}
536
537/// Wrapper type that maps an address to a contract identifier and contract ABI.
538pub type ContractsByAddress = BTreeMap<Address, (String, JsonAbi)>;
539
540/// Very simple fuzzy matching of contract bytecode.
541///
542/// Returns a value between `0.0` (identical) and `1.0` (completely different).
543/// Returns whether `creation_code` is exactly this contract's linked creation bytecode followed by
544/// a complete, canonically encoded constructor argument tuple.
545pub fn matches_contract_creation(contract: &ContractData, creation_code: &[u8]) -> bool {
546    let Some(bytecode) = contract.bytecode() else { return false };
547    let Some(arguments) = creation_code.strip_prefix(bytecode.as_ref()) else { return false };
548    match contract.abi.constructor() {
549        Some(constructor) => constructor
550            .abi_decode_input(arguments)
551            .ok()
552            .and_then(|values| constructor.abi_encode_input(&values).ok())
553            .is_some_and(|encoded| encoded == arguments),
554        None => arguments.is_empty(),
555    }
556}
557
558pub fn bytecode_diff_score<'a>(mut a: &'a [u8], mut b: &'a [u8]) -> f64 {
559    // Make sure `a` is the longer one.
560    if a.len() < b.len() {
561        std::mem::swap(&mut a, &mut b);
562    }
563
564    // Account for different lengths.
565    let mut n_different_bytes = a.len() - b.len();
566
567    // If the difference is more than 32 bytes and more than 10% of the total length,
568    // we assume the bytecodes are completely different.
569    // This is a simple heuristic to avoid checking every byte when the lengths are very different.
570    // 32 is chosen to be a reasonable minimum as it's the size of metadata hashes and one EVM word.
571    if n_different_bytes > 32 && n_different_bytes * 10 > a.len() {
572        return 1.0;
573    }
574
575    // Count different bytes.
576    // SAFETY: `a` is longer than `b`.
577    n_different_bytes += unsafe { count_different_bytes(a, b) };
578
579    n_different_bytes as f64 / a.len() as f64
580}
581
582/// Returns the amount of different bytes between two slices.
583///
584/// # Safety
585///
586/// `a` must be at least as long as `b`.
587const unsafe fn count_different_bytes(a: &[u8], b: &[u8]) -> usize {
588    // This could've been written as `std::iter::zip(a, b).filter(|(x, y)| x != y).count()`,
589    // however this function is very hot, and has been written to be as primitive as
590    // possible for lower optimization levels.
591
592    let a_ptr = a.as_ptr();
593    let b_ptr = b.as_ptr();
594    let len = b.len();
595
596    let mut sum = 0;
597    let mut i = 0;
598    while i < len {
599        // SAFETY: `a` is at least as long as `b`, and `i` is in bound of `b`.
600        sum += unsafe { *a_ptr.add(i) != *b_ptr.add(i) } as usize;
601        i += 1;
602    }
603    sum
604}
605
606/// Returns contract name for a given contract identifier.
607///
608/// Artifact/Contract identifier can take the following form:
609/// `<artifact file name>:<contract name>`, the `artifact file name` is the name of the json file of
610/// the contract's artifact and the contract name is the name of the solidity contract, like
611/// `SafeTransferLibTest.json:SafeTransferLibTest`
612///
613/// This returns the `contract name` part
614///
615/// # Example
616///
617/// ```
618/// use foundry_common::*;
619/// assert_eq!(
620///     "SafeTransferLibTest",
621///     get_contract_name("SafeTransferLibTest.json:SafeTransferLibTest")
622/// );
623/// ```
624pub fn get_contract_name(id: &str) -> &str {
625    id.rsplit(':').next().unwrap_or(id)
626}
627
628/// This returns the `file name` part, See [`get_contract_name`]
629///
630/// # Example
631///
632/// ```
633/// use foundry_common::*;
634/// assert_eq!(
635///     "SafeTransferLibTest.json",
636///     get_file_name("SafeTransferLibTest.json:SafeTransferLibTest")
637/// );
638/// ```
639pub fn get_file_name(id: &str) -> &str {
640    id.split(':').next().unwrap_or(id)
641}
642
643/// Helper function to convert CompactContractBytecode ~> ContractBytecodeSome
644pub fn compact_to_contract(contract: CompactContractBytecode) -> Result<ContractBytecodeSome> {
645    Ok(ContractBytecodeSome {
646        abi: contract.abi.ok_or_else(|| eyre::eyre!("No contract abi"))?,
647        bytecode: contract.bytecode.ok_or_else(|| eyre::eyre!("No contract bytecode"))?.into(),
648        deployed_bytecode: contract
649            .deployed_bytecode
650            .ok_or_else(|| eyre::eyre!("No contract deployed bytecode"))?
651            .into(),
652    })
653}
654
655/// Returns the canonicalized target path for the given identifier.
656pub fn find_target_path(project: &Project, identifier: &PathOrContractInfo) -> Result<PathBuf> {
657    match identifier {
658        PathOrContractInfo::Path(path) => Ok(canonicalized(project.root().join(path))),
659        PathOrContractInfo::ContractInfo(info) => {
660            if let Some(path) = info.path.as_ref() {
661                let path = canonicalized(project.root().join(path));
662                if !path.is_file() {
663                    eyre::bail!(
664                        "Could not find source file for contract `{}` at {}",
665                        info.name,
666                        path.strip_prefix(project.root()).unwrap_or(&path).display()
667                    );
668                }
669                return Ok(path);
670            }
671            // If ContractInfo.path hasn't been provided we try to find the contract using the name.
672            // This will fail if projects have multiple contracts with the same name. In that case,
673            // path must be specified.
674            let path = project.find_contract_path(&info.name)?;
675            Ok(path)
676        }
677    }
678}
679
680/// Returns the target artifact given the path and name.
681pub fn find_matching_contract_artifact(
682    output: &mut ProjectCompileOutput,
683    target_path: &Path,
684    target_name: Option<&str>,
685) -> eyre::Result<ConfigurableContractArtifact> {
686    if let Some(name) = target_name {
687        if let Some(artifact) = output.remove(target_path, name) {
688            return Ok(artifact);
689        }
690
691        let target_path = canonicalized(target_path);
692        let matching_source = output.artifact_ids().find_map(|(id, _artifact)| {
693            (id.name == name && canonicalized(&id.source) == target_path).then(|| id.source.clone())
694        });
695
696        matching_source
697            .and_then(|source| output.remove(&source, name))
698            .ok_or_eyre(format!("Could not find artifact `{name}` in the compiled artifacts"))
699    } else {
700        let possible_targets = output
701            .artifact_ids()
702            .filter(|(id, _artifact)| id.source == target_path)
703            .collect::<Vec<_>>();
704
705        if possible_targets.is_empty() {
706            eyre::bail!(
707                "Could not find artifact linked to source `{target_path:?}` in the compiled artifacts"
708            );
709        }
710
711        let (target_id, target_artifact) = possible_targets[0].clone();
712        if possible_targets.len() == 1 {
713            return Ok(target_artifact.clone());
714        }
715
716        // If all artifact_ids in `possible_targets` have the same name (without ".", indicates
717        // additional compiler profiles), it means that there are multiple contracts in the
718        // same file.
719        if !target_id.name.contains('.')
720            && possible_targets.iter().any(|(id, _)| id.name != target_id.name)
721        {
722            eyre::bail!(
723                "Multiple contracts found in the same file, please specify the target <path>:<contract> or <contract>"
724            );
725        }
726
727        // Otherwise, we're dealing with additional compiler profiles wherein `id.source` is the
728        // same but `id.path` is different.
729        let artifact = possible_targets
730            .iter()
731            .find_map(|(id, artifact)| (id.profile == "default").then_some(*artifact))
732            .unwrap_or(target_artifact);
733
734        Ok(artifact.clone())
735    }
736}
737
738#[cfg(test)]
739mod tests {
740    use super::*;
741    use alloy_dyn_abi::DynSolValue;
742    use alloy_primitives::U256;
743    use semver::Version;
744
745    fn deployed_artifact(name: &str, code: Bytes) -> (ArtifactId, CompactContractBytecode) {
746        deployed_artifact_with_immutables(name, code, Default::default())
747    }
748
749    fn deployed_artifact_with_immutables(
750        name: &str,
751        code: Bytes,
752        immutable_references: BTreeMap<String, Vec<Offsets>>,
753    ) -> (ArtifactId, CompactContractBytecode) {
754        (
755            ArtifactId {
756                path: format!("out/{name}.json").into(),
757                name: name.to_owned(),
758                source: format!("src/{name}.sol").into(),
759                version: Version::new(0, 8, 30),
760                build_id: String::new(),
761                profile: "default".to_owned(),
762            },
763            CompactContractBytecode {
764                abi: Some(Default::default()),
765                bytecode: None,
766                deployed_bytecode: Some(CompactDeployedBytecode {
767                    bytecode: Some(CompactBytecode {
768                        object: BytecodeObject::Bytecode(code),
769                        source_map: None,
770                        link_references: Default::default(),
771                    }),
772                    immutable_references,
773                }),
774            },
775        )
776    }
777
778    #[test]
779    fn exact_creation_match_requires_canonical_constructor_suffix() {
780        let abi = JsonAbi::parse(["constructor(uint256 value)"]).unwrap();
781        let bytecode = Bytes::from_static(&[0x60, 0x00]);
782        let contract = ContractData {
783            name: "C".to_owned(),
784            abi,
785            bytecode: Some(BytecodeData {
786                object: Some(BytecodeObject::Bytecode(bytecode.clone())),
787                link_references: BTreeMap::new(),
788                immutable_references: BTreeMap::new(),
789            }),
790            deployed_bytecode: None,
791            storage_layout: None,
792        };
793        let arguments = contract
794            .abi
795            .constructor()
796            .unwrap()
797            .abi_encode_input(&[DynSolValue::Uint(U256::ONE, 256)])
798            .unwrap();
799        let creation = [bytecode.as_ref(), &arguments].concat();
800
801        assert!(matches_contract_creation(&contract, &creation));
802        assert!(!matches_contract_creation(&contract, &creation[..creation.len() - 1]));
803        assert!(!matches_contract_creation(&contract, &[creation, vec![0]].concat()));
804    }
805
806    #[test]
807    fn bytecode_diffing() {
808        assert_eq!(bytecode_diff_score(b"a", b"a"), 0.0);
809        assert_eq!(bytecode_diff_score(b"a", b"b"), 1.0);
810
811        let a_100 = &b"a".repeat(100)[..];
812        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(100)), 1.0);
813        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(99)), 1.0);
814        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(101)), 1.0);
815        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(120)), 1.0);
816        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(1000)), 1.0);
817
818        let a_99 = &b"a".repeat(99)[..];
819        assert!(bytecode_diff_score(a_100, a_99) <= 0.01);
820    }
821
822    #[test]
823    fn find_by_deployed_code_exact_with_empty_deployed() {
824        let contracts = ContractsByArtifact::new(vec![]);
825
826        assert!(contracts.find_by_deployed_code_exact(&[]).is_none());
827    }
828
829    #[test]
830    fn find_by_deployed_code_exact_unique_rejects_ambiguity() {
831        let code = Bytes::from_static(&[0x60, 0x00]);
832        let contracts = ContractsByArtifact::new([
833            deployed_artifact("A", code.clone()),
834            deployed_artifact("B", code.clone()),
835        ]);
836
837        assert!(contracts.find_by_deployed_code_exact_unique(&code).is_none());
838
839        let contracts = ContractsByArtifact::new([deployed_artifact("A", code.clone())]);
840        assert_eq!(contracts.find_by_deployed_code_exact_unique(&code).unwrap().0.name, "A");
841    }
842
843    #[test]
844    fn find_by_deployed_code_exact_unique_rejects_partial_metadata_match() {
845        let artifact_code = Bytes::from_static(&[0x60, 0x00, 0xa0, 0x00, 0x01]);
846        let deployed_code = Bytes::from_static(&[0x60, 0x00, 0xf6, 0x00, 0x01]);
847        let contracts = ContractsByArtifact::new([deployed_artifact("A", artifact_code)]);
848
849        assert!(contracts.find_by_deployed_code_exact(&deployed_code).is_some());
850        assert!(contracts.find_by_deployed_code_exact_unique(&deployed_code).is_none());
851    }
852
853    #[test]
854    fn find_by_deployed_code_exact_unambiguous_preserves_single_partial() {
855        let artifact_code = Bytes::from_static(&[0x60, 0x00, 0xa0, 0x00, 0x01]);
856        let deployed_code = Bytes::from_static(&[0x60, 0x00, 0xf6, 0x00, 0x01]);
857        let contracts = ContractsByArtifact::new([deployed_artifact("A", artifact_code)]);
858
859        assert_eq!(
860            contracts
861                .find_by_deployed_code_exact_unambiguous(&deployed_code)
862                .unwrap()
863                .unwrap()
864                .0
865                .name,
866            "A"
867        );
868        assert!(contracts.find_by_deployed_code_exact_unambiguous(&[0x00]).unwrap().is_none());
869    }
870
871    #[test]
872    fn find_by_deployed_code_exact_unambiguous_prefers_exact_over_ambiguous_partial() {
873        let partial_code = Bytes::from_static(&[0x60, 0x00, 0xa0, 0x00, 0x01]);
874        let deployed_code = Bytes::from_static(&[0x60, 0x00, 0xf6, 0x00, 0x01]);
875        for exact_name in ["A", "Z"] {
876            let contracts = ContractsByArtifact::new([
877                deployed_artifact("B", partial_code.clone()),
878                deployed_artifact("C", partial_code.clone()),
879            ]);
880            assert!(contracts.find_by_deployed_code_exact(&deployed_code).is_some());
881            assert!(contracts.find_by_deployed_code_exact_unique(&deployed_code).is_none());
882            assert_eq!(
883                contracts
884                    .find_by_deployed_code_exact_unambiguous(&deployed_code)
885                    .unwrap_err()
886                    .to_string(),
887                "Multiple local contracts match the deployed bytecode with different metadata. Specify the contract as <path>:<contract>"
888            );
889
890            let contracts = ContractsByArtifact::new([
891                deployed_artifact(exact_name, deployed_code.clone()),
892                deployed_artifact("B", partial_code.clone()),
893                deployed_artifact("C", partial_code.clone()),
894            ]);
895            assert_eq!(
896                contracts
897                    .find_by_deployed_code_exact_unambiguous(&deployed_code)
898                    .unwrap()
899                    .unwrap()
900                    .0
901                    .name,
902                exact_name
903            );
904        }
905    }
906
907    /// Tests an immutable reference within a library call-protection prefix.
908    #[test]
909    fn find_by_deployed_code_exact_handles_overlapping_ignored_ranges() {
910        // Call-protection prefix covering [1, 21).
911        let mut code = vec![0x73u8];
912        code.extend(std::iter::repeat_n(0u8, 20));
913        // Bytes matched after the ignored range.
914        code.extend_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD]);
915        let code = Bytes::from(code);
916
917        // Immutable reference [5, 8) lies within [1, 21).
918        let immutable_references =
919            BTreeMap::from([("someImmutable".to_string(), vec![Offsets { start: 5, length: 3 }])]);
920
921        let contracts = ContractsByArtifact::new([deployed_artifact_with_immutables(
922            "LibWithEarlyImmutable",
923            code.clone(),
924            immutable_references,
925        )]);
926
927        // The overlap must not panic or prevent a match.
928        assert!(contracts.find_by_deployed_code_exact(&code).is_some());
929    }
930
931    #[test]
932    fn normalize_offsets_merges_overlaps_and_adjacency() {
933        // Contained overlap.
934        let mut offsets = vec![Offsets { start: 1, length: 20 }, Offsets { start: 5, length: 3 }];
935        normalize_offsets(&mut offsets);
936        assert_eq!(offsets, vec![Offsets { start: 1, length: 20 }]);
937
938        // Extending overlap.
939        let mut offsets = vec![Offsets { start: 1, length: 20 }, Offsets { start: 15, length: 15 }];
940        normalize_offsets(&mut offsets);
941        assert_eq!(offsets, vec![Offsets { start: 1, length: 29 }]);
942
943        // Adjacent ranges.
944        let mut offsets = vec![Offsets { start: 1, length: 20 }, Offsets { start: 21, length: 4 }];
945        normalize_offsets(&mut offsets);
946        assert_eq!(offsets, vec![Offsets { start: 1, length: 24 }]);
947
948        // Disjoint ranges.
949        let mut offsets = vec![Offsets { start: 1, length: 5 }, Offsets { start: 10, length: 5 }];
950        normalize_offsets(&mut offsets);
951        assert_eq!(
952            offsets,
953            vec![Offsets { start: 1, length: 5 }, Offsets { start: 10, length: 5 }]
954        );
955
956        // Unsorted ranges.
957        let mut offsets = vec![Offsets { start: 10, length: 5 }, Offsets { start: 1, length: 5 }];
958        normalize_offsets(&mut offsets);
959        assert_eq!(
960            offsets,
961            vec![Offsets { start: 1, length: 5 }, Offsets { start: 10, length: 5 }]
962        );
963    }
964}