foundry_evm_core/fork/
init.rs

1use crate::{utils::apply_chain_and_block_specific_env_changes, AsEnvMut, Env, EvmEnv};
2use alloy_consensus::BlockHeader;
3use alloy_primitives::Address;
4use alloy_provider::{network::BlockResponse, Network, Provider};
5use alloy_rpc_types::BlockNumberOrTag;
6use eyre::WrapErr;
7use foundry_common::NON_ARCHIVE_NODE_WARNING;
8use revm::context::{BlockEnv, CfgEnv, TxEnv};
9
10/// Initializes a REVM block environment based on a forked
11/// ethereum provider.
12pub async fn environment<N: Network, P: Provider<N>>(
13    provider: &P,
14    memory_limit: u64,
15    gas_price: Option<u128>,
16    override_chain_id: Option<u64>,
17    pin_block: Option<u64>,
18    origin: Address,
19    disable_block_gas_limit: bool,
20) -> eyre::Result<(Env, N::BlockResponse)> {
21    let block_number = if let Some(pin_block) = pin_block {
22        pin_block
23    } else {
24        provider.get_block_number().await.wrap_err("failed to get latest block number")?
25    };
26    let (fork_gas_price, rpc_chain_id, block) = tokio::try_join!(
27        provider.get_gas_price(),
28        provider.get_chain_id(),
29        provider.get_block_by_number(BlockNumberOrTag::Number(block_number))
30    )?;
31    let block = if let Some(block) = block {
32        block
33    } else {
34        if let Ok(latest_block) = provider.get_block_number().await {
35            // If the `eth_getBlockByNumber` call succeeds, but returns null instead of
36            // the block, and the block number is less than equal the latest block, then
37            // the user is forking from a non-archive node with an older block number.
38            if block_number <= latest_block {
39                error!("{NON_ARCHIVE_NODE_WARNING}");
40            }
41            eyre::bail!(
42                "failed to get block for block number: {block_number}; \
43                 latest block number: {latest_block}"
44            );
45        }
46        eyre::bail!("failed to get block for block number: {block_number}")
47    };
48
49    let cfg = configure_env(
50        override_chain_id.unwrap_or(rpc_chain_id),
51        memory_limit,
52        disable_block_gas_limit,
53    );
54
55    let mut env = Env {
56        evm_env: EvmEnv {
57            cfg_env: cfg,
58            block_env: BlockEnv {
59                number: block.header().number(),
60                timestamp: block.header().timestamp(),
61                beneficiary: block.header().beneficiary(),
62                difficulty: block.header().difficulty(),
63                prevrandao: block.header().mix_hash(),
64                basefee: block.header().base_fee_per_gas().unwrap_or_default(),
65                gas_limit: block.header().gas_limit(),
66                ..Default::default()
67            },
68        },
69        tx: TxEnv {
70            caller: origin,
71            gas_price: gas_price.unwrap_or(fork_gas_price),
72            chain_id: Some(override_chain_id.unwrap_or(rpc_chain_id)),
73            gas_limit: block.header().gas_limit() as u64,
74            ..Default::default()
75        },
76    };
77
78    apply_chain_and_block_specific_env_changes::<N>(env.as_env_mut(), &block);
79
80    Ok((env, block))
81}
82
83/// Configures the environment for the given chain id and memory limit.
84pub fn configure_env(chain_id: u64, memory_limit: u64, disable_block_gas_limit: bool) -> CfgEnv {
85    let mut cfg = CfgEnv::default();
86    cfg.chain_id = chain_id;
87    cfg.memory_limit = memory_limit;
88    cfg.limit_contract_code_size = Some(usize::MAX);
89    // EIP-3607 rejects transactions from senders with deployed code.
90    // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the caller
91    // is a contract. So we disable the check by default.
92    cfg.disable_eip3607 = true;
93    cfg.disable_block_gas_limit = disable_block_gas_limit;
94    cfg.disable_nonce_check = true;
95    cfg
96}