foundry_evm_core/
utils.rs

1use crate::EnvMut;
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_network::{AnyTxEnvelope, TransactionResponse};
7use alloy_primitives::{Address, B256, ChainId, Selector, TxKind, U256};
8use alloy_provider::{Network, network::BlockResponse};
9use alloy_rpc_types::{Transaction, TransactionRequest};
10use foundry_config::NamedChain;
11use foundry_evm_networks::NetworkConfigs;
12use revm::primitives::{
13    eip4844::{BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE},
14    hardfork::SpecId,
15};
16pub use revm::state::EvmState as StateChangeset;
17
18/// Hints to the compiler that this is a cold path, i.e. unlikely to be taken.
19#[cold]
20#[inline(always)]
21pub fn cold_path() {
22    // TODO: remove `#[cold]` and call `std::hint::cold_path` once stable.
23}
24
25/// Depending on the configured chain id and block number this should apply any specific changes
26///
27/// - checks for prevrandao mixhash after merge
28/// - applies chain specifics: on Arbitrum `block.number` is the L1 block
29///
30/// Should be called with proper chain id (retrieved from provider if not provided).
31pub fn apply_chain_and_block_specific_env_changes<N: Network>(
32    env: EnvMut<'_>,
33    block: &N::BlockResponse,
34    configs: NetworkConfigs,
35) {
36    use NamedChain::*;
37
38    if let Ok(chain) = NamedChain::try_from(env.cfg.chain_id) {
39        let block_number = block.header().number();
40
41        match chain {
42            Mainnet => {
43                // after merge difficulty is supplanted with prevrandao EIP-4399
44                if block_number >= 15_537_351u64 {
45                    env.block.difficulty = env.block.prevrandao.unwrap_or_default().into();
46                }
47
48                return;
49            }
50            BinanceSmartChain | BinanceSmartChainTestnet => {
51                // https://github.com/foundry-rs/foundry/issues/9942
52                // As far as observed from the source code of bnb-chain/bsc, the `difficulty` field
53                // is still in use and returned by the corresponding opcode but `prevrandao`
54                // (`mixHash`) is always zero, even though bsc adopts the newer EVM
55                // specification. This will confuse revm and causes emulation
56                // failure.
57                env.block.prevrandao = Some(env.block.difficulty.into());
58                return;
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 configs.bypass_prevrandao(env.cfg.chain_id) && env.block.prevrandao.is_none() {
78        // <https://github.com/foundry-rs/foundry/issues/4232>
79        env.block.prevrandao = Some(B256::random());
80    }
81
82    // if difficulty is `0` we assume it's past merge
83    if block.header().difficulty().is_zero() {
84        env.block.difficulty = env.block.prevrandao.unwrap_or_default().into();
85    }
86}
87
88/// Derives the active [`BlobParams`] based on the given timestamp.
89///
90/// This falls back to regular ethereum blob params if no hardforks for the given chain id are
91/// detected.
92pub fn get_blob_params(chain_id: ChainId, timestamp: u64) -> BlobParams {
93    let hardfork = EthereumHardfork::from_chain_and_timestamp(Chain::from_id(chain_id), timestamp)
94        .unwrap_or_default();
95
96    match hardfork {
97        EthereumHardfork::Prague => BlobParams::prague(),
98        EthereumHardfork::Osaka => BlobParams::osaka(),
99        EthereumHardfork::Bpo1 => BlobParams::bpo1(),
100        EthereumHardfork::Bpo2 => BlobParams::bpo2(),
101
102        // future hardforks/unknown settings: update once decided
103        EthereumHardfork::Bpo3 => BlobParams::bpo2(),
104        EthereumHardfork::Bpo4 => BlobParams::bpo2(),
105        EthereumHardfork::Bpo5 => BlobParams::bpo2(),
106        EthereumHardfork::Amsterdam => BlobParams::bpo2(),
107
108        // fallback
109        _ => BlobParams::cancun(),
110    }
111}
112
113/// Derive the blob base fee update fraction based on the chain and timestamp by checking the
114/// hardfork.
115pub fn get_blob_base_fee_update_fraction(chain_id: ChainId, timestamp: u64) -> u64 {
116    get_blob_params(chain_id, timestamp).update_fraction as u64
117}
118
119/// Returns the blob base fee update fraction based on the spec id.
120pub fn get_blob_base_fee_update_fraction_by_spec_id(spec: SpecId) -> u64 {
121    if spec >= SpecId::PRAGUE {
122        BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE
123    } else {
124        BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN
125    }
126}
127
128/// Given an ABI and selector, it tries to find the respective function.
129pub fn get_function<'a>(
130    contract_name: &str,
131    selector: Selector,
132    abi: &'a JsonAbi,
133) -> eyre::Result<&'a Function> {
134    abi.functions()
135        .find(|func| func.selector() == selector)
136        .ok_or_else(|| eyre::eyre!("{contract_name} does not have the selector {selector}"))
137}
138
139/// Configures the env for the given RPC transaction.
140/// Accounts for an impersonated transaction by resetting the `env.tx.caller` field to `tx.from`.
141pub fn configure_tx_env(env: &mut EnvMut<'_>, tx: &Transaction<AnyTxEnvelope>) {
142    let from = tx.from();
143    if let AnyTxEnvelope::Ethereum(tx) = &tx.inner.inner() {
144        configure_tx_req_env(env, &tx.clone().into(), Some(from)).expect("cannot fail");
145    }
146}
147
148/// Configures the env for the given RPC transaction request.
149/// `impersonated_from` is the address of the impersonated account. This helps account for an
150/// impersonated transaction by resetting the `env.tx.caller` field to `impersonated_from`.
151pub fn configure_tx_req_env(
152    env: &mut EnvMut<'_>,
153    tx: &TransactionRequest,
154    impersonated_from: Option<Address>,
155) -> eyre::Result<()> {
156    // If no transaction type is provided, we need to infer it from the other fields.
157    let tx_type = tx.transaction_type.unwrap_or_else(|| tx.minimal_tx_type() as u8);
158    env.tx.tx_type = tx_type;
159
160    let TransactionRequest {
161        nonce,
162        from,
163        to,
164        value,
165        gas_price,
166        gas,
167        max_fee_per_gas,
168        max_priority_fee_per_gas,
169        max_fee_per_blob_gas,
170        ref input,
171        chain_id,
172        ref blob_versioned_hashes,
173        ref access_list,
174        ref authorization_list,
175        transaction_type: _,
176        sidecar: _,
177    } = *tx;
178
179    // If no `to` field then set create kind: https://eips.ethereum.org/EIPS/eip-2470#deployment-transaction
180    env.tx.kind = to.unwrap_or(TxKind::Create);
181    // If the transaction is impersonated, we need to set the caller to the from
182    // address Ref: https://github.com/foundry-rs/foundry/issues/9541
183    env.tx.caller =
184        impersonated_from.unwrap_or(from.ok_or_else(|| eyre::eyre!("missing `from` field"))?);
185    env.tx.gas_limit = gas.ok_or_else(|| eyre::eyre!("missing `gas` field"))?;
186    env.tx.nonce = nonce.unwrap_or_default();
187    env.tx.value = value.unwrap_or_default();
188    env.tx.data = input.input().cloned().unwrap_or_default();
189    env.tx.chain_id = chain_id;
190
191    // Type 1, EIP-2930
192    env.tx.access_list = access_list.clone().unwrap_or_default();
193
194    // Type 2, EIP-1559
195    env.tx.gas_price = gas_price.or(max_fee_per_gas).unwrap_or_default();
196    env.tx.gas_priority_fee = max_priority_fee_per_gas;
197
198    // Type 3, EIP-4844
199    env.tx.blob_hashes = blob_versioned_hashes.clone().unwrap_or_default();
200    env.tx.max_fee_per_blob_gas = max_fee_per_blob_gas.unwrap_or_default();
201
202    // Type 4, EIP-7702
203    env.tx.set_signed_authorization(authorization_list.clone().unwrap_or_default());
204
205    Ok(())
206}