foundry_common/
contracts.rs

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