Skip to main content

foundry_evm_core/
utils.rs

1use crate::{EvmEnv, FoundryBlock, hardfork::FoundryHardfork};
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/// Applies chain-specific changes required to replay transactions accepted on-chain.
34pub fn apply_chain_specific_tx_replay_env_changes<SPEC, BLOCK>(evm_env: &mut EvmEnv<SPEC, BLOCK>) {
35    let chain_id = evm_env.cfg_env.chain_id;
36    apply_chain_specific_tx_replay_env_changes_for_chain(evm_env, chain_id);
37}
38
39/// Applies replay normalization for the provided source chain.
40///
41/// This keeps fork-specific transaction validation independent from an execution `CHAINID`
42/// override.
43pub fn apply_chain_specific_tx_replay_env_changes_for_chain<SPEC, BLOCK>(
44    evm_env: &mut EvmEnv<SPEC, BLOCK>,
45    source_chain_id: ChainId,
46) {
47    if NamedChain::try_from(source_chain_id).is_ok_and(|chain| chain.is_arbitrum()) {
48        // Arbitrum does not enforce the EIP-1559 priority fee ordering constraint.
49        evm_env.cfg_env.disable_priority_fee_check = true;
50    }
51}
52
53/// Depending on the configured chain id and block number this should apply any specific changes
54///
55/// - checks for prevrandao mixhash after merge
56/// - applies chain specifics: on Arbitrum `block.number` is the L1 block
57///
58/// Should be called with proper chain id (retrieved from provider if not provided), works with any
59/// [`FoundryBlock`] type.
60pub fn apply_chain_and_block_specific_env_changes<
61    N: Network,
62    SPEC: Into<SpecId> + Copy,
63    BLOCK: FoundryBlock,
64>(
65    evm_env: &mut EvmEnv<SPEC, BLOCK>,
66    block: &N::BlockResponse,
67    configs: NetworkConfigs,
68) {
69    let chain_id = evm_env.cfg_env.chain_id;
70    apply_chain_and_block_specific_env_changes_for_chain::<N, _, _>(
71        evm_env, block, chain_id, configs,
72    );
73}
74
75/// Applies block normalization for the provided source chain.
76///
77/// This keeps fork-specific header handling independent from an execution `CHAINID` override.
78pub fn apply_chain_and_block_specific_env_changes_for_chain<
79    N: Network,
80    SPEC: Into<SpecId> + Copy,
81    BLOCK: FoundryBlock,
82>(
83    evm_env: &mut EvmEnv<SPEC, BLOCK>,
84    block: &N::BlockResponse,
85    source_chain_id: ChainId,
86    configs: NetworkConfigs,
87) {
88    use NamedChain::{BinanceSmartChain, BinanceSmartChainTestnet, Mainnet};
89
90    if let Ok(chain) = NamedChain::try_from(source_chain_id) {
91        let block_number = block.header().number();
92
93        match chain {
94            Mainnet => {
95                // after merge difficulty is supplanted with prevrandao EIP-4399
96                if block_number >= 15_537_351u64 {
97                    evm_env
98                        .block_env
99                        .set_difficulty(evm_env.block_env.prevrandao().unwrap_or_default().into());
100                }
101
102                return;
103            }
104            BinanceSmartChain | BinanceSmartChainTestnet => {
105                // https://github.com/foundry-rs/foundry/issues/9942
106                // As far as observed from the source code of bnb-chain/bsc, the `difficulty` field
107                // is still in use and returned by the corresponding opcode but `prevrandao`
108                // (`mixHash`) is always zero, even though bsc adopts the newer EVM
109                // specification. This will confuse revm and causes emulation
110                // failure.
111                evm_env.block_env.set_prevrandao(Some(evm_env.block_env.difficulty().into()));
112                return;
113            }
114            c if c.is_arbitrum() => {
115                // on arbitrum `block.number` is the L1 block which is included in the
116                // `l1BlockNumber` field
117                if let Some(l1_block_number) = block
118                    .other_fields()
119                    .and_then(|other| other.get("l1BlockNumber").cloned())
120                    .and_then(|l1_block_number| {
121                        serde_json::from_value::<U256>(l1_block_number).ok()
122                    })
123                {
124                    evm_env.block_env.set_number(l1_block_number);
125                }
126            }
127            _ => {}
128        }
129    }
130
131    if configs.bypass_prevrandao(source_chain_id) && evm_env.block_env.prevrandao().is_none() {
132        // <https://github.com/foundry-rs/foundry/issues/4232>
133        evm_env.block_env.set_prevrandao(Some(B256::random()));
134    }
135
136    // if difficulty is `0` we assume it's past merge
137    if block.header().difficulty().is_zero() {
138        evm_env.block_env.set_difficulty(evm_env.block_env.prevrandao().unwrap_or_default().into());
139    }
140}
141
142/// Derives the active [`BlobParams`] based on the given timestamp.
143///
144/// This falls back to regular ethereum blob params if no hardforks for the given chain id are
145/// detected.
146pub fn get_blob_params(chain_id: ChainId, timestamp: u64) -> BlobParams {
147    let hardfork = EthereumHardfork::from_chain_and_timestamp(Chain::from_id(chain_id), timestamp)
148        .unwrap_or_default();
149
150    match hardfork {
151        EthereumHardfork::Prague => BlobParams::prague(),
152        EthereumHardfork::Osaka => BlobParams::osaka(),
153        EthereumHardfork::Bpo1 => BlobParams::bpo1(),
154        EthereumHardfork::Bpo2 => BlobParams::bpo2(),
155
156        // future hardforks/unknown settings: update once decided
157        EthereumHardfork::Bpo3 => BlobParams::bpo2(),
158        EthereumHardfork::Bpo4 => BlobParams::bpo2(),
159        EthereumHardfork::Bpo5 => BlobParams::bpo2(),
160        EthereumHardfork::Amsterdam => BlobParams::bpo2(),
161
162        // fallback
163        _ => BlobParams::cancun(),
164    }
165}
166
167/// Derive the blob base fee update fraction based on the chain and timestamp by checking the
168/// hardfork.
169pub fn get_blob_base_fee_update_fraction(chain_id: ChainId, timestamp: u64) -> u64 {
170    get_blob_params(chain_id, timestamp).update_fraction as u64
171}
172
173/// Returns the blob params based on the spec id.
174pub fn get_blob_params_by_spec_id(spec: SpecId) -> BlobParams {
175    if spec >= SpecId::AMSTERDAM {
176        BlobParams::bpo2()
177    } else if spec >= SpecId::OSAKA {
178        BlobParams::osaka()
179    } else if spec >= SpecId::PRAGUE {
180        BlobParams::prague()
181    } else {
182        BlobParams::cancun()
183    }
184}
185
186/// Returns the blob parameters selected by an explicit Foundry hardfork.
187pub fn get_blob_params_by_hardfork(hardfork: FoundryHardfork) -> BlobParams {
188    match hardfork {
189        FoundryHardfork::Ethereum(EthereumHardfork::Prague) => BlobParams::prague(),
190        FoundryHardfork::Ethereum(EthereumHardfork::Osaka) => BlobParams::osaka(),
191        FoundryHardfork::Ethereum(EthereumHardfork::Bpo1) => BlobParams::bpo1(),
192        FoundryHardfork::Ethereum(EthereumHardfork::Bpo2) => BlobParams::bpo2(),
193        FoundryHardfork::Ethereum(
194            EthereumHardfork::Bpo3
195            | EthereumHardfork::Bpo4
196            | EthereumHardfork::Bpo5
197            | EthereumHardfork::Amsterdam,
198        ) => BlobParams::bpo2(),
199        _ => get_blob_params_by_spec_id(hardfork.into()),
200    }
201}
202
203/// Returns the blob base fee update fraction based on the spec id.
204pub fn get_blob_base_fee_update_fraction_by_spec_id(spec: SpecId) -> u64 {
205    get_blob_params_by_spec_id(spec).update_fraction as u64
206}
207
208/// Given an ABI and selector, it tries to find the respective function.
209pub fn get_function<'a>(
210    contract_name: &str,
211    selector: Selector,
212    abi: &'a JsonAbi,
213) -> eyre::Result<&'a Function> {
214    abi.functions()
215        .find(|func| func.selector() == selector)
216        .ok_or_else(|| eyre::eyre!("{contract_name} does not have the selector {selector}"))
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222    use alloy_network::{AnyHeader, AnyNetwork, AnyRpcBlock, AnyRpcHeader};
223    use alloy_rpc_types::{Block, BlockTransactions};
224    use revm::context::{BlockEnv, CfgEnv};
225
226    #[test]
227    fn block_normalization_uses_source_chain() {
228        let header = AnyHeader { number: 500, ..Default::default() };
229        let mut block = AnyRpcBlock::new(
230            Block::new(
231                AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
232                BlockTransactions::Full(Vec::new()),
233            )
234            .into(),
235        );
236        block.other.insert("l1BlockNumber".to_string(), serde_json::json!("0x64"));
237
238        let mut cfg_env = CfgEnv::<SpecId>::default();
239        cfg_env.chain_id = NamedChain::Mainnet as u64;
240        let mut evm_env = EvmEnv {
241            cfg_env,
242            block_env: BlockEnv { number: U256::from(500), ..Default::default() },
243        };
244
245        apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
246            &mut evm_env,
247            &block,
248            NamedChain::Arbitrum as u64,
249            NetworkConfigs::default(),
250        );
251
252        assert_eq!(evm_env.cfg_env.chain_id, NamedChain::Mainnet as u64);
253        assert_eq!(evm_env.block_env.number, U256::from(100));
254    }
255
256    #[test]
257    fn block_normalization_sets_prevrandao_for_moonbeam() {
258        let header = AnyHeader { difficulty: U256::from(1), ..Default::default() };
259        let block = AnyRpcBlock::new(
260            Block::new(
261                AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
262                BlockTransactions::Full(Vec::new()),
263            )
264            .into(),
265        );
266        let mut evm_env = EvmEnv::new(
267            CfgEnv::<SpecId>::default(),
268            BlockEnv { prevrandao: None, ..Default::default() },
269        );
270
271        apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
272            &mut evm_env,
273            &block,
274            NamedChain::Moonbeam as u64,
275            NetworkConfigs::default(),
276        );
277
278        assert!(evm_env.block_env.prevrandao.is_some());
279    }
280
281    #[test]
282    fn tx_replay_env_changes_disable_priority_fee_check_only_for_arbitrum() {
283        let mut evm_env = EvmEnv::new(
284            revm::context::CfgEnv::<SpecId>::default(),
285            revm::context::BlockEnv::default(),
286        );
287        evm_env.cfg_env.chain_id = NamedChain::Arbitrum as u64;
288
289        apply_chain_specific_tx_replay_env_changes(&mut evm_env);
290        assert!(evm_env.cfg_env.disable_priority_fee_check);
291
292        evm_env.cfg_env.chain_id = NamedChain::Mainnet as u64;
293        evm_env.cfg_env.disable_priority_fee_check = false;
294
295        apply_chain_specific_tx_replay_env_changes(&mut evm_env);
296        assert!(!evm_env.cfg_env.disable_priority_fee_check);
297    }
298
299    #[test]
300    fn tx_replay_env_changes_use_source_chain() {
301        let mut evm_env = EvmEnv::new(
302            revm::context::CfgEnv::<SpecId>::default(),
303            revm::context::BlockEnv::default(),
304        );
305        evm_env.cfg_env.chain_id = NamedChain::Mainnet as u64;
306
307        apply_chain_specific_tx_replay_env_changes_for_chain(
308            &mut evm_env,
309            NamedChain::Arbitrum as u64,
310        );
311
312        assert_eq!(evm_env.cfg_env.chain_id, NamedChain::Mainnet as u64);
313        assert!(evm_env.cfg_env.disable_priority_fee_check);
314    }
315
316    #[test]
317    fn blob_params_by_spec_id_tracks_latest_known_blob_schedule() {
318        assert_eq!(get_blob_params_by_spec_id(SpecId::CANCUN), BlobParams::cancun());
319        assert_eq!(get_blob_params_by_spec_id(SpecId::PRAGUE), BlobParams::prague());
320        assert_eq!(get_blob_params_by_spec_id(SpecId::OSAKA), BlobParams::osaka());
321        assert_eq!(get_blob_params_by_spec_id(SpecId::AMSTERDAM), BlobParams::bpo2());
322        assert_eq!(
323            get_blob_base_fee_update_fraction_by_spec_id(SpecId::AMSTERDAM),
324            BlobParams::bpo2().update_fraction as u64
325        );
326    }
327
328    #[test]
329    fn blob_params_by_explicit_hardfork() {
330        for (hardfork, expected) in [
331            (EthereumHardfork::Cancun, BlobParams::cancun()),
332            (EthereumHardfork::Prague, BlobParams::prague()),
333            (EthereumHardfork::Osaka, BlobParams::osaka()),
334            (EthereumHardfork::Bpo1, BlobParams::bpo1()),
335            (EthereumHardfork::Bpo2, BlobParams::bpo2()),
336            (EthereumHardfork::Amsterdam, BlobParams::bpo2()),
337        ] {
338            assert_eq!(get_blob_params_by_hardfork(hardfork.into()), expected);
339        }
340    }
341}