foundry_evm_core/
utils.rs

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