Skip to main content

foundry_evm_core/
utils.rs

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