foundry_evm_core/
utils.rs

1use crate::EnvMut;
2use alloy_chains::Chain;
3use alloy_consensus::BlockHeader;
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 => {
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/// Derive the blob base fee update fraction based on the chain and timestamp by checking the
88/// hardfork.
89pub fn get_blob_base_fee_update_fraction(chain_id: ChainId, timestamp: u64) -> u64 {
90    let hardfork = EthereumHardfork::from_chain_and_timestamp(Chain::from_id(chain_id), timestamp)
91        .unwrap_or_default();
92
93    if hardfork >= EthereumHardfork::Prague {
94        BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE
95    } else {
96        BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN
97    }
98}
99
100/// Returns the blob base fee update fraction based on the spec id.
101pub fn get_blob_base_fee_update_fraction_by_spec_id(spec: SpecId) -> u64 {
102    if spec >= SpecId::PRAGUE {
103        BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE
104    } else {
105        BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN
106    }
107}
108
109/// Given an ABI and selector, it tries to find the respective function.
110pub fn get_function<'a>(
111    contract_name: &str,
112    selector: Selector,
113    abi: &'a JsonAbi,
114) -> eyre::Result<&'a Function> {
115    abi.functions()
116        .find(|func| func.selector() == selector)
117        .ok_or_else(|| eyre::eyre!("{contract_name} does not have the selector {selector}"))
118}
119
120/// Configures the env for the given RPC transaction.
121/// Accounts for an impersonated transaction by resetting the `env.tx.caller` field to `tx.from`.
122pub fn configure_tx_env(env: &mut EnvMut<'_>, tx: &Transaction<AnyTxEnvelope>) {
123    let from = tx.from();
124    if let AnyTxEnvelope::Ethereum(tx) = &tx.inner.inner() {
125        configure_tx_req_env(env, &tx.clone().into(), Some(from)).expect("cannot fail");
126    }
127}
128
129/// Configures the env for the given RPC transaction request.
130/// `impersonated_from` is the address of the impersonated account. This helps account for an
131/// impersonated transaction by resetting the `env.tx.caller` field to `impersonated_from`.
132pub fn configure_tx_req_env(
133    env: &mut EnvMut<'_>,
134    tx: &TransactionRequest,
135    impersonated_from: Option<Address>,
136) -> eyre::Result<()> {
137    // If no transaction type is provided, we need to infer it from the other fields.
138    let tx_type = tx.transaction_type.unwrap_or_else(|| tx.minimal_tx_type() as u8);
139    env.tx.tx_type = tx_type;
140
141    let TransactionRequest {
142        nonce,
143        from,
144        to,
145        value,
146        gas_price,
147        gas,
148        max_fee_per_gas,
149        max_priority_fee_per_gas,
150        max_fee_per_blob_gas,
151        ref input,
152        chain_id,
153        ref blob_versioned_hashes,
154        ref access_list,
155        ref authorization_list,
156        transaction_type: _,
157        sidecar: _,
158    } = *tx;
159
160    // If no `to` field then set create kind: https://eips.ethereum.org/EIPS/eip-2470#deployment-transaction
161    env.tx.kind = to.unwrap_or(TxKind::Create);
162    // If the transaction is impersonated, we need to set the caller to the from
163    // address Ref: https://github.com/foundry-rs/foundry/issues/9541
164    env.tx.caller =
165        impersonated_from.unwrap_or(from.ok_or_else(|| eyre::eyre!("missing `from` field"))?);
166    env.tx.gas_limit = gas.ok_or_else(|| eyre::eyre!("missing `gas` field"))?;
167    env.tx.nonce = nonce.unwrap_or_default();
168    env.tx.value = value.unwrap_or_default();
169    env.tx.data = input.input().cloned().unwrap_or_default();
170    env.tx.chain_id = chain_id;
171
172    // Type 1, EIP-2930
173    env.tx.access_list = access_list.clone().unwrap_or_default();
174
175    // Type 2, EIP-1559
176    env.tx.gas_price = gas_price.or(max_fee_per_gas).unwrap_or_default();
177    env.tx.gas_priority_fee = max_priority_fee_per_gas;
178
179    // Type 3, EIP-4844
180    env.tx.blob_hashes = blob_versioned_hashes.clone().unwrap_or_default();
181    env.tx.max_fee_per_blob_gas = max_fee_per_blob_gas.unwrap_or_default();
182
183    // Type 4, EIP-7702
184    env.tx.set_signed_authorization(authorization_list.clone().unwrap_or_default());
185
186    Ok(())
187}