Skip to main content

reth_rpc_eth_types/
simulate.rs

1//! Utilities for serving `eth_simulateV1`
2
3use crate::{
4    error::{api::FromEthApiError, FromEvmError, ToRpcError},
5    EthApiError,
6};
7use alloy_chains::Chain;
8use alloy_consensus::{transaction::TxHashRef, BlockHeader, Transaction as _};
9use alloy_eips::eip2718::WithEncoded;
10use alloy_network::{NetworkTransactionBuilder, TransactionBuilder};
11use alloy_primitives::{keccak256, map::AddressMap, Address, Bytes, TxKind, U256};
12use alloy_rpc_types_eth::{
13    simulate::{SimBlock, SimCallResult, SimulateError, SimulatedBlock},
14    state::StateOverride,
15    BlockId, BlockOverrides, BlockTransactionsKind,
16};
17use evm2::{interpreter::InstrStop, precompiles::MovePrecompileError, EvmFeatures, TxResult};
18use evm2_inspectors::transfer::TransferInspector;
19use jsonrpsee_types::{error::INTERNAL_ERROR_CODE, ErrorObject};
20use reth_errors::ProviderResult;
21use reth_evm::{
22    execute::{BlockBuilder, BlockBuilderOutcome, BlockTransactionResult},
23    BlockExecutionError, BundleState, Database, DynDatabase, Evm as RethEvm, EvmEnv,
24};
25use reth_primitives_traits::{
26    BlockBody as _, BlockTy, NodePrimitives, Recovered, RecoveredBlock, SealedHeader,
27};
28use reth_rpc_convert::{RpcBlock, RpcConvert, RpcTxReq};
29use reth_rpc_server_types::result::rpc_err;
30use reth_storage_api::{noop::NoopProvider, HashedPostStateProvider, StateProvider};
31use reth_tasks::cancel::is_cancelled;
32use reth_trie_common::{HashedPostState, KeccakKeyHasher};
33use std::mem;
34
35/// Fallback seconds added between simulated block timestamps when neither the user nor the chain
36/// hint provides a value.
37const SIMULATE_FALLBACK_TIMESTAMP_INCREMENT: u64 = 12;
38
39/// Error code for execution reverted in `eth_simulateV1`.
40///
41/// Consistent with `eth_call` revert error code.
42///
43/// <https://github.com/ethereum/execution-apis/pull/748>
44pub const SIMULATE_REVERT_CODE: i32 = 3;
45
46/// Error code for VM execution errors (e.g., out of gas) in `eth_simulateV1`.
47///
48/// <https://github.com/ethereum/execution-apis>
49pub const SIMULATE_VM_ERROR_CODE: i32 = -32015;
50
51/// Errors which may occur during `eth_simulateV1` execution.
52#[derive(Debug, thiserror::Error)]
53pub enum EthSimulateError {
54    /// Total gas limit of transactions for the block exceeds the block gas limit.
55    #[error("Block gas limit exceeded by the block's transactions")]
56    BlockGasLimitExceeded,
57    /// Number of simulated blocks exceeds the configured client limit.
58    #[error("too many blocks")]
59    TooManyBlocks,
60    /// Max gas limit for entire operation exceeded.
61    #[error("Client adjustable limit reached")]
62    GasLimitReached,
63    /// Base block for the simulation was not found.
64    #[error("block not found: {block}")]
65    BlockNotFound {
66        /// The block id that was requested.
67        block: BlockId,
68    },
69    /// Block number in sequence did not increase.
70    #[error("block numbers must be in order: {got} <= {parent}")]
71    BlockNumberInvalid {
72        /// The block number that was provided.
73        got: u64,
74        /// The parent block number.
75        parent: u64,
76    },
77    /// Block timestamp in sequence did not increase.
78    #[error("block timestamps must be in order: {got} <= {parent}")]
79    BlockTimestampInvalid {
80        /// The block timestamp that was provided.
81        got: u64,
82        /// The parent block timestamp.
83        parent: u64,
84    },
85    /// Transaction nonce is too low.
86    #[error("nonce too low: next nonce {state}, tx nonce {tx}")]
87    NonceTooLow {
88        /// Transaction nonce.
89        tx: u64,
90        /// Current state nonce.
91        state: u64,
92    },
93    /// Transaction nonce is too high.
94    #[error("nonce too high")]
95    NonceTooHigh,
96    /// Transaction nonce cannot be incremented.
97    #[error("nonce has max value")]
98    NonceMaxValue,
99    /// Transaction's baseFeePerGas is too low.
100    #[error("max fee per gas less than block base fee")]
101    BaseFeePerGasTooLow,
102    /// Not enough gas provided to pay for intrinsic gas.
103    #[error("intrinsic gas too low")]
104    IntrinsicGasTooLow,
105    /// Insufficient funds to pay for gas fees and value.
106    #[error("insufficient funds for gas * price + value: have {balance} want {cost}")]
107    InsufficientFunds {
108        /// Transaction cost.
109        cost: U256,
110        /// Sender balance.
111        balance: U256,
112    },
113    /// Sender is not an EOA.
114    #[error("sender is not an EOA")]
115    SenderNotEOA,
116    /// Max init code size exceeded.
117    #[error("max initcode size exceeded")]
118    MaxInitCodeSizeExceeded,
119    /// Attempted to move a non-precompile address.
120    #[error("account {0} is not a precompile")]
121    NotAPrecompile(Address),
122    /// Attempted to move a precompile to its own address.
123    #[error("cannot move precompile {0} to itself")]
124    MovePrecompileToSelf(Address),
125}
126
127impl EthSimulateError {
128    /// Returns the JSON-RPC error code for a `eth_simulateV1` error.
129    pub const fn error_code(&self) -> i32 {
130        match self {
131            Self::NonceTooLow { .. } => -38010,
132            Self::NonceTooHigh => -38011,
133            Self::NonceMaxValue => INTERNAL_ERROR_CODE,
134            Self::BaseFeePerGasTooLow => -38012,
135            Self::IntrinsicGasTooLow => -38013,
136            Self::InsufficientFunds { .. } => -38014,
137            Self::BlockGasLimitExceeded => -38015,
138            Self::BlockNumberInvalid { .. } => -38020,
139            Self::BlockTimestampInvalid { .. } => -38021,
140            Self::SenderNotEOA => -38024,
141            Self::MaxInitCodeSizeExceeded => -38025,
142            Self::TooManyBlocks | Self::GasLimitReached => -38026,
143            Self::MovePrecompileToSelf(_) => -38022,
144            Self::BlockNotFound { .. } | Self::NotAPrecompile(_) => -32000,
145        }
146    }
147}
148
149impl ToRpcError for EthSimulateError {
150    fn to_rpc_error(&self) -> ErrorObject<'static> {
151        rpc_err(self.error_code(), self.to_string(), None)
152    }
153}
154
155/// Sanitizes and gap-fills the chain of [`SimBlock`]s for `eth_simulateV1`.
156///
157/// Walks the provided block-state calls in order and:
158/// - validates that each block number and timestamp strictly increases relative to the parent and
159///   prior simulated block;
160/// - inserts empty filler blocks for every gap in block numbers, so a request like `[block at
161///   number N + k]` over a parent at `N` expands to `k - 1` empty blocks followed by the requested
162///   one (per the execution-apis spec: "If the block number is increased more than `1` compared to
163///   the previous block, new empty blocks are generated in between.");
164/// - assigns default block numbers (`prev_number + 1`) and timestamps (`prev_timestamp + chain
165///   block time`) when missing, so every returned entry has explicit `number` and `time` overrides.
166///   The block time defaults to [`Chain::average_blocktime_hint`] for the chain id, falling back to
167///   `SIMULATE_FALLBACK_TIMESTAMP_INCREMENT` when no hint is registered. Sub-second chain hints are
168///   rounded up because block timestamps are second-granular;
169/// - enforces the global `max_simulate_blocks` cap on the total number of blocks (including
170///   generated fillers).
171pub fn sanitize_chain<TxReq, H>(
172    blocks: Vec<SimBlock<TxReq>>,
173    parent: &SealedHeader<H>,
174    chain_id: u64,
175    max_simulate_blocks: u64,
176) -> Result<Vec<SimBlock<TxReq>>, EthApiError>
177where
178    H: BlockHeader,
179{
180    let timestamp_increment = Chain::from(chain_id)
181        .average_blocktime_hint()
182        .map(|d| d.as_secs().saturating_add(u64::from(d.subsec_nanos() > 0)))
183        .filter(|&s| s > 0)
184        .unwrap_or(SIMULATE_FALLBACK_TIMESTAMP_INCREMENT);
185
186    let mut out = Vec::with_capacity(blocks.len());
187    let base_number = parent.number();
188    let mut prev_number = base_number;
189    let mut prev_timestamp = parent.timestamp();
190
191    for mut block in blocks {
192        let overrides = block.block_overrides.get_or_insert_with(BlockOverrides::default);
193
194        // Default block number to prev + 1 if not specified.
195        let target_number = if let Some(n) = overrides.number {
196            u64::try_from(n).unwrap_or(u64::MAX)
197        } else {
198            let n = prev_number.saturating_add(1);
199            overrides.number = Some(U256::from(n));
200            n
201        };
202
203        if target_number <= prev_number {
204            return Err(EthApiError::other(EthSimulateError::BlockNumberInvalid {
205                got: target_number,
206                parent: prev_number,
207            }));
208        }
209
210        if target_number.saturating_sub(base_number) > max_simulate_blocks {
211            return Err(EthApiError::other(EthSimulateError::TooManyBlocks));
212        }
213
214        // Insert empty filler blocks for any gap between prev_number and target_number.
215        let gap = target_number - prev_number;
216        if gap > 1 {
217            for i in 1..gap {
218                let filler_number = prev_number + i;
219                let filler_time =
220                    prev_timestamp.checked_add(timestamp_increment).ok_or_else(|| {
221                        EthApiError::other(EthSimulateError::BlockTimestampInvalid {
222                            got: prev_timestamp,
223                            parent: prev_timestamp,
224                        })
225                    })?;
226                out.push(SimBlock {
227                    block_overrides: Some(BlockOverrides {
228                        number: Some(U256::from(filler_number)),
229                        time: Some(filler_time),
230                        ..Default::default()
231                    }),
232                    state_overrides: None,
233                    calls: Vec::new(),
234                });
235                prev_timestamp = filler_time;
236            }
237        }
238
239        prev_number = target_number;
240        // Default timestamp to prev + increment if not specified, otherwise validate ordering.
241        let block_time = if let Some(t) = overrides.time {
242            if t <= prev_timestamp {
243                return Err(EthApiError::other(EthSimulateError::BlockTimestampInvalid {
244                    got: t,
245                    parent: prev_timestamp,
246                }));
247            }
248            t
249        } else {
250            let t = prev_timestamp.checked_add(timestamp_increment).ok_or_else(|| {
251                EthApiError::other(EthSimulateError::BlockTimestampInvalid {
252                    got: prev_timestamp,
253                    parent: prev_timestamp,
254                })
255            })?;
256            overrides.time = Some(t);
257            t
258        };
259        prev_timestamp = block_time;
260
261        out.push(block);
262    }
263
264    Ok(out)
265}
266
267/// Applies precompile move overrides from state overrides to the EVM's precompiles map.
268///
269/// This function processes `movePrecompileToAddress` entries from the state overrides and
270/// moves precompiles from their original addresses to new addresses. The original address
271/// is cleared (precompile removed) and the precompile is installed at the destination address.
272pub fn apply_precompile_overrides(
273    state_overrides: &StateOverride,
274    evm: &mut impl RethEvm,
275) -> Result<(), EthSimulateError> {
276    let moves: Vec<_> = state_overrides
277        .iter()
278        .filter_map(|(source, account_override)| {
279            account_override.move_precompile_to.map(|dest| (*source, dest))
280        })
281        .collect();
282    if moves.is_empty() {
283        return Ok(())
284    }
285
286    for (source, dest) in &moves {
287        if source == dest {
288            if !evm.has_precompile(source) {
289                return Err(EthSimulateError::NotAPrecompile(*source))
290            }
291            return Err(EthSimulateError::MovePrecompileToSelf(*source))
292        }
293    }
294
295    evm.move_precompiles(moves).map_err(|MovePrecompileError::NotAPrecompile(addr)| {
296        EthSimulateError::NotAPrecompile(addr)
297    })?;
298
299    Ok(())
300}
301
302/// Converts all [`TransactionRequest`]s into [`Recovered`] transactions and applies them to the
303/// given [`BlockBuilder`].
304///
305/// Returns all executed transactions and the result of the execution.
306///
307/// For each call without an explicit `gas` field, the remaining block gas is used as the default.
308/// The RPC gas cap is tracked as a request-wide remaining budget and caps each call before
309/// execution. This matches the spec rule `"gasLimit: blockGasLimit - soFarUsedGasInBlock"` and
310/// geth's per-call `sanitizeCall` behavior.
311///
312/// `call_nonce` returns the nonce for a call without one from the state before the call, or `None`
313/// to use the sender's account nonce.
314///
315/// [`TransactionRequest`]: alloy_rpc_types_eth::TransactionRequest
316#[expect(clippy::type_complexity, clippy::too_many_arguments)]
317pub fn execute_transactions<S, T, EvmTypes>(
318    mut builder: S,
319    state_provider: impl StateProvider,
320    calls: Vec<RpcTxReq<T::Network>>,
321    remaining_call_gas_limit: &mut Option<u64>,
322    chain_id: u64,
323    simulation_state: Option<&mut SimulatedState>,
324    converter: &T,
325    mut call_nonce: impl FnMut(
326        &RpcTxReq<T::Network>,
327        &mut dyn DynDatabase,
328    ) -> Result<Option<u64>, EthApiError>,
329) -> Result<(BlockBuilderOutcome<S::Primitives>, Vec<TxResult<EvmTypes>>), EthApiError>
330where
331    S: BlockBuilder<Executor: reth_evm::BlockExecutor<Evm: RethEvm<EvmTypes = EvmTypes>>>,
332    T: RpcConvert<Primitives = S::Primitives>,
333    EvmTypes: evm2::EvmTypes,
334{
335    if is_cancelled() {
336        return Err(EthApiError::InternalEthError)
337    }
338    builder.apply_pre_execution_changes()?;
339
340    let mut results = Vec::with_capacity(calls.len());
341    let mut cumulative_tx_gas_used: u64 = 0;
342    let mut block_regular_gas_used: u64 = 0;
343    let mut block_state_gas_used: u64 = 0;
344    let block_gas_limit = builder.evm_env().block_env().gas_limit.to::<u64>();
345    let is_amsterdam = builder.evm_env().version().feature(EvmFeatures::EIP8037);
346    let tx_gas_limit_cap = builder.evm_env().version().tx_gas_limit_cap;
347    for mut call in calls {
348        if is_cancelled() {
349            return Err(EthApiError::InternalEthError)
350        }
351        let block_gas_remaining = if is_amsterdam {
352            let regular_available_gas = block_gas_limit.saturating_sub(block_regular_gas_used);
353            let state_available_gas = block_gas_limit.saturating_sub(block_state_gas_used);
354            // Gas above the tx gas cap goes to the state gas reservoir and only needs state gas.
355            if regular_available_gas >= tx_gas_limit_cap {
356                state_available_gas
357            } else {
358                regular_available_gas.min(state_available_gas)
359            }
360        } else {
361            block_gas_limit.saturating_sub(cumulative_tx_gas_used)
362        };
363        let mut default_gas_limit = block_gas_remaining;
364
365        if let Some(gas_limit) = call.as_ref().gas_limit() &&
366            gas_limit > block_gas_remaining
367        {
368            return Err(EthApiError::other(EthSimulateError::BlockGasLimitExceeded))
369        }
370
371        if let Some(remaining_call_gas_limit) = *remaining_call_gas_limit {
372            if let Some(gas_limit) = call.as_ref().gas_limit() {
373                if gas_limit > remaining_call_gas_limit {
374                    call.as_mut().set_gas_limit(remaining_call_gas_limit);
375                }
376            } else {
377                default_gas_limit = default_gas_limit.min(remaining_call_gas_limit);
378            }
379        }
380
381        if call.as_ref().nonce().is_none() &&
382            let Some(nonce) = call_nonce(&call, builder.evm_mut().state_db_mut())?
383        {
384            call.as_mut().set_nonce(nonce);
385        }
386
387        // Resolve transaction, populate missing fields and enforce calls
388        // correctness.
389        let tx = resolve_transaction_with_evm(
390            call,
391            default_gas_limit,
392            builder.evm_env().block_base_fee(),
393            chain_id,
394            !builder.evm_env().version().feature(EvmFeatures::NONCE_CHECK),
395            builder.evm_mut(),
396            converter,
397        )?;
398        // Create transaction with an empty envelope.
399        // The effect for a layer-2 execution client is that it does not charge L1 cost.
400        let tx = WithEncoded::new(Default::default(), tx);
401
402        let mut tx_regular_gas_used = 0;
403        let gas_output = builder.execute_transaction_with_result_closure(tx, |output| {
404            let output = &output.result().result;
405            tx_regular_gas_used = output.execution_gas_spent();
406            results.push(output.clone())
407        })?;
408
409        // The transfer tracer keeps the transaction's logs, with the synthetic transfer logs
410        // among them, outside the EVM's own logs.
411        // SAFETY: `TransferInspector` has no lifetime parameters.
412        if let Some(logs) = unsafe {
413            builder
414                .evm_mut()
415                .with_inspector(|inspector: &mut TransferInspector| inspector.take_logs())
416        } && let Some(result) = results.last_mut()
417        {
418            result.logs = logs;
419        }
420
421        let gas_used = gas_output.tx_gas_used();
422        if let Some(remaining_call_gas_limit) = remaining_call_gas_limit.as_mut() {
423            if gas_used > *remaining_call_gas_limit {
424                return Err(EthApiError::other(EthSimulateError::GasLimitReached))
425            }
426            *remaining_call_gas_limit -= gas_used;
427        }
428
429        cumulative_tx_gas_used = cumulative_tx_gas_used.saturating_add(gas_used);
430        block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas_used);
431        block_state_gas_used = block_state_gas_used.saturating_add(gas_output.state_gas_used());
432    }
433
434    let result = if let Some(simulation_state) = simulation_state {
435        builder.finish_with_state_root(&state_provider, |output| {
436            simulation_state
437                .extend(&state_provider, &output.state)
438                .map_err(BlockExecutionError::other)?;
439            state_provider
440                .state_root_with_updates(simulation_state.hashed_state().clone())
441                .map(Some)
442                .map_err(BlockExecutionError::other)
443        })?
444    } else {
445        builder.finish(NoopProvider::default(), None)?
446    };
447
448    Ok((result, results))
449}
450
451/// Goes over the list of [`TransactionRequest`]s and populates missing fields trying to resolve
452/// them into primitive transactions.
453///
454/// This will set the defaults as defined in <https://github.com/ethereum/execution-apis/blob/e56d3208789259d0b09fa68e9d8594aa4d73c725/docs/ethsimulatev1-notes.md#default-values-for-transactions>
455///
456/// [`TransactionRequest`]: alloy_rpc_types_eth::TransactionRequest
457pub fn resolve_transaction<DB: Database, Tx, T>(
458    tx: RpcTxReq<T::Network>,
459    default_gas_limit: u64,
460    block_base_fee_per_gas: u64,
461    chain_id: u64,
462    disable_nonce_check: bool,
463    db: &mut DB,
464    converter: &T,
465) -> Result<Recovered<Tx>, EthApiError>
466where
467    DB::Error: Into<EthApiError>,
468    T: RpcConvert<Primitives: NodePrimitives<SignedTx = Tx>>,
469{
470    resolve_transaction_with_account(
471        tx,
472        default_gas_limit,
473        block_base_fee_per_gas,
474        chain_id,
475        disable_nonce_check,
476        |address| db.get_account(address).map_err(Into::into),
477        converter,
478    )
479}
480
481fn resolve_transaction_with_evm<Tx, T, E>(
482    tx: RpcTxReq<T::Network>,
483    default_gas_limit: u64,
484    block_base_fee_per_gas: u64,
485    chain_id: u64,
486    disable_nonce_check: bool,
487    evm: &mut E,
488    converter: &T,
489) -> Result<Recovered<Tx>, EthApiError>
490where
491    T: RpcConvert<Primitives: NodePrimitives<SignedTx = Tx>>,
492    E: RethEvm,
493{
494    resolve_transaction_with_account(
495        tx,
496        default_gas_limit,
497        block_base_fee_per_gas,
498        chain_id,
499        disable_nonce_check,
500        |address| evm.account_info(address).map_err(Into::into),
501        converter,
502    )
503}
504
505fn resolve_transaction_with_account<Tx, T, F>(
506    mut tx: RpcTxReq<T::Network>,
507    default_gas_limit: u64,
508    block_base_fee_per_gas: u64,
509    chain_id: u64,
510    disable_nonce_check: bool,
511    mut account: F,
512    converter: &T,
513) -> Result<Recovered<Tx>, EthApiError>
514where
515    T: RpcConvert<Primitives: NodePrimitives<SignedTx = Tx>>,
516    F: FnMut(&Address) -> Result<Option<evm2::evm::AccountInfo>, EthApiError>,
517{
518    // If we're missing any fields we try to fill nonce, gas and
519    // gas price.
520    let tx_type = tx.as_ref().output_tx_type();
521
522    let from = tx.as_ref().from().unwrap_or_else(|| {
523        tx.as_mut().set_from(Address::ZERO);
524        Address::ZERO
525    });
526
527    let state_nonce = account(&from)?.map(|acc| acc.nonce).unwrap_or_default();
528    if tx.as_ref().nonce().is_none() {
529        tx.as_mut().set_nonce(state_nonce);
530    }
531    // EIP-2681 applies in both modes. Check it here before checking the fee cap.
532    if state_nonce == u64::MAX && (disable_nonce_check || tx.as_ref().nonce() == Some(u64::MAX)) {
533        return Err(EthApiError::other(EthSimulateError::NonceMaxValue))
534    }
535    // The EVM rejects a tx nonce of u64::MAX even with the nonce check disabled. The state nonce is
536    // below the limit here, so only the tx object changes.
537    if disable_nonce_check && tx.as_ref().nonce() == Some(u64::MAX) {
538        tx.as_mut().set_nonce(0);
539    }
540
541    if tx.as_ref().gas_limit().is_none() {
542        tx.as_mut().set_gas_limit(default_gas_limit);
543    }
544
545    if tx.as_ref().chain_id().is_none() {
546        tx.as_mut().set_chain_id(chain_id);
547    }
548
549    if tx.as_ref().kind().is_none() {
550        tx.as_mut().set_kind(TxKind::Create);
551    }
552
553    // if we can't build the _entire_ transaction yet, fill the fee fields.
554    //
555    // Per the eth_simulateV1 spec, unspecified fee fields default to 0 (not the block base fee),
556    // matching geth's `CallDefaults` behavior. This lets simulation behave like a free-gas
557    // `eth_call` when validation is off, and surfaces "max fee per gas less than block base fee"
558    // errors when validation is on with a real base fee.
559    let _ = block_base_fee_per_gas;
560    if tx.as_ref().output_tx_type_checked().is_none() {
561        if tx_type.is_legacy() || tx_type.is_eip2930() {
562            if tx.as_ref().gas_price().is_none() {
563                tx.as_mut().set_gas_price(0);
564            }
565        } else {
566            if tx.as_ref().max_fee_per_gas().is_none() {
567                tx.as_mut().set_max_fee_per_gas(0);
568            }
569            if tx.as_ref().max_priority_fee_per_gas().is_none() {
570                tx.as_mut().set_max_priority_fee_per_gas(0);
571            }
572        }
573    }
574
575    let tx =
576        converter.build_simulate_v1_transaction(tx).map_err(|e| EthApiError::other(e.into()))?;
577
578    Ok(Recovered::new_unchecked(tx, from))
579}
580
581/// Handles outputs of the calls execution and builds a [`SimulatedBlock`].
582pub fn build_simulated_block<Err, T, EvmTypes>(
583    block: RecoveredBlock<BlockTy<T::Primitives>>,
584    results: Vec<TxResult<EvmTypes>>,
585    txs_kind: BlockTransactionsKind,
586    converter: &T,
587) -> Result<SimulatedBlock<RpcBlock<T::Network>>, Err>
588where
589    Err: std::error::Error
590        + FromEthApiError
591        + FromEvmError<T::Evm>
592        + From<T::Error>
593        + Into<jsonrpsee_types::ErrorObject<'static>>,
594    T: RpcConvert,
595    EvmTypes: evm2::EvmTypes,
596{
597    let mut calls: Vec<SimCallResult> = Vec::with_capacity(results.len());
598
599    let mut log_index = 0;
600    for (index, (result, tx)) in results.into_iter().zip(block.body().transactions()).enumerate() {
601        let gas_used = result.tx_gas_used();
602        let max_used_gas = Some(result.total_gas_spent.max(result.floor_gas));
603        let call = if result.status {
604            SimCallResult {
605                return_data: result.output,
606                error: None,
607                gas_used,
608                max_used_gas,
609                logs: result
610                    .logs
611                    .into_iter()
612                    .map(|log| {
613                        log_index += 1;
614                        alloy_rpc_types_eth::Log {
615                            inner: log,
616                            log_index: Some(log_index - 1),
617                            transaction_index: Some(index as u64),
618                            transaction_hash: Some(*tx.tx_hash()),
619                            block_hash: Some(block.hash()),
620                            block_number: Some(block.header().number()),
621                            block_timestamp: Some(block.header().timestamp()),
622                            ..Default::default()
623                        }
624                    })
625                    .collect(),
626                status: true,
627            }
628        } else if result.stop == InstrStop::Revert {
629            let error = Err::from_revert(result.output.clone());
630            SimCallResult {
631                return_data: Bytes::new(),
632                error: Some(SimulateError {
633                    message: error.to_string(),
634                    code: SIMULATE_REVERT_CODE,
635                    data: Some(result.output),
636                }),
637                gas_used,
638                max_used_gas,
639                status: false,
640                logs: Vec::new(),
641            }
642        } else {
643            let error = Err::from_evm_halt(result.stop, tx.gas_limit());
644            SimCallResult {
645                return_data: Bytes::new(),
646                error: Some(SimulateError {
647                    message: error.to_string(),
648                    code: SIMULATE_VM_ERROR_CODE,
649                    ..SimulateError::invalid_params()
650                }),
651                gas_used,
652                max_used_gas,
653                logs: Vec::new(),
654                status: false,
655            }
656        };
657
658        calls.push(call);
659    }
660
661    let block = block.into_rpc_block(
662        txs_kind,
663        |tx, tx_info| converter.fill(tx, tx_info),
664        |header, block_size| converter.convert_header(header, Some(block_size)),
665    )?;
666    Ok(SimulatedBlock { inner: block, calls })
667}
668
669/// The hashed post-state accumulated across simulated blocks, relative to the parent state.
670///
671/// Each block's changes are hashed once when they are added. Re-hashing a cumulative
672/// [`BundleState`] instead would read the parent storage of every destroyed account again for
673/// every block.
674#[derive(Debug, Default)]
675pub struct SimulatedState {
676    hashed: HashedPostState,
677    /// Whether the parent storage of an account still has to be zeroed in `hashed`, by the
678    /// account's first appearance in the changes.
679    parent_storage_unzeroed: AddressMap<bool>,
680}
681
682impl SimulatedState {
683    /// Returns the accumulated hashed post-state.
684    pub const fn hashed_state(&self) -> &HashedPostState {
685        &self.hashed
686    }
687
688    /// Adds the changes of a block or of state overrides on top of the accumulated state.
689    ///
690    /// A destroyed account drops every storage value recorded so far. Its parent storage is read
691    /// from `provider` only the first time, and later wipes zero the recorded slots in place.
692    pub fn extend(
693        &mut self,
694        provider: &impl HashedPostStateProvider,
695        changes: &BundleState,
696    ) -> ProviderResult<()> {
697        let mut parent_storage = BundleState::default();
698        for (address, account) in &changes.state {
699            // The first change to an account starts from its parent state.
700            let unzeroed = self
701                .parent_storage_unzeroed
702                .entry(*address)
703                .or_insert_with(|| account.original_info.is_some());
704            if !account.was_destroyed() {
705                continue
706            }
707            if let Some(storage) = self.hashed.storages.get_mut(&keccak256(address)) {
708                storage.storage.values_mut().for_each(|value| *value = U256::ZERO);
709            }
710            if mem::take(unzeroed) {
711                let mut account = account.clone();
712                account.storage.clear();
713                parent_storage.state.insert(*address, account);
714            }
715        }
716
717        if !parent_storage.state.is_empty() {
718            self.hashed.extend(provider.hashed_post_state(&parent_storage)?);
719        }
720
721        self.hashed.extend(HashedPostState::from_bundle_state::<KeccakKeyHasher>(&changes.state));
722        Ok(())
723    }
724}
725
726#[cfg(test)]
727mod tests {
728    use super::{
729        apply_precompile_overrides, sanitize_chain, EthSimulateError, ProviderResult,
730        SimulatedState, INTERNAL_ERROR_CODE,
731    };
732    use crate::{cache::db::apply_state_overrides_with_state, error::ToRpcError, EthApiError};
733    use alloy_chains::Chain;
734    use alloy_consensus::Header;
735    use alloy_primitives::{
736        address, keccak256,
737        map::{AddressMap, B256Map},
738        Address, B256, U256,
739    };
740    use alloy_rpc_types_eth::{
741        simulate::SimBlock,
742        state::{AccountOverride, StateOverride},
743        BlockOverrides, TransactionRequest,
744    };
745    use evm2::{
746        ethereum::ethereum_tx_registry,
747        evm::{AccountInfo, CacheDB, EmptyDB, InMemoryDB},
748        BaseEvmTypes, Evm, Precompiles, SpecId,
749    };
750    use reth_evm::BundleState;
751    use reth_primitives_traits::SealedHeader;
752    use reth_storage_api::HashedPostStateProvider;
753    use reth_trie_common::{HashedPostState, KeccakKeyHasher};
754    use std::{
755        collections::BTreeMap,
756        sync::atomic::{AtomicUsize, Ordering},
757    };
758
759    fn test_evm() -> Evm<'static, BaseEvmTypes> {
760        Evm::new(
761            SpecId::PRAGUE,
762            Default::default(),
763            ethereum_tx_registry(SpecId::PRAGUE),
764            EmptyDB::default(),
765            Precompiles::base(SpecId::PRAGUE),
766        )
767    }
768
769    /// Mimics the parent-state storage reads of the database-backed providers and counts them.
770    #[derive(Default)]
771    struct CountingProvider {
772        storage: AddressMap<B256Map<U256>>,
773        scans: AtomicUsize,
774        reads: AtomicUsize,
775    }
776
777    impl CountingProvider {
778        /// Returns the non-zero storage that results from layering `hashed` over the parent state.
779        fn resulting_storage(&self, hashed: &HashedPostState) -> BTreeMap<(B256, B256), U256> {
780            let parent = self.storage.iter().flat_map(|(address, slots)| {
781                slots.iter().map(move |(slot, value)| ((keccak256(address), *slot), *value))
782            });
783            let changes = hashed.storages.iter().flat_map(|(address, storage)| {
784                storage.storage.iter().map(|(slot, value)| ((*address, *slot), *value))
785            });
786            parent
787                .chain(changes)
788                .collect::<BTreeMap<_, _>>()
789                .into_iter()
790                .filter(|(_, v)| !v.is_zero())
791                .collect()
792        }
793    }
794
795    impl HashedPostStateProvider for CountingProvider {
796        fn hashed_post_state(&self, bundle_state: &BundleState) -> ProviderResult<HashedPostState> {
797            let mut hashed =
798                HashedPostState::from_bundle_state::<KeccakKeyHasher>(&bundle_state.state);
799            for (address, account) in &bundle_state.state {
800                if account.was_destroyed() && account.original_info.is_some() {
801                    self.scans.fetch_add(1, Ordering::Relaxed);
802                    let storage = self.storage.get(address).into_iter().flatten();
803                    let slots = &mut hashed.storages.entry(keccak256(address)).or_default().storage;
804                    for (slot, _) in storage {
805                        self.reads.fetch_add(1, Ordering::Relaxed);
806                        slots.entry(*slot).or_insert(U256::ZERO);
807                    }
808                }
809            }
810            Ok(hashed)
811        }
812    }
813
814    #[test]
815    fn nonce_max_value_error_uses_internal_error_code() {
816        let err = EthSimulateError::NonceMaxValue.to_rpc_error();
817
818        assert_eq!(err.code(), INTERNAL_ERROR_CODE);
819        assert_eq!(err.message(), "nonce has max value");
820    }
821
822    #[test]
823    fn block_not_found_error_uses_simulate_code() {
824        let err = EthSimulateError::BlockNotFound { block: 100000.into() }.to_rpc_error();
825
826        assert_eq!(err.code(), -32000);
827        assert_eq!(err.message(), "block not found: 0x186a0");
828    }
829
830    fn parent_at(number: u64, timestamp: u64) -> SealedHeader<Header> {
831        SealedHeader::seal_slow(Header { number, timestamp, ..Default::default() })
832    }
833
834    fn block_with_number(number: u64) -> SimBlock<TransactionRequest> {
835        SimBlock {
836            block_overrides: Some(BlockOverrides {
837                number: Some(U256::from(number)),
838                ..Default::default()
839            }),
840            ..Default::default()
841        }
842    }
843
844    #[test]
845    fn precompile_self_move_requires_existing_precompile() {
846        let address = address!("c100000000000000000000000000000000000000");
847        let mut state_overrides = StateOverride::default();
848        state_overrides.insert(
849            address,
850            AccountOverride { move_precompile_to: Some(address), ..Default::default() },
851        );
852        let mut evm = test_evm();
853
854        let err = apply_precompile_overrides(&state_overrides, &mut evm).unwrap_err();
855
856        assert!(matches!(err, EthSimulateError::NotAPrecompile(addr) if addr == address));
857    }
858
859    #[test]
860    fn precompile_self_move_errors_for_existing_precompile() {
861        let address = Address::with_last_byte(1);
862        let mut state_overrides = StateOverride::default();
863        state_overrides.insert(
864            address,
865            AccountOverride { move_precompile_to: Some(address), ..Default::default() },
866        );
867        let mut evm = test_evm();
868
869        let err = apply_precompile_overrides(&state_overrides, &mut evm).unwrap_err();
870
871        assert!(matches!(err, EthSimulateError::MovePrecompileToSelf(addr) if addr == address));
872    }
873
874    #[test]
875    fn moved_precompile_is_callable() {
876        let source = Address::with_last_byte(1);
877        let dest = address!("0000000000000000000000000000000000123456");
878        let mut state_overrides = StateOverride::default();
879        state_overrides.insert(
880            source,
881            AccountOverride { move_precompile_to: Some(dest), ..Default::default() },
882        );
883        let mut evm = test_evm();
884
885        apply_precompile_overrides(&state_overrides, &mut evm).unwrap();
886
887        let precompiles = evm.precompiles_as::<Precompiles>().unwrap().as_map();
888        assert!(!precompiles.contains(&source));
889        assert!(precompiles.contains(&dest));
890    }
891
892    #[test]
893    fn sanitize_chain_fills_gaps_with_empty_blocks() {
894        // parent at block 5; user requests one block at 8 — sanitize should insert fillers at 6
895        // and 7 before the requested block.
896        let parent = parent_at(5, 100);
897        let blocks = vec![block_with_number(8)];
898
899        let out = sanitize_chain(blocks, &parent, Chain::mainnet().id(), 256).unwrap();
900        assert_eq!(out.len(), 3);
901
902        let numbers: Vec<u64> = out
903            .iter()
904            .map(|b| b.block_overrides.as_ref().unwrap().number.unwrap().try_into().unwrap())
905            .collect();
906        assert_eq!(numbers, vec![6, 7, 8]);
907
908        assert!(out[0].calls.is_empty());
909        assert!(out[1].calls.is_empty());
910
911        // Mainnet hint is 12s, so timestamps should auto-increment from 100 by 12.
912        let times: Vec<u64> =
913            out.iter().map(|b| b.block_overrides.as_ref().unwrap().time.unwrap()).collect();
914        assert_eq!(times, vec![112, 124, 136]);
915    }
916
917    #[test]
918    fn sanitize_chain_defaults_missing_number_and_time() {
919        let parent = parent_at(10, 1000);
920        let blocks: Vec<SimBlock<TransactionRequest>> =
921            vec![SimBlock::default(), SimBlock::default()];
922
923        let out = sanitize_chain(blocks, &parent, Chain::mainnet().id(), 256).unwrap();
924        assert_eq!(out.len(), 2);
925
926        let overrides = out[0].block_overrides.as_ref().unwrap();
927        assert_eq!(overrides.number.unwrap(), U256::from(11));
928        assert_eq!(overrides.time, Some(1012));
929
930        let overrides = out[1].block_overrides.as_ref().unwrap();
931        assert_eq!(overrides.number.unwrap(), U256::from(12));
932        assert_eq!(overrides.time, Some(1024));
933    }
934
935    #[test]
936    fn sanitize_chain_uses_chain_blocktime_hint() {
937        // Optimism has a 2s blocktime hint; filler/auto timestamps should reflect that.
938        let parent = parent_at(0, 0);
939        let blocks = vec![block_with_number(3)];
940
941        let out = sanitize_chain(blocks, &parent, Chain::optimism_mainnet().id(), 256).unwrap();
942        let times: Vec<u64> =
943            out.iter().map(|b| b.block_overrides.as_ref().unwrap().time.unwrap()).collect();
944        assert_eq!(times, vec![2, 4, 6]);
945    }
946
947    #[test]
948    fn sanitize_chain_rounds_subsecond_blocktime_hint_up() {
949        // Arbitrum has a 260ms blocktime hint. Simulated timestamps are second-granular, so this
950        // rounds up to a 1s increment instead of falling back to the default.
951        let parent = parent_at(0, 0);
952        let blocks = vec![block_with_number(3)];
953
954        let out = sanitize_chain(blocks, &parent, Chain::arbitrum_mainnet().id(), 256).unwrap();
955        let times: Vec<u64> =
956            out.iter().map(|b| b.block_overrides.as_ref().unwrap().time.unwrap()).collect();
957        assert_eq!(times, vec![1, 2, 3]);
958    }
959
960    #[test]
961    fn sanitize_chain_falls_back_when_chain_has_no_hint() {
962        // An unknown chain id has no blocktime hint — fall back to the 12s default.
963        let parent = parent_at(0, 0);
964        let blocks = vec![block_with_number(2)];
965
966        let out = sanitize_chain(blocks, &parent, Chain::from_id(123_456_789).id(), 256).unwrap();
967        let times: Vec<u64> =
968            out.iter().map(|b| b.block_overrides.as_ref().unwrap().time.unwrap()).collect();
969        assert_eq!(times, vec![12, 24]);
970    }
971
972    #[test]
973    fn sanitize_chain_rejects_non_increasing_number() {
974        let parent = parent_at(10, 100);
975        let err = sanitize_chain(vec![block_with_number(10)], &parent, Chain::mainnet().id(), 256)
976            .unwrap_err();
977        assert!(matches!(err, EthApiError::Other(_)));
978    }
979
980    #[test]
981    fn sanitize_chain_rejects_timestamp_overflow() {
982        // A block may set any timestamp above its parent's, including `u64::MAX`. The following
983        // block then defaults to `prev + increment`, which must not wrap.
984        let parent = parent_at(0, 0);
985        let blocks: Vec<SimBlock<TransactionRequest>> = vec![
986            SimBlock {
987                block_overrides: Some(BlockOverrides {
988                    time: Some(u64::MAX),
989                    ..Default::default()
990                }),
991                ..Default::default()
992            },
993            SimBlock::default(),
994        ];
995
996        let err = sanitize_chain(blocks, &parent, Chain::mainnet().id(), 256).unwrap_err();
997        assert!(matches!(err, EthApiError::Other(_)));
998    }
999
1000    #[test]
1001    fn sanitize_chain_rejects_filler_timestamp_overflow() {
1002        // Same, but the wrap would happen while generating filler blocks for a number gap.
1003        let parent = parent_at(0, 0);
1004        let blocks = vec![
1005            SimBlock {
1006                block_overrides: Some(BlockOverrides {
1007                    number: Some(U256::ONE),
1008                    time: Some(u64::MAX),
1009                    ..Default::default()
1010                }),
1011                ..Default::default()
1012            },
1013            block_with_number(4),
1014        ];
1015
1016        let err = sanitize_chain(blocks, &parent, Chain::mainnet().id(), 256).unwrap_err();
1017        assert!(matches!(err, EthApiError::Other(_)));
1018    }
1019
1020    #[test]
1021    fn sanitize_chain_enforces_max_blocks() {
1022        let parent = parent_at(0, 0);
1023        let err = sanitize_chain(vec![block_with_number(257)], &parent, Chain::mainnet().id(), 256)
1024            .unwrap_err();
1025        assert!(matches!(err, EthApiError::Other(_)));
1026    }
1027
1028    /// Hashing each block's changes once gives the same state as hashing the cumulative bundle
1029    /// again after every block, without reading the parent storage of destroyed accounts again.
1030    #[test]
1031    fn simulated_state_reads_parent_storage_once() {
1032        let wiped = Address::with_last_byte(1);
1033        let emptied = Address::with_last_byte(2);
1034        let created = Address::with_last_byte(3);
1035        let slot = |n: u64| B256::from(U256::from(n));
1036        let slots = |range: std::ops::Range<u64>| {
1037            range.map(|n| (slot(n), slot(n + 1000))).collect::<Vec<_>>()
1038        };
1039
1040        let mut db = InMemoryDB::default();
1041        let mut provider = CountingProvider::default();
1042        for (address, count) in [(wiped, 100), (emptied, 10)] {
1043            db.insert_account_info(
1044                &address,
1045                AccountInfo { balance: U256::from(5), ..Default::default() },
1046            );
1047            for (key, value) in slots(0..count) {
1048                db.insert_account_storage(
1049                    &address,
1050                    &U256::from_be_bytes(key.0),
1051                    &U256::from_be_bytes(value.0),
1052                );
1053                provider
1054                    .storage
1055                    .entry(address)
1056                    .or_default()
1057                    .insert(keccak256(key), U256::from_be_bytes(value.0));
1058            }
1059        }
1060        let mut db = CacheDB::new(db);
1061
1062        let blocks = [
1063            // Full replacement of an existing account's storage, an emptied account, and a new
1064            // account.
1065            StateOverride::from_iter([
1066                (
1067                    wiped,
1068                    AccountOverride::default()
1069                        .with_state([(slot(1), slot(7)), (slot(500), slot(8))]),
1070                ),
1071                (emptied, AccountOverride::default().with_balance(U256::ZERO)),
1072                (created, AccountOverride::default().with_balance(U256::from(3))),
1073            ]),
1074            // Later blocks touch the same account without wiping it. The new account has no
1075            // parent storage to read when its storage is replaced.
1076            StateOverride::from_iter([
1077                (wiped, AccountOverride::default().with_state_diff([(slot(2), slot(9))])),
1078                (created, AccountOverride::default().with_state([(slot(1), slot(4))])),
1079            ]),
1080            StateOverride::from_iter([(
1081                wiped,
1082                AccountOverride::default().with_balance(U256::from(6)),
1083            )]),
1084            // A second wipe, recreating a slot of the first.
1085            StateOverride::from_iter([(
1086                wiped,
1087                AccountOverride::default().with_state([(slot(500), slot(10))]),
1088            )]),
1089            StateOverride::from_iter([(
1090                wiped,
1091                AccountOverride::default().with_state_diff([(slot(3), slot(11))]),
1092            )]),
1093        ];
1094
1095        let mut simulated = SimulatedState::default();
1096        let mut cumulative = BundleState::default();
1097        for (index, state_overrides) in blocks.into_iter().enumerate() {
1098            let mut changes = BundleState::default();
1099            apply_state_overrides_with_state(state_overrides, &mut db, Some(&mut changes)).unwrap();
1100
1101            simulated.extend(&provider, &changes).unwrap();
1102            // Parent storage is read only when an account existing in the parent is first
1103            // destroyed.
1104            let reads = provider.reads.swap(0, Ordering::Relaxed);
1105            assert_eq!(reads, [110, 0, 0, 0, 0][index], "block {index}");
1106            let scans = provider.scans.swap(0, Ordering::Relaxed);
1107            assert_eq!(scans, [2, 0, 0, 0, 0][index], "block {index}");
1108
1109            cumulative.extend(changes);
1110            let expected = provider.hashed_post_state(&cumulative).unwrap();
1111            provider.reads.store(0, Ordering::Relaxed);
1112            provider.scans.store(0, Ordering::Relaxed);
1113            assert_eq!(simulated.hashed_state().accounts, expected.accounts, "block {index}");
1114            assert_eq!(
1115                provider.resulting_storage(simulated.hashed_state()),
1116                provider.resulting_storage(&expected),
1117                "block {index}"
1118            );
1119        }
1120    }
1121}