Skip to main content

foundry_evm_core/
utils.rs

1use crate::EvmEnv;
2use alloy_chains::Chain;
3use alloy_consensus::{BlockHeader, private::alloy_eips::eip7840::BlobParams};
4use alloy_hardforks::EthereumHardfork;
5use alloy_json_abi::{Function, JsonAbi};
6use alloy_primitives::{B256, ChainId, Selector, U256};
7use alloy_provider::{Network, network::BlockResponse};
8use foundry_config::NamedChain;
9use foundry_evm_networks::NetworkConfigs;
10pub use revm::state::EvmState as StateChangeset;
11use revm::{
12    context::BlockEnv,
13    primitives::{
14        eip4844::{BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE},
15        hardfork::SpecId,
16    },
17};
18
19/// Hints to the compiler that this is a cold path, i.e. unlikely to be taken.
20#[cold]
21#[inline(always)]
22pub fn cold_path() {
23    // TODO: remove `#[cold]` and call `std::hint::cold_path` once stable.
24}
25
26/// Constructs a [`BlockEnv`] from a block header.
27pub fn block_env_from_header(header: &impl BlockHeader) -> BlockEnv {
28    BlockEnv {
29        number: U256::from(header.number()),
30        beneficiary: header.beneficiary(),
31        timestamp: U256::from(header.timestamp()),
32        difficulty: header.difficulty(),
33        prevrandao: header.mix_hash(),
34        basefee: header.base_fee_per_gas().unwrap_or_default(),
35        gas_limit: header.gas_limit(),
36        ..Default::default()
37    }
38}
39
40/// Depending on the configured chain id and block number this should apply any specific changes
41///
42/// - checks for prevrandao mixhash after merge
43/// - applies chain specifics: on Arbitrum `block.number` is the L1 block
44///
45/// Should be called with proper chain id (retrieved from provider if not provided).
46pub fn apply_chain_and_block_specific_env_changes<N: Network>(
47    evm_env: &mut EvmEnv,
48    block: &N::BlockResponse,
49    configs: NetworkConfigs,
50) {
51    use NamedChain::*;
52
53    if let Ok(chain) = NamedChain::try_from(evm_env.cfg_env.chain_id) {
54        let block_number = block.header().number();
55
56        match chain {
57            Mainnet => {
58                // after merge difficulty is supplanted with prevrandao EIP-4399
59                if block_number >= 15_537_351u64 {
60                    evm_env.block_env.difficulty =
61                        evm_env.block_env.prevrandao.unwrap_or_default().into();
62                }
63
64                return;
65            }
66            BinanceSmartChain | BinanceSmartChainTestnet => {
67                // https://github.com/foundry-rs/foundry/issues/9942
68                // As far as observed from the source code of bnb-chain/bsc, the `difficulty` field
69                // is still in use and returned by the corresponding opcode but `prevrandao`
70                // (`mixHash`) is always zero, even though bsc adopts the newer EVM
71                // specification. This will confuse revm and causes emulation
72                // failure.
73                evm_env.block_env.prevrandao = Some(evm_env.block_env.difficulty.into());
74                return;
75            }
76            c if c.is_arbitrum() => {
77                // on arbitrum `block.number` is the L1 block which is included in the
78                // `l1BlockNumber` field
79                if let Some(l1_block_number) = block
80                    .other_fields()
81                    .and_then(|other| other.get("l1BlockNumber").cloned())
82                    .and_then(|l1_block_number| {
83                        serde_json::from_value::<U256>(l1_block_number).ok()
84                    })
85                {
86                    evm_env.block_env.number = l1_block_number.to();
87                }
88            }
89            _ => {}
90        }
91    }
92
93    if configs.bypass_prevrandao(evm_env.cfg_env.chain_id) && evm_env.block_env.prevrandao.is_none()
94    {
95        // <https://github.com/foundry-rs/foundry/issues/4232>
96        evm_env.block_env.prevrandao = Some(B256::random());
97    }
98
99    // if difficulty is `0` we assume it's past merge
100    if block.header().difficulty().is_zero() {
101        evm_env.block_env.difficulty = evm_env.block_env.prevrandao.unwrap_or_default().into();
102    }
103}
104
105/// Derives the active [`BlobParams`] based on the given timestamp.
106///
107/// This falls back to regular ethereum blob params if no hardforks for the given chain id are
108/// detected.
109pub fn get_blob_params(chain_id: ChainId, timestamp: u64) -> BlobParams {
110    let hardfork = EthereumHardfork::from_chain_and_timestamp(Chain::from_id(chain_id), timestamp)
111        .unwrap_or_default();
112
113    match hardfork {
114        EthereumHardfork::Prague => BlobParams::prague(),
115        EthereumHardfork::Osaka => BlobParams::osaka(),
116        EthereumHardfork::Bpo1 => BlobParams::bpo1(),
117        EthereumHardfork::Bpo2 => BlobParams::bpo2(),
118
119        // future hardforks/unknown settings: update once decided
120        EthereumHardfork::Bpo3 => BlobParams::bpo2(),
121        EthereumHardfork::Bpo4 => BlobParams::bpo2(),
122        EthereumHardfork::Bpo5 => BlobParams::bpo2(),
123        EthereumHardfork::Amsterdam => BlobParams::bpo2(),
124
125        // fallback
126        _ => BlobParams::cancun(),
127    }
128}
129
130/// Derive the blob base fee update fraction based on the chain and timestamp by checking the
131/// hardfork.
132pub fn get_blob_base_fee_update_fraction(chain_id: ChainId, timestamp: u64) -> u64 {
133    get_blob_params(chain_id, timestamp).update_fraction as u64
134}
135
136/// Returns the blob base fee update fraction based on the spec id.
137pub fn get_blob_base_fee_update_fraction_by_spec_id(spec: SpecId) -> u64 {
138    if spec >= SpecId::PRAGUE {
139        BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE
140    } else {
141        BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN
142    }
143}
144
145/// Given an ABI and selector, it tries to find the respective function.
146pub fn get_function<'a>(
147    contract_name: &str,
148    selector: Selector,
149    abi: &'a JsonAbi,
150) -> eyre::Result<&'a Function> {
151    abi.functions()
152        .find(|func| func.selector() == selector)
153        .ok_or_else(|| eyre::eyre!("{contract_name} does not have the selector {selector}"))
154}