foundry_common/
utils.rs

1//! Uncategorised utilities.
2
3use alloy_primitives::{B256, Bytes, U256, hex, keccak256};
4use foundry_compilers::{
5    Project,
6    artifacts::{BytecodeObject, SolcLanguage},
7    error::SolcError,
8    flatten::{Flattener, FlattenerError},
9};
10use regex::Regex;
11use std::{path::Path, sync::LazyLock};
12
13static BYTECODE_PLACEHOLDER_RE: LazyLock<Regex> =
14    LazyLock::new(|| Regex::new(r"__\$.{34}\$__").expect("invalid regex"));
15
16/// Block on a future using the current tokio runtime on the current thread.
17pub fn block_on<F: std::future::Future>(future: F) -> F::Output {
18    block_on_handle(&tokio::runtime::Handle::current(), future)
19}
20
21/// Block on a future using the current tokio runtime on the current thread with the given handle.
22pub fn block_on_handle<F: std::future::Future>(
23    handle: &tokio::runtime::Handle,
24    future: F,
25) -> F::Output {
26    tokio::task::block_in_place(|| handle.block_on(future))
27}
28
29/// Computes the storage slot as specified by `ERC-7201`, using the `erc7201` formula ID.
30///
31/// This is defined as:
32///
33/// ```text
34/// erc7201(id: string) = keccak256(keccak256(id) - 1) & ~0xff
35/// ```
36///
37/// # Examples
38///
39/// ```
40/// use alloy_primitives::b256;
41/// use foundry_common::erc7201;
42///
43/// assert_eq!(
44///     erc7201("example.main"),
45///     b256!("0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500"),
46/// );
47/// ```
48pub fn erc7201(id: &str) -> B256 {
49    let x = U256::from_be_bytes(keccak256(id).0) - U256::from(1);
50    keccak256(x.to_be_bytes::<32>()) & B256::from(!U256::from(0xff))
51}
52
53/// Utility function to find the start of the metadata in the bytecode.
54/// This assumes that the metadata is at the end of the bytecode.
55pub fn find_metadata_start(bytecode: &[u8]) -> Option<usize> {
56    // Get the last two bytes of the bytecode to find the length of CBOR metadata.
57    let (rest, metadata_len_bytes) = bytecode.split_last_chunk()?;
58    let metadata_len = u16::from_be_bytes(*metadata_len_bytes) as usize;
59    if metadata_len > rest.len() {
60        return None;
61    }
62    ciborium::from_reader::<ciborium::Value, _>(&rest[rest.len() - metadata_len..])
63        .is_ok()
64        .then(|| rest.len() - metadata_len)
65}
66
67/// Utility function to ignore metadata hash of the given bytecode.
68/// This assumes that the metadata is at the end of the bytecode.
69pub fn ignore_metadata_hash(bytecode: &[u8]) -> &[u8] {
70    if let Some(metadata) = find_metadata_start(bytecode) {
71        &bytecode[..metadata]
72    } else {
73        bytecode
74    }
75}
76
77/// Strips all __$xxx$__ placeholders from the bytecode if it's an unlinked bytecode.
78/// by replacing them with 20 zero bytes.
79/// This is useful for matching bytecodes to a contract source, and for the source map,
80/// in which the actual address of the placeholder isn't important.
81pub fn strip_bytecode_placeholders(bytecode: &BytecodeObject) -> Option<Bytes> {
82    match &bytecode {
83        BytecodeObject::Bytecode(bytes) => Some(bytes.clone()),
84        BytecodeObject::Unlinked(s) => {
85            // Replace all __$xxx$__ placeholders with 32 zero bytes
86            let s = (*BYTECODE_PLACEHOLDER_RE).replace_all(s, "00".repeat(40));
87            let bytes = hex::decode(s.as_bytes());
88            Some(bytes.ok()?.into())
89        }
90    }
91}
92
93/// Flattens the given target of the project. Falls back to the old flattening implementation
94/// if the target cannot be compiled successfully. This would be the case if the target has invalid
95/// syntax. (e.g. Solang)
96pub fn flatten(project: Project, target_path: &Path) -> eyre::Result<String> {
97    let flattened = match Flattener::new(project.clone(), target_path) {
98        Ok(flattener) => Ok(flattener.flatten()),
99        Err(FlattenerError::Compilation(_)) => {
100            project.paths.with_language::<SolcLanguage>().flatten(target_path)
101        }
102        Err(FlattenerError::Other(err)) => Err(err),
103    }
104    .map_err(|err: SolcError| eyre::eyre!("Failed to flatten: {err}"))?;
105
106    Ok(flattened)
107}