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::hardfork::SpecId;
11pub use revm::state::EvmState as StateChangeset;
12
13/// Hints to the compiler that this is a cold path, i.e. unlikely to be taken.
14#[cold]
15#[inline(always)]
16pub const fn cold_path() {
17    // TODO: remove `#[cold]` and call `std::hint::cold_path` once stable.
18}
19
20/// Constructs a generic [`FoundryBlock`] from a block header.
21pub fn block_env_from_header<BLOCK: FoundryBlock + Default>(header: &impl BlockHeader) -> BLOCK {
22    let mut block = BLOCK::default();
23    block.set_number(U256::from(header.number()));
24    block.set_beneficiary(header.beneficiary());
25    block.set_timestamp(U256::from(header.timestamp()));
26    block.set_difficulty(header.difficulty());
27    block.set_prevrandao(header.mix_hash());
28    block.set_basefee(header.base_fee_per_gas().unwrap_or_default());
29    block.set_gas_limit(header.gas_limit());
30    block
31}
32
33/// Depending on the configured chain id and block number this should apply any specific changes
34///
35/// - checks for prevrandao mixhash after merge
36/// - applies chain specifics: on Arbitrum `block.number` is the L1 block
37///
38/// Should be called with proper chain id (retrieved from provider if not provided), works with any
39/// [`FoundryBlock`] type.
40pub fn apply_chain_and_block_specific_env_changes<
41    N: Network,
42    SPEC: Into<SpecId> + Copy,
43    BLOCK: FoundryBlock,
44>(
45    evm_env: &mut EvmEnv<SPEC, BLOCK>,
46    block: &N::BlockResponse,
47    configs: NetworkConfigs,
48) {
49    use NamedChain::{BinanceSmartChain, BinanceSmartChainTestnet, Mainnet};
50
51    if let Ok(chain) = NamedChain::try_from(evm_env.cfg_env.chain_id) {
52        let block_number = block.header().number();
53
54        match chain {
55            Mainnet => {
56                // after merge difficulty is supplanted with prevrandao EIP-4399
57                if block_number >= 15_537_351u64 {
58                    evm_env
59                        .block_env
60                        .set_difficulty(evm_env.block_env.prevrandao().unwrap_or_default().into());
61                }
62
63                return;
64            }
65            BinanceSmartChain | BinanceSmartChainTestnet => {
66                // https://github.com/foundry-rs/foundry/issues/9942
67                // As far as observed from the source code of bnb-chain/bsc, the `difficulty` field
68                // is still in use and returned by the corresponding opcode but `prevrandao`
69                // (`mixHash`) is always zero, even though bsc adopts the newer EVM
70                // specification. This will confuse revm and causes emulation
71                // failure.
72                evm_env.block_env.set_prevrandao(Some(evm_env.block_env.difficulty().into()));
73                return;
74            }
75            c if c.is_arbitrum() => {
76                // on arbitrum `block.number` is the L1 block which is included in the
77                // `l1BlockNumber` field
78                if let Some(l1_block_number) = block
79                    .other_fields()
80                    .and_then(|other| other.get("l1BlockNumber").cloned())
81                    .and_then(|l1_block_number| {
82                        serde_json::from_value::<U256>(l1_block_number).ok()
83                    })
84                {
85                    evm_env.block_env.set_number(l1_block_number);
86                }
87            }
88            _ => {}
89        }
90    }
91
92    if configs.bypass_prevrandao(evm_env.cfg_env.chain_id)
93        && evm_env.block_env.prevrandao().is_none()
94    {
95        // <https://github.com/foundry-rs/foundry/issues/4232>
96        evm_env.block_env.set_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.set_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 params based on the spec id.
137pub fn get_blob_params_by_spec_id(spec: SpecId) -> BlobParams {
138    if spec >= SpecId::AMSTERDAM {
139        BlobParams::bpo2()
140    } else if spec >= SpecId::OSAKA {
141        BlobParams::osaka()
142    } else if spec >= SpecId::PRAGUE {
143        BlobParams::prague()
144    } else {
145        BlobParams::cancun()
146    }
147}
148
149/// Returns the blob base fee update fraction based on the spec id.
150pub fn get_blob_base_fee_update_fraction_by_spec_id(spec: SpecId) -> u64 {
151    get_blob_params_by_spec_id(spec).update_fraction as u64
152}
153
154/// Given an ABI and selector, it tries to find the respective function.
155pub fn get_function<'a>(
156    contract_name: &str,
157    selector: Selector,
158    abi: &'a JsonAbi,
159) -> eyre::Result<&'a Function> {
160    abi.functions()
161        .find(|func| func.selector() == selector)
162        .ok_or_else(|| eyre::eyre!("{contract_name} does not have the selector {selector}"))
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn blob_params_by_spec_id_tracks_latest_known_blob_schedule() {
171        assert_eq!(get_blob_params_by_spec_id(SpecId::CANCUN), BlobParams::cancun());
172        assert_eq!(get_blob_params_by_spec_id(SpecId::PRAGUE), BlobParams::prague());
173        assert_eq!(get_blob_params_by_spec_id(SpecId::OSAKA), BlobParams::osaka());
174        assert_eq!(get_blob_params_by_spec_id(SpecId::AMSTERDAM), BlobParams::bpo2());
175        assert_eq!(
176            get_blob_base_fee_update_fraction_by_spec_id(SpecId::AMSTERDAM),
177            BlobParams::bpo2().update_fraction as u64
178        );
179    }
180}