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