foundry_common/
utils.rs

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