foundry_evm_core/
utils.rs

1use crate::EnvMut;
2use alloy_consensus::BlockHeader;
3use alloy_json_abi::{Function, JsonAbi};
4use alloy_network::{AnyTxEnvelope, TransactionResponse};
5use alloy_primitives::{Address, B256, Selector, TxKind, U256};
6use alloy_provider::{Network, network::BlockResponse};
7use alloy_rpc_types::{Transaction, TransactionRequest};
8use foundry_config::NamedChain;
9pub use revm::state::EvmState as StateChangeset;
10
11/// Depending on the configured chain id and block number this should apply any specific changes
12///
13/// - checks for prevrandao mixhash after merge
14/// - applies chain specifics: on Arbitrum `block.number` is the L1 block
15///
16/// Should be called with proper chain id (retrieved from provider if not provided).
17pub fn apply_chain_and_block_specific_env_changes<N: Network>(
18    env: EnvMut<'_>,
19    block: &N::BlockResponse,
20) {
21    use NamedChain::*;
22
23    if let Ok(chain) = NamedChain::try_from(env.cfg.chain_id) {
24        let block_number = block.header().number();
25
26        match chain {
27            Mainnet => {
28                // after merge difficulty is supplanted with prevrandao EIP-4399
29                if block_number >= 15_537_351u64 {
30                    env.block.difficulty = env.block.prevrandao.unwrap_or_default().into();
31                }
32
33                return;
34            }
35            BinanceSmartChain | BinanceSmartChainTestnet => {
36                // https://github.com/foundry-rs/foundry/issues/9942
37                // As far as observed from the source code of bnb-chain/bsc, the `difficulty` field
38                // is still in use and returned by the corresponding opcode but `prevrandao`
39                // (`mixHash`) is always zero, even though bsc adopts the newer EVM
40                // specification. This will confuse revm and causes emulation
41                // failure.
42                env.block.prevrandao = Some(env.block.difficulty.into());
43                return;
44            }
45            Moonbeam | Moonbase | Moonriver | MoonbeamDev | Rsk | RskTestnet => {
46                if env.block.prevrandao.is_none() {
47                    // <https://github.com/foundry-rs/foundry/issues/4232>
48                    env.block.prevrandao = Some(B256::random());
49                }
50            }
51            c if c.is_arbitrum() => {
52                // on arbitrum `block.number` is the L1 block which is included in the
53                // `l1BlockNumber` field
54                if let Some(l1_block_number) = block
55                    .other_fields()
56                    .and_then(|other| other.get("l1BlockNumber").cloned())
57                    .and_then(|l1_block_number| {
58                        serde_json::from_value::<U256>(l1_block_number).ok()
59                    })
60                {
61                    env.block.number = l1_block_number.to();
62                }
63            }
64            _ => {}
65        }
66    }
67
68    // if difficulty is `0` we assume it's past merge
69    if block.header().difficulty().is_zero() {
70        env.block.difficulty = env.block.prevrandao.unwrap_or_default().into();
71    }
72}
73
74/// Given an ABI and selector, it tries to find the respective function.
75pub fn get_function<'a>(
76    contract_name: &str,
77    selector: Selector,
78    abi: &'a JsonAbi,
79) -> eyre::Result<&'a Function> {
80    abi.functions()
81        .find(|func| func.selector() == selector)
82        .ok_or_else(|| eyre::eyre!("{contract_name} does not have the selector {selector}"))
83}
84
85/// Configures the env for the given RPC transaction.
86/// Accounts for an impersonated transaction by resetting the `env.tx.caller` field to `tx.from`.
87pub fn configure_tx_env(env: &mut EnvMut<'_>, tx: &Transaction<AnyTxEnvelope>) {
88    let from = tx.from();
89    if let AnyTxEnvelope::Ethereum(tx) = &tx.inner.inner() {
90        configure_tx_req_env(env, &tx.clone().into(), Some(from)).expect("cannot fail");
91    }
92}
93
94/// Configures the env for the given RPC transaction request.
95/// `impersonated_from` is the address of the impersonated account. This helps account for an
96/// impersonated transaction by resetting the `env.tx.caller` field to `impersonated_from`.
97pub fn configure_tx_req_env(
98    env: &mut EnvMut<'_>,
99    tx: &TransactionRequest,
100    impersonated_from: Option<Address>,
101) -> eyre::Result<()> {
102    // If no transaction type is provided, we need to infer it from the other fields.
103    let tx_type = tx.transaction_type.unwrap_or_else(|| tx.minimal_tx_type() as u8);
104    env.tx.tx_type = tx_type;
105
106    let TransactionRequest {
107        nonce,
108        from,
109        to,
110        value,
111        gas_price,
112        gas,
113        max_fee_per_gas,
114        max_priority_fee_per_gas,
115        max_fee_per_blob_gas,
116        ref input,
117        chain_id,
118        ref blob_versioned_hashes,
119        ref access_list,
120        ref authorization_list,
121        transaction_type: _,
122        sidecar: _,
123    } = *tx;
124
125    // If no `to` field then set create kind: https://eips.ethereum.org/EIPS/eip-2470#deployment-transaction
126    env.tx.kind = to.unwrap_or(TxKind::Create);
127    // If the transaction is impersonated, we need to set the caller to the from
128    // address Ref: https://github.com/foundry-rs/foundry/issues/9541
129    env.tx.caller =
130        impersonated_from.unwrap_or(from.ok_or_else(|| eyre::eyre!("missing `from` field"))?);
131    env.tx.gas_limit = gas.ok_or_else(|| eyre::eyre!("missing `gas` field"))?;
132    env.tx.nonce = nonce.unwrap_or_default();
133    env.tx.value = value.unwrap_or_default();
134    env.tx.data = input.input().cloned().unwrap_or_default();
135    env.tx.chain_id = chain_id;
136
137    // Type 1, EIP-2930
138    env.tx.access_list = access_list.clone().unwrap_or_default();
139
140    // Type 2, EIP-1559
141    env.tx.gas_price = gas_price.or(max_fee_per_gas).unwrap_or_default();
142    env.tx.gas_priority_fee = max_priority_fee_per_gas;
143
144    // Type 3, EIP-4844
145    env.tx.blob_hashes = blob_versioned_hashes.clone().unwrap_or_default();
146    env.tx.max_fee_per_blob_gas = max_fee_per_blob_gas.unwrap_or_default();
147
148    // Type 4, EIP-7702
149    env.tx.set_signed_authorization(authorization_list.clone().unwrap_or_default());
150
151    Ok(())
152}