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, 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/// Subset of [CompactBytecode] excluding sourcemaps.
33#[expect(missing_docs)]
34#[derive(Debug, Clone)]
35pub struct BytecodeData {
36    pub object: Option<BytecodeObject>,
37    pub link_references: BTreeMap<String, BTreeMap<String, Vec<Offsets>>>,
38    pub immutable_references: BTreeMap<String, Vec<Offsets>>,
39}
40
41impl BytecodeData {
42    fn bytes(&self) -> Option<&Bytes> {
43        self.object.as_ref().and_then(|b| b.as_bytes())
44    }
45}
46
47impl From<CompactBytecode> for BytecodeData {
48    fn from(bytecode: CompactBytecode) -> Self {
49        Self {
50            object: Some(bytecode.object),
51            link_references: bytecode.link_references,
52            immutable_references: BTreeMap::new(),
53        }
54    }
55}
56
57impl From<CompactDeployedBytecode> for BytecodeData {
58    fn from(bytecode: CompactDeployedBytecode) -> Self {
59        let (object, link_references) = if let Some(compact) = bytecode.bytecode {
60            (Some(compact.object), compact.link_references)
61        } else {
62            (None, BTreeMap::new())
63        };
64        Self { object, link_references, immutable_references: bytecode.immutable_references }
65    }
66}
67
68/// Container for commonly used contract data.
69#[derive(Debug)]
70pub struct ContractData {
71    /// Contract name.
72    pub name: String,
73    /// Contract ABI.
74    pub abi: JsonAbi,
75    /// Contract creation code.
76    pub bytecode: Option<BytecodeData>,
77    /// Contract runtime code.
78    pub deployed_bytecode: Option<BytecodeData>,
79    /// Contract storage layout, if available.
80    pub storage_layout: Option<Arc<StorageLayout>>,
81}
82
83impl ContractData {
84    /// Returns reference to bytes of contract creation code, if present.
85    pub fn bytecode(&self) -> Option<&Bytes> {
86        self.bytecode.as_ref()?.bytes().filter(|b| !b.is_empty())
87    }
88
89    /// Returns reference to bytes of contract deployed code, if present.
90    pub fn deployed_bytecode(&self) -> Option<&Bytes> {
91        self.deployed_bytecode.as_ref()?.bytes().filter(|b| !b.is_empty())
92    }
93
94    /// Returns the bytecode without placeholders, if present.
95    pub fn bytecode_without_placeholders(&self) -> Option<Bytes> {
96        strip_bytecode_placeholders(self.bytecode.as_ref()?.object.as_ref()?)
97    }
98
99    /// Returns the deployed bytecode without placeholders, if present.
100    pub fn deployed_bytecode_without_placeholders(&self) -> Option<Bytes> {
101        strip_bytecode_placeholders(self.deployed_bytecode.as_ref()?.object.as_ref()?)
102    }
103}
104
105/// Builder for creating a `ContractsByArtifact` instance, optionally including storage layouts
106/// from project compile output.
107pub struct ContractsByArtifactBuilder<'a> {
108    /// All compiled artifact bytecodes (borrowed).
109    artifacts: BTreeMap<ArtifactId, CompactContractBytecodeCow<'a>>,
110    /// Optionally collected storage layouts for matching artifact IDs.
111    storage_layouts: BTreeMap<ArtifactId, StorageLayout>,
112}
113
114impl<'a> ContractsByArtifactBuilder<'a> {
115    /// Creates a new builder from artifacts with present bytecode iterator.
116    pub fn new(
117        artifacts: impl IntoIterator<Item = (ArtifactId, CompactContractBytecodeCow<'a>)>,
118    ) -> Self {
119        Self { artifacts: artifacts.into_iter().collect(), storage_layouts: BTreeMap::new() }
120    }
121
122    /// Add storage layouts from the given `ProjectCompileOutput` to known artifacts.
123    pub fn with_output(self, output: &ProjectCompileOutput, base: &Path) -> Self {
124        self.with_storage_layouts(output.artifact_ids().filter_map(|(id, artifact)| {
125            artifact
126                .storage_layout
127                .as_ref()
128                .map(|layout| (id.with_stripped_file_prefixes(base), layout.clone()))
129        }))
130    }
131
132    /// Add storage layouts.
133    pub fn with_storage_layouts(
134        mut self,
135        layouts: impl IntoIterator<Item = (ArtifactId, StorageLayout)>,
136    ) -> Self {
137        self.storage_layouts.extend(layouts);
138        self
139    }
140
141    /// Builds `ContractsByArtifact`.
142    pub fn build(self) -> ContractsByArtifact {
143        let map = self
144            .artifacts
145            .into_iter()
146            .filter_map(|(id, artifact)| {
147                let name = id.name.clone();
148                let CompactContractBytecodeCow { abi, bytecode, deployed_bytecode } = artifact;
149
150                Some((
151                    id.clone(),
152                    ContractData {
153                        name,
154                        abi: abi?.into_owned(),
155                        bytecode: bytecode.map(|b| b.into_owned().into()),
156                        deployed_bytecode: deployed_bytecode.map(|b| b.into_owned().into()),
157                        storage_layout: self.storage_layouts.get(&id).map(|l| Arc::new(l.clone())),
158                    },
159                ))
160            })
161            .collect();
162
163        ContractsByArtifact(Arc::new(map))
164    }
165}
166
167type ArtifactWithContractRef<'a> = (&'a ArtifactId, &'a ContractData);
168
169/// Wrapper type that maps an artifact to a contract ABI and bytecode.
170#[derive(Clone, Default, Debug)]
171pub struct ContractsByArtifact(Arc<BTreeMap<ArtifactId, ContractData>>);
172
173impl ContractsByArtifact {
174    /// Creates a new instance by collecting all artifacts with present bytecode from an iterator.
175    pub fn new(artifacts: impl IntoIterator<Item = (ArtifactId, CompactContractBytecode)>) -> Self {
176        let map = artifacts
177            .into_iter()
178            .filter_map(|(id, artifact)| {
179                let name = id.name.clone();
180                let CompactContractBytecode { abi, bytecode, deployed_bytecode } = artifact;
181                Some((
182                    id,
183                    ContractData {
184                        name,
185                        abi: abi?,
186                        bytecode: bytecode.map(Into::into),
187                        deployed_bytecode: deployed_bytecode.map(Into::into),
188                        storage_layout: None,
189                    },
190                ))
191            })
192            .collect();
193        Self(Arc::new(map))
194    }
195
196    /// Clears all contracts.
197    pub fn clear(&mut self) {
198        *self = Self::default();
199    }
200
201    /// Finds a contract which has a similar bytecode as `code`.
202    pub fn find_by_creation_code(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
203        self.find_by_code(code, 0.1, true, ContractData::bytecode)
204    }
205
206    /// Finds a contract which has a similar deployed bytecode as `code`.
207    pub fn find_by_deployed_code(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
208        self.find_by_code(code, 0.15, false, ContractData::deployed_bytecode)
209    }
210
211    /// Finds a contract based on provided bytecode and accepted match score.
212    /// If strip constructor args flag is true then removes args from bytecode to compare.
213    fn find_by_code(
214        &self,
215        code: &[u8],
216        accepted_score: f64,
217        strip_ctor_args: bool,
218        get: impl Fn(&ContractData) -> Option<&Bytes>,
219    ) -> Option<ArtifactWithContractRef<'_>> {
220        self.iter()
221            .filter_map(|(id, contract)| {
222                if let Some(deployed_bytecode) = get(contract) {
223                    let mut code = code;
224                    if strip_ctor_args && code.len() > deployed_bytecode.len() {
225                        // Try to decode ctor args with contract abi.
226                        if let Some(constructor) = contract.abi.constructor() {
227                            let constructor_args = &code[deployed_bytecode.len()..];
228                            if constructor.abi_decode_input(constructor_args).is_ok() {
229                                // If we can decode args with current abi then remove args from
230                                // code to compare.
231                                code = &code[..deployed_bytecode.len()]
232                            }
233                        }
234                    };
235
236                    let score = bytecode_diff_score(deployed_bytecode.as_ref(), code);
237                    (score <= accepted_score).then_some((score, (id, contract)))
238                } else {
239                    None
240                }
241            })
242            .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2))
243            .map(|(_, data)| data)
244    }
245
246    /// Finds a contract which deployed bytecode exactly matches the given code. Accounts for link
247    /// references and immutables.
248    pub fn find_by_deployed_code_exact(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
249        // Immediately return None if the code is empty.
250        if code.is_empty() {
251            return None;
252        }
253
254        let mut partial_match = None;
255        self.iter()
256            .find(|(id, contract)| {
257                let Some(deployed_bytecode) = &contract.deployed_bytecode else {
258                    return false;
259                };
260                let Some(deployed_code) = &deployed_bytecode.object else {
261                    return false;
262                };
263
264                let len = match deployed_code {
265                    BytecodeObject::Bytecode(bytes) => bytes.len(),
266                    BytecodeObject::Unlinked(bytes) => bytes.len() / 2,
267                };
268
269                if len != code.len() {
270                    return false;
271                }
272
273                // Collect ignored offsets by chaining link and immutable references.
274                let mut ignored = deployed_bytecode
275                    .immutable_references
276                    .values()
277                    .chain(deployed_bytecode.link_references.values().flat_map(|v| v.values()))
278                    .flatten()
279                    .cloned()
280                    .collect::<Vec<_>>();
281
282                // For libraries solidity adds a call protection prefix to the bytecode. We need to
283                // ignore it as it includes library address determined at runtime.
284                // See https://docs.soliditylang.org/en/latest/contracts.html#call-protection-for-libraries and
285                // https://github.com/NomicFoundation/hardhat/blob/af7807cf38842a4f56e7f4b966b806e39631568a/packages/hardhat-verify/src/internal/solc/bytecode.ts#L172
286                let has_call_protection = match deployed_code {
287                    BytecodeObject::Bytecode(bytes) => {
288                        bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX)
289                    }
290                    BytecodeObject::Unlinked(bytes) => {
291                        if let Ok(bytes) =
292                            Bytes::from_str(&bytes[..CALL_PROTECTION_BYTECODE_PREFIX.len() * 2])
293                        {
294                            bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX)
295                        } else {
296                            false
297                        }
298                    }
299                };
300
301                if has_call_protection {
302                    ignored.push(Offsets { start: 1, length: 20 });
303                }
304
305                let metadata_start = find_metadata_start(code);
306
307                if let Some(metadata) = metadata_start {
308                    ignored.push(Offsets {
309                        start: metadata as u32,
310                        length: (code.len() - metadata) as u32,
311                    });
312                }
313
314                ignored.sort_by_key(|o| o.start);
315
316                let mut left = 0;
317                for offset in ignored {
318                    let right = offset.start as usize;
319
320                    let matched = match deployed_code {
321                        BytecodeObject::Bytecode(bytes) => bytes[left..right] == code[left..right],
322                        BytecodeObject::Unlinked(bytes) => {
323                            if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..right * 2]) {
324                                bytes == code[left..right]
325                            } else {
326                                false
327                            }
328                        }
329                    };
330
331                    if !matched {
332                        return false;
333                    }
334
335                    left = right + offset.length as usize;
336                }
337
338                let is_partial = if left < code.len() {
339                    match deployed_code {
340                        BytecodeObject::Bytecode(bytes) => bytes[left..] == code[left..],
341                        BytecodeObject::Unlinked(bytes) => {
342                            if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..]) {
343                                bytes == code[left..]
344                            } else {
345                                false
346                            }
347                        }
348                    }
349                } else {
350                    true
351                };
352
353                if !is_partial {
354                    return false;
355                }
356
357                let Some(metadata) = metadata_start else { return true };
358
359                let exact_match = match deployed_code {
360                    BytecodeObject::Bytecode(bytes) => bytes[metadata..] == code[metadata..],
361                    BytecodeObject::Unlinked(bytes) => {
362                        if let Ok(bytes) = Bytes::from_str(&bytes[metadata * 2..]) {
363                            bytes == code[metadata..]
364                        } else {
365                            false
366                        }
367                    }
368                };
369
370                if exact_match {
371                    true
372                } else {
373                    partial_match = Some((*id, *contract));
374                    false
375                }
376            })
377            .or(partial_match)
378    }
379
380    /// Finds a contract which has the same contract name or identifier as `id`. If more than one is
381    /// found, return error.
382    pub fn find_by_name_or_identifier(
383        &self,
384        id: &str,
385    ) -> Result<Option<ArtifactWithContractRef<'_>>> {
386        let contracts = self
387            .iter()
388            .filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id)
389            .collect::<Vec<_>>();
390
391        if contracts.len() > 1 {
392            eyre::bail!("{id} has more than one implementation.");
393        }
394
395        Ok(contracts.first().copied())
396    }
397
398    /// Finds abi by name or source path
399    ///
400    /// Returns the abi and the contract name.
401    pub fn find_abi_by_name_or_src_path(&self, name_or_path: &str) -> Option<(JsonAbi, String)> {
402        self.iter()
403            .find(|(artifact, _)| {
404                artifact.name == name_or_path || artifact.source == Path::new(name_or_path)
405            })
406            .map(|(_, contract)| (contract.abi.clone(), contract.name.clone()))
407    }
408
409    /// Flattens the contracts into functions, events and errors.
410    pub fn flatten(&self) -> (BTreeMap<Selector, Function>, BTreeMap<B256, Event>, JsonAbi) {
411        let mut funcs = BTreeMap::new();
412        let mut events = BTreeMap::new();
413        let mut errors_abi = JsonAbi::new();
414        for (_name, contract) in self.iter() {
415            for func in contract.abi.functions() {
416                funcs.insert(func.selector(), func.clone());
417            }
418            for event in contract.abi.events() {
419                events.insert(event.selector(), event.clone());
420            }
421            for error in contract.abi.errors() {
422                errors_abi.errors.entry(error.name.clone()).or_default().push(error.clone());
423            }
424        }
425        (funcs, events, errors_abi)
426    }
427}
428
429impl From<ProjectCompileOutput> for ContractsByArtifact {
430    fn from(value: ProjectCompileOutput) -> Self {
431        Self::new(value.into_artifacts().map(|(id, ar)| {
432            (
433                id,
434                CompactContractBytecode {
435                    abi: ar.abi,
436                    bytecode: ar.bytecode,
437                    deployed_bytecode: ar.deployed_bytecode,
438                },
439            )
440        }))
441    }
442}
443
444impl Deref for ContractsByArtifact {
445    type Target = BTreeMap<ArtifactId, ContractData>;
446
447    fn deref(&self) -> &Self::Target {
448        &self.0
449    }
450}
451
452/// Wrapper type that maps an address to a contract identifier and contract ABI.
453pub type ContractsByAddress = BTreeMap<Address, (String, JsonAbi)>;
454
455/// Very simple fuzzy matching of contract bytecode.
456///
457/// Returns a value between `0.0` (identical) and `1.0` (completely different).
458pub fn bytecode_diff_score<'a>(mut a: &'a [u8], mut b: &'a [u8]) -> f64 {
459    // Make sure `a` is the longer one.
460    if a.len() < b.len() {
461        std::mem::swap(&mut a, &mut b);
462    }
463
464    // Account for different lengths.
465    let mut n_different_bytes = a.len() - b.len();
466
467    // If the difference is more than 32 bytes and more than 10% of the total length,
468    // we assume the bytecodes are completely different.
469    // This is a simple heuristic to avoid checking every byte when the lengths are very different.
470    // 32 is chosen to be a reasonable minimum as it's the size of metadata hashes and one EVM word.
471    if n_different_bytes > 32 && n_different_bytes * 10 > a.len() {
472        return 1.0;
473    }
474
475    // Count different bytes.
476    // SAFETY: `a` is longer than `b`.
477    n_different_bytes += unsafe { count_different_bytes(a, b) };
478
479    n_different_bytes as f64 / a.len() as f64
480}
481
482/// Returns the amount of different bytes between two slices.
483///
484/// # Safety
485///
486/// `a` must be at least as long as `b`.
487unsafe fn count_different_bytes(a: &[u8], b: &[u8]) -> usize {
488    // This could've been written as `std::iter::zip(a, b).filter(|(x, y)| x != y).count()`,
489    // however this function is very hot, and has been written to be as primitive as
490    // possible for lower optimization levels.
491
492    let a_ptr = a.as_ptr();
493    let b_ptr = b.as_ptr();
494    let len = b.len();
495
496    let mut sum = 0;
497    let mut i = 0;
498    while i < len {
499        // SAFETY: `a` is at least as long as `b`, and `i` is in bound of `b`.
500        sum += unsafe { *a_ptr.add(i) != *b_ptr.add(i) } as usize;
501        i += 1;
502    }
503    sum
504}
505
506/// Returns contract name for a given contract identifier.
507///
508/// Artifact/Contract identifier can take the following form:
509/// `<artifact file name>:<contract name>`, the `artifact file name` is the name of the json file of
510/// the contract's artifact and the contract name is the name of the solidity contract, like
511/// `SafeTransferLibTest.json:SafeTransferLibTest`
512///
513/// This returns the `contract name` part
514///
515/// # Example
516///
517/// ```
518/// use foundry_common::*;
519/// assert_eq!(
520///     "SafeTransferLibTest",
521///     get_contract_name("SafeTransferLibTest.json:SafeTransferLibTest")
522/// );
523/// ```
524pub fn get_contract_name(id: &str) -> &str {
525    id.rsplit(':').next().unwrap_or(id)
526}
527
528/// This returns the `file name` part, See [`get_contract_name`]
529///
530/// # Example
531///
532/// ```
533/// use foundry_common::*;
534/// assert_eq!(
535///     "SafeTransferLibTest.json",
536///     get_file_name("SafeTransferLibTest.json:SafeTransferLibTest")
537/// );
538/// ```
539pub fn get_file_name(id: &str) -> &str {
540    id.split(':').next().unwrap_or(id)
541}
542
543/// Helper function to convert CompactContractBytecode ~> ContractBytecodeSome
544pub fn compact_to_contract(contract: CompactContractBytecode) -> Result<ContractBytecodeSome> {
545    Ok(ContractBytecodeSome {
546        abi: contract.abi.ok_or_else(|| eyre::eyre!("No contract abi"))?,
547        bytecode: contract.bytecode.ok_or_else(|| eyre::eyre!("No contract bytecode"))?.into(),
548        deployed_bytecode: contract
549            .deployed_bytecode
550            .ok_or_else(|| eyre::eyre!("No contract deployed bytecode"))?
551            .into(),
552    })
553}
554
555/// Returns the canonicalized target path for the given identifier.
556pub fn find_target_path(project: &Project, identifier: &PathOrContractInfo) -> Result<PathBuf> {
557    match identifier {
558        PathOrContractInfo::Path(path) => Ok(canonicalized(project.root().join(path))),
559        PathOrContractInfo::ContractInfo(info) => {
560            if let Some(path) = info.path.as_ref() {
561                let path = canonicalized(project.root().join(path));
562                let sources = project.sources()?;
563                let contract_path = sources
564                    .iter()
565                    .find_map(|(src_path, _)| {
566                        if **src_path == path {
567                            return Some(src_path.clone());
568                        }
569                        None
570                    })
571                    .ok_or_else(|| {
572                        eyre::eyre!(
573                            "Could not find source file for contract `{}` at {}",
574                            info.name,
575                            path.strip_prefix(project.root()).unwrap().display()
576                        )
577                    })?;
578                return Ok(contract_path);
579            }
580            // If ContractInfo.path hasn't been provided we try to find the contract using the name.
581            // This will fail if projects have multiple contracts with the same name. In that case,
582            // path must be specified.
583            let path = project.find_contract_path(&info.name)?;
584            Ok(path)
585        }
586    }
587}
588
589/// Returns the target artifact given the path and name.
590pub fn find_matching_contract_artifact(
591    output: &mut ProjectCompileOutput,
592    target_path: &Path,
593    target_name: Option<&str>,
594) -> eyre::Result<ConfigurableContractArtifact> {
595    if let Some(name) = target_name {
596        output
597            .remove(target_path, name)
598            .ok_or_eyre(format!("Could not find artifact `{name}` in the compiled artifacts"))
599    } else {
600        let possible_targets = output
601            .artifact_ids()
602            .filter(|(id, _artifact)| id.source == target_path)
603            .collect::<Vec<_>>();
604
605        if possible_targets.is_empty() {
606            eyre::bail!(
607                "Could not find artifact linked to source `{target_path:?}` in the compiled artifacts"
608            );
609        }
610
611        let (target_id, target_artifact) = possible_targets[0].clone();
612        if possible_targets.len() == 1 {
613            return Ok(target_artifact.clone());
614        }
615
616        // If all artifact_ids in `possible_targets` have the same name (without ".", indicates
617        // additional compiler profiles), it means that there are multiple contracts in the
618        // same file.
619        if !target_id.name.contains(".")
620            && possible_targets.iter().any(|(id, _)| id.name != target_id.name)
621        {
622            eyre::bail!(
623                "Multiple contracts found in the same file, please specify the target <path>:<contract> or <contract>"
624            );
625        }
626
627        // Otherwise, we're dealing with additional compiler profiles wherein `id.source` is the
628        // same but `id.path` is different.
629        let artifact = possible_targets
630            .iter()
631            .find_map(|(id, artifact)| if id.profile == "default" { Some(*artifact) } else { None })
632            .unwrap_or(target_artifact);
633
634        Ok(artifact.clone())
635    }
636}
637
638#[cfg(test)]
639mod tests {
640    use super::*;
641
642    #[test]
643    fn bytecode_diffing() {
644        assert_eq!(bytecode_diff_score(b"a", b"a"), 0.0);
645        assert_eq!(bytecode_diff_score(b"a", b"b"), 1.0);
646
647        let a_100 = &b"a".repeat(100)[..];
648        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(100)), 1.0);
649        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(99)), 1.0);
650        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(101)), 1.0);
651        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(120)), 1.0);
652        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(1000)), 1.0);
653
654        let a_99 = &b"a".repeat(99)[..];
655        assert!(bytecode_diff_score(a_100, a_99) <= 0.01);
656    }
657
658    #[test]
659    fn find_by_deployed_code_exact_with_empty_deployed() {
660        let contracts = ContractsByArtifact::new(vec![]);
661
662        assert!(contracts.find_by_deployed_code_exact(&[]).is_none());
663    }
664}