1use crate::{
4 error::{api::FromEthApiError, FromEvmError, ToRpcError},
5 EthApiError,
6};
7use alloy_consensus::{transaction::TxHashRef, BlockHeader, Transaction as _};
8use alloy_eips::eip2718::WithEncoded;
9use alloy_evm::{block::TxResult, precompiles::PrecompilesMap};
10use alloy_network::{NetworkTransactionBuilder, TransactionBuilder};
11use alloy_rpc_types_eth::{
12 simulate::{SimCallResult, SimulateError, SimulatedBlock},
13 state::StateOverride,
14 BlockTransactionsKind,
15};
16use jsonrpsee_types::ErrorObject;
17use reth_evm::{
18 execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutor},
19 Evm, HaltReasonFor,
20};
21use reth_primitives_traits::{BlockBody as _, BlockTy, NodePrimitives, Recovered, RecoveredBlock};
22use reth_rpc_convert::{RpcBlock, RpcConvert, RpcTxReq};
23use reth_rpc_server_types::result::rpc_err;
24use reth_storage_api::noop::NoopProvider;
25use revm::{
26 context::Block,
27 context_interface::result::ExecutionResult,
28 primitives::{Address, Bytes, TxKind, U256},
29 Database,
30};
31
32pub const SIMULATE_REVERT_CODE: i32 = 3;
38
39pub const SIMULATE_VM_ERROR_CODE: i32 = -32015;
43
44#[derive(Debug, thiserror::Error)]
46pub enum EthSimulateError {
47 #[error("Block gas limit exceeded by the block's transactions")]
49 BlockGasLimitExceeded,
50 #[error("too many blocks")]
52 TooManyBlocks,
53 #[error("Client adjustable limit reached")]
55 GasLimitReached,
56 #[error("block numbers must be in order: {got} <= {parent}")]
58 BlockNumberInvalid {
59 got: u64,
61 parent: u64,
63 },
64 #[error("block timestamps must be in order: {got} <= {parent}")]
66 BlockTimestampInvalid {
67 got: u64,
69 parent: u64,
71 },
72 #[error("nonce too low: next nonce {state}, tx nonce {tx}")]
74 NonceTooLow {
75 tx: u64,
77 state: u64,
79 },
80 #[error("nonce too high")]
82 NonceTooHigh,
83 #[error("max fee per gas less than block base fee")]
85 BaseFeePerGasTooLow,
86 #[error("intrinsic gas too low")]
88 IntrinsicGasTooLow,
89 #[error("insufficient funds for gas * price + value: have {balance} want {cost}")]
91 InsufficientFunds {
92 cost: U256,
94 balance: U256,
96 },
97 #[error("sender is not an EOA")]
99 SenderNotEOA,
100 #[error("max initcode size exceeded")]
102 MaxInitCodeSizeExceeded,
103 #[error("account {0} is not a precompile")]
105 NotAPrecompile(Address),
106}
107
108impl EthSimulateError {
109 pub const fn error_code(&self) -> i32 {
111 match self {
112 Self::NonceTooLow { .. } => -38010,
113 Self::NonceTooHigh => -38011,
114 Self::BaseFeePerGasTooLow => -38012,
115 Self::IntrinsicGasTooLow => -38013,
116 Self::InsufficientFunds { .. } => -38014,
117 Self::BlockGasLimitExceeded => -38015,
118 Self::BlockNumberInvalid { .. } => -38020,
119 Self::BlockTimestampInvalid { .. } => -38021,
120 Self::SenderNotEOA => -38024,
121 Self::MaxInitCodeSizeExceeded => -38025,
122 Self::TooManyBlocks | Self::GasLimitReached => -38026,
123 Self::NotAPrecompile(_) => -32000,
124 }
125 }
126}
127
128impl ToRpcError for EthSimulateError {
129 fn to_rpc_error(&self) -> ErrorObject<'static> {
130 rpc_err(self.error_code(), self.to_string(), None)
131 }
132}
133
134pub fn apply_precompile_overrides(
140 state_overrides: &StateOverride,
141 precompiles: &mut PrecompilesMap,
142) -> Result<(), EthSimulateError> {
143 let moves: Vec<_> = state_overrides
144 .iter()
145 .filter_map(|(source, account_override)| {
146 account_override.move_precompile_to.map(|dest| (*source, dest))
147 })
148 .collect();
149
150 precompiles.move_precompiles(moves).map_err(
151 |alloy_evm::precompiles::MovePrecompileError::NotAPrecompile(addr)| {
152 EthSimulateError::NotAPrecompile(addr)
153 },
154 )?;
155
156 Ok(())
157}
158
159#[expect(clippy::type_complexity)]
166pub fn execute_transactions<S, T>(
167 mut builder: S,
168 calls: Vec<RpcTxReq<T::Network>>,
169 default_gas_limit: u64,
170 chain_id: u64,
171 converter: &T,
172) -> Result<
173 (
174 BlockBuilderOutcome<S::Primitives>,
175 Vec<ExecutionResult<<<S::Executor as BlockExecutor>::Evm as Evm>::HaltReason>>,
176 ),
177 EthApiError,
178>
179where
180 S: BlockBuilder<Executor: BlockExecutor<Evm: Evm<DB: Database<Error: Into<EthApiError>>>>>,
181 T: RpcConvert<Primitives = S::Primitives>,
182{
183 builder.apply_pre_execution_changes()?;
184
185 let mut results = Vec::with_capacity(calls.len());
186 for call in calls {
187 let tx = resolve_transaction(
190 call,
191 default_gas_limit,
192 builder.evm().block().basefee(),
193 chain_id,
194 builder.evm_mut().db_mut(),
195 converter,
196 )?;
197 let tx = WithEncoded::new(Default::default(), tx);
200
201 builder.execute_transaction_with_result_closure(tx, |result| {
202 results.push(result.result().result.clone())
203 })?;
204 }
205
206 let result = builder.finish(NoopProvider::default(), None)?;
208
209 Ok((result, results))
210}
211
212pub fn resolve_transaction<DB: Database, Tx, T>(
219 mut tx: RpcTxReq<T::Network>,
220 default_gas_limit: u64,
221 block_base_fee_per_gas: u64,
222 chain_id: u64,
223 db: &mut DB,
224 converter: &T,
225) -> Result<Recovered<Tx>, EthApiError>
226where
227 DB::Error: Into<EthApiError>,
228 T: RpcConvert<Primitives: NodePrimitives<SignedTx = Tx>>,
229{
230 let tx_type = tx.as_ref().output_tx_type();
233
234 let from = if let Some(from) = tx.as_ref().from() {
235 from
236 } else {
237 tx.as_mut().set_from(Address::ZERO);
238 Address::ZERO
239 };
240
241 if tx.as_ref().nonce().is_none() {
242 tx.as_mut().set_nonce(
243 db.basic(from).map_err(Into::into)?.map(|acc| acc.nonce).unwrap_or_default(),
244 );
245 }
246
247 if tx.as_ref().gas_limit().is_none() {
248 tx.as_mut().set_gas_limit(default_gas_limit);
249 }
250
251 if tx.as_ref().chain_id().is_none() {
252 tx.as_mut().set_chain_id(chain_id);
253 }
254
255 if tx.as_ref().kind().is_none() {
256 tx.as_mut().set_kind(TxKind::Create);
257 }
258
259 if tx.as_ref().output_tx_type_checked().is_none() {
261 if tx_type.is_legacy() || tx_type.is_eip2930() {
262 if tx.as_ref().gas_price().is_none() {
263 tx.as_mut().set_gas_price(block_base_fee_per_gas as u128);
264 }
265 } else {
266 if tx.as_ref().max_fee_per_gas().is_none() {
268 let mut max_fee_per_gas = block_base_fee_per_gas as u128;
269 if let Some(prio_fee) = tx.as_ref().max_priority_fee_per_gas() {
270 max_fee_per_gas = prio_fee.max(max_fee_per_gas);
273 }
274 tx.as_mut().set_max_fee_per_gas(max_fee_per_gas);
275 }
276 if tx.as_ref().max_priority_fee_per_gas().is_none() {
277 tx.as_mut().set_max_priority_fee_per_gas(0);
278 }
279 }
280 }
281
282 let tx =
283 converter.build_simulate_v1_transaction(tx).map_err(|e| EthApiError::other(e.into()))?;
284
285 Ok(Recovered::new_unchecked(tx, from))
286}
287
288pub fn build_simulated_block<Err, T>(
290 block: RecoveredBlock<BlockTy<T::Primitives>>,
291 results: Vec<ExecutionResult<HaltReasonFor<T::Evm>>>,
292 txs_kind: BlockTransactionsKind,
293 converter: &T,
294) -> Result<SimulatedBlock<RpcBlock<T::Network>>, Err>
295where
296 Err: std::error::Error
297 + FromEthApiError
298 + FromEvmError<T::Evm>
299 + From<T::Error>
300 + Into<jsonrpsee_types::ErrorObject<'static>>,
301 T: RpcConvert,
302{
303 let mut calls: Vec<SimCallResult> = Vec::with_capacity(results.len());
304
305 let mut log_index = 0;
306 for (index, (result, tx)) in results.into_iter().zip(block.body().transactions()).enumerate() {
307 let call = match result {
308 ExecutionResult::Halt { reason, gas, .. } => {
309 let error = Err::from_evm_halt(reason, tx.gas_limit());
310 SimCallResult {
311 return_data: Bytes::new(),
312 error: Some(SimulateError {
313 message: error.to_string(),
314 code: SIMULATE_VM_ERROR_CODE,
315 ..SimulateError::invalid_params()
316 }),
317 gas_used: gas.used(),
318 logs: Vec::new(),
319 status: false,
320 ..Default::default()
321 }
322 }
323 ExecutionResult::Revert { output, gas, .. } => {
324 let error = Err::from_revert(output.clone());
325 SimCallResult {
326 return_data: output,
327 error: Some(SimulateError {
328 message: error.to_string(),
329 code: SIMULATE_REVERT_CODE,
330 ..SimulateError::invalid_params()
331 }),
332 gas_used: gas.used(),
333 status: false,
334 logs: Vec::new(),
335 ..Default::default()
336 }
337 }
338 ExecutionResult::Success { output, gas, logs, .. } => SimCallResult {
339 return_data: output.into_data(),
340 error: None,
341 gas_used: gas.used(),
342 logs: logs
343 .into_iter()
344 .map(|log| {
345 log_index += 1;
346 alloy_rpc_types_eth::Log {
347 inner: log,
348 log_index: Some(log_index - 1),
349 transaction_index: Some(index as u64),
350 transaction_hash: Some(*tx.tx_hash()),
351 block_hash: Some(block.hash()),
352 block_number: Some(block.header().number()),
353 block_timestamp: Some(block.header().timestamp()),
354 ..Default::default()
355 }
356 })
357 .collect(),
358 status: true,
359 ..Default::default()
360 },
361 };
362
363 calls.push(call);
364 }
365
366 let block = block.into_rpc_block(
367 txs_kind,
368 |tx, tx_info| converter.fill(tx, tx_info),
369 |header, size| converter.convert_header(header, size),
370 )?;
371 Ok(SimulatedBlock { inner: block, calls })
372}