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