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