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::EvmStateas StateChangeset;
1718use crate::EnvMut;
1920/// 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) {
30use NamedChain::*;
3132if let Ok(chain) = NamedChain::try_from(env.cfg.chain_id) {
33let block_number = block.header().number();
3435match chain {
36 Mainnet => {
37// after merge difficulty is supplanted with prevrandao EIP-4399
38if block_number >= 15_537_351u64 {
39env.block.difficulty = env.block.prevrandao.unwrap_or_default().into();
40 }
4142return;
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.
51env.block.prevrandao = Some(env.block.difficulty.into());
52return;
53 }
54 Moonbeam | Moonbase | Moonriver | MoonbeamDev | Rsk | RskTestnet => {
55if env.block.prevrandao.is_none() {
56// <https://github.com/foundry-rs/foundry/issues/4232>
57env.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
63if let Some(l1_block_number) = block64 .other_fields()
65 .and_then(|other| other.get("l1BlockNumber").cloned())
66 .and_then(|l1_block_number| {
67serde_json::from_value::<U256>(l1_block_number).ok()
68 })
69 {
70env.block.number = l1_block_number.to();
71 }
72 }
73_ => {}
74 }
75 }
7677// if difficulty is `0` we assume it's past merge
78if block.header().difficulty().is_zero() {
79env.block.difficulty = env.block.prevrandao.unwrap_or_default().into();
80 }
81}
8283/// 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> {
89abi.functions()
90 .find(|func| func.selector() == selector)
91 .ok_or_else(|| eyre::eyre!("{contract_name} does not have the selector {selector}"))
92}
9394/// 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>) {
97let impersonated_from = is_impersonated_tx(&tx.inner).then_some(tx.from());
98if let AnyTxEnvelope::Ethereum(tx) = &tx.inner.inner() {
99configure_tx_req_env(env, &tx.clone().into(), impersonated_from).expect("cannot fail");
100 }
101}
102103/// 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<()> {
111let 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,
121ref input,
122 chain_id,
123ref blob_versioned_hashes,
124ref access_list,
125 transaction_type,
126ref authorization_list,
127 sidecar: _,
128 } = *tx;
129130// If no transaction type is provided, we need to infer it from the other fields.
131let tx_type = transaction_type.unwrap_or_else(|| {
132if 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 });
144env.tx.tx_type = tx_type;
145146// If no `to` field then set create kind: https://eips.ethereum.org/EIPS/eip-2470#deployment-transaction
147env.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
150env.tx.caller =
151impersonated_from.unwrap_or(from.ok_or_else(|| eyre::eyre!("missing `from` field"))?);
152env.tx.gas_limit = gas.ok_or_else(|| eyre::eyre!("missing `gas` field"))?;
153env.tx.nonce = nonce.unwrap_or_default();
154env.tx.value = value.unwrap_or_default();
155env.tx.data = input.input().cloned().unwrap_or_default();
156env.tx.chain_id = chain_id;
157158// Type 1, EIP-2930
159env.tx.access_list = access_list.clone().unwrap_or_default();
160161// Type 2, EIP-1559
162env.tx.gas_price = gas_price.or(max_fee_per_gas).unwrap_or_default();
163env.tx.gas_priority_fee = max_priority_fee_per_gas;
164165// Type 3, EIP-4844
166env.tx.blob_hashes = blob_versioned_hashes.clone().unwrap_or_default();
167env.tx.max_fee_per_blob_gas = max_fee_per_blob_gas.unwrap_or_default();
168169// Type 4, EIP-7702
170env.tx.set_signed_authorization(authorization_list.clone().unwrap_or_default());
171172Ok(())
173}
174175/// Get the gas used, accounting for refunds
176pub fn gas_used(spec: SpecId, spent: u64, refunded: u64) -> u64 {
177let refund_quotient = if SpecId::is_enabled_in(spec, SpecId::LONDON) { 5 } else { 2 };
178spent - (refunded).min(spent / refund_quotient)
179}