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::precompiles::PrecompilesMap;
10use alloy_network::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 = -32000;
36
37pub const SIMULATE_VM_ERROR_CODE: i32 = -32015;
41
42#[derive(Debug, thiserror::Error)]
44pub enum EthSimulateError {
45 #[error("Block gas limit exceeded by the block's transactions")]
47 BlockGasLimitExceeded,
48 #[error("Client adjustable limit reached")]
50 GasLimitReached,
51 #[error("block numbers must be in order: {got} <= {parent}")]
53 BlockNumberInvalid {
54 got: u64,
56 parent: u64,
58 },
59 #[error("block timestamps must be in order: {got} <= {parent}")]
61 BlockTimestampInvalid {
62 got: u64,
64 parent: u64,
66 },
67 #[error("nonce too low: next nonce {state}, tx nonce {tx}")]
69 NonceTooLow {
70 tx: u64,
72 state: u64,
74 },
75 #[error("nonce too high")]
77 NonceTooHigh,
78 #[error("max fee per gas less than block base fee")]
80 BaseFeePerGasTooLow,
81 #[error("intrinsic gas too low")]
83 IntrinsicGasTooLow,
84 #[error("insufficient funds for gas * price + value: have {balance} want {cost}")]
86 InsufficientFunds {
87 cost: U256,
89 balance: U256,
91 },
92 #[error("sender is not an EOA")]
94 SenderNotEOA,
95 #[error("max initcode size exceeded")]
97 MaxInitCodeSizeExceeded,
98 #[error("MovePrecompileToAddress referenced itself")]
100 PrecompileSelfReference,
101 #[error("Multiple MovePrecompileToAddress referencing the same address")]
103 PrecompileDuplicateAddress,
104 #[error("account {0} is not a precompile")]
106 NotAPrecompile(Address),
107}
108
109impl EthSimulateError {
110 pub const fn error_code(&self) -> i32 {
112 match self {
113 Self::NonceTooLow { .. } => -38010,
114 Self::NonceTooHigh => -38011,
115 Self::BaseFeePerGasTooLow => -38012,
116 Self::IntrinsicGasTooLow => -38013,
117 Self::InsufficientFunds { .. } => -38014,
118 Self::BlockGasLimitExceeded => -38015,
119 Self::BlockNumberInvalid { .. } => -38020,
120 Self::BlockTimestampInvalid { .. } => -38021,
121 Self::PrecompileSelfReference => -38022,
122 Self::PrecompileDuplicateAddress => -38023,
123 Self::SenderNotEOA => -38024,
124 Self::MaxInitCodeSizeExceeded => -38025,
125 Self::GasLimitReached => -38026,
126 Self::NotAPrecompile(_) => -32000,
127 }
128 }
129}
130
131impl ToRpcError for EthSimulateError {
132 fn to_rpc_error(&self) -> ErrorObject<'static> {
133 rpc_err(self.error_code(), self.to_string(), None)
134 }
135}
136
137pub fn apply_precompile_overrides(
160 state_overrides: &StateOverride,
161 precompiles: &mut PrecompilesMap,
162) -> Result<(), EthSimulateError> {
163 use alloy_evm::precompiles::DynPrecompile;
164
165 let moves: Vec<_> = state_overrides
166 .iter()
167 .filter_map(|(source, account_override)| {
168 account_override.move_precompile_to.map(|dest| (*source, dest))
169 })
170 .collect();
171
172 if moves.is_empty() {
173 return Ok(());
174 }
175
176 for (source, _) in &moves {
177 if precompiles.get(source).is_none() {
178 return Err(EthSimulateError::NotAPrecompile(*source));
179 }
180 }
181
182 let mut extracted: Vec<(Address, Address, DynPrecompile)> = Vec::with_capacity(moves.len());
183
184 for (source, dest) in moves {
185 if source == dest {
186 continue;
187 }
188
189 let mut found_precompile: Option<DynPrecompile> = None;
190 precompiles.apply_precompile(&source, |existing| {
191 found_precompile = existing;
192 None
193 });
194
195 if let Some(precompile) = found_precompile {
196 extracted.push((source, dest, precompile));
197 }
198 }
199
200 for (_, dest, precompile) in extracted {
201 precompiles.apply_precompile(&dest, |_| Some(precompile));
202 }
203
204 Ok(())
205}
206
207#[expect(clippy::type_complexity)]
214pub fn execute_transactions<S, T>(
215 mut builder: S,
216 calls: Vec<RpcTxReq<T::Network>>,
217 default_gas_limit: u64,
218 chain_id: u64,
219 converter: &T,
220) -> Result<
221 (
222 BlockBuilderOutcome<S::Primitives>,
223 Vec<ExecutionResult<<<S::Executor as BlockExecutor>::Evm as Evm>::HaltReason>>,
224 ),
225 EthApiError,
226>
227where
228 S: BlockBuilder<Executor: BlockExecutor<Evm: Evm<DB: Database<Error: Into<EthApiError>>>>>,
229 T: RpcConvert<Primitives = S::Primitives>,
230{
231 builder.apply_pre_execution_changes()?;
232
233 let mut results = Vec::with_capacity(calls.len());
234 for call in calls {
235 let tx = resolve_transaction(
238 call,
239 default_gas_limit,
240 builder.evm().block().basefee(),
241 chain_id,
242 builder.evm_mut().db_mut(),
243 converter,
244 )?;
245 let tx = WithEncoded::new(Default::default(), tx);
248
249 builder
250 .execute_transaction_with_result_closure(tx, |result| results.push(result.clone()))?;
251 }
252
253 let result = builder.finish(NoopProvider::default())?;
255
256 Ok((result, results))
257}
258
259pub fn resolve_transaction<DB: Database, Tx, T>(
266 mut tx: RpcTxReq<T::Network>,
267 default_gas_limit: u64,
268 block_base_fee_per_gas: u64,
269 chain_id: u64,
270 db: &mut DB,
271 converter: &T,
272) -> Result<Recovered<Tx>, EthApiError>
273where
274 DB::Error: Into<EthApiError>,
275 T: RpcConvert<Primitives: NodePrimitives<SignedTx = Tx>>,
276{
277 let tx_type = tx.as_ref().output_tx_type();
280
281 let from = if let Some(from) = tx.as_ref().from() {
282 from
283 } else {
284 tx.as_mut().set_from(Address::ZERO);
285 Address::ZERO
286 };
287
288 if tx.as_ref().nonce().is_none() {
289 tx.as_mut().set_nonce(
290 db.basic(from).map_err(Into::into)?.map(|acc| acc.nonce).unwrap_or_default(),
291 );
292 }
293
294 if tx.as_ref().gas_limit().is_none() {
295 tx.as_mut().set_gas_limit(default_gas_limit);
296 }
297
298 if tx.as_ref().chain_id().is_none() {
299 tx.as_mut().set_chain_id(chain_id);
300 }
301
302 if tx.as_ref().kind().is_none() {
303 tx.as_mut().set_kind(TxKind::Create);
304 }
305
306 if tx.as_ref().output_tx_type_checked().is_none() {
308 if tx_type.is_legacy() || tx_type.is_eip2930() {
309 if tx.as_ref().gas_price().is_none() {
310 tx.as_mut().set_gas_price(block_base_fee_per_gas as u128);
311 }
312 } else {
313 if tx.as_ref().max_fee_per_gas().is_none() {
315 let mut max_fee_per_gas = block_base_fee_per_gas as u128;
316 if let Some(prio_fee) = tx.as_ref().max_priority_fee_per_gas() {
317 max_fee_per_gas = prio_fee.max(max_fee_per_gas);
320 }
321 tx.as_mut().set_max_fee_per_gas(max_fee_per_gas);
322 }
323 if tx.as_ref().max_priority_fee_per_gas().is_none() {
324 tx.as_mut().set_max_priority_fee_per_gas(0);
325 }
326 }
327 }
328
329 let tx =
330 converter.build_simulate_v1_transaction(tx).map_err(|e| EthApiError::other(e.into()))?;
331
332 Ok(Recovered::new_unchecked(tx, from))
333}
334
335pub fn build_simulated_block<Err, T>(
337 block: RecoveredBlock<BlockTy<T::Primitives>>,
338 results: Vec<ExecutionResult<HaltReasonFor<T::Evm>>>,
339 txs_kind: BlockTransactionsKind,
340 converter: &T,
341) -> Result<SimulatedBlock<RpcBlock<T::Network>>, Err>
342where
343 Err: std::error::Error
344 + FromEthApiError
345 + FromEvmError<T::Evm>
346 + From<T::Error>
347 + Into<jsonrpsee_types::ErrorObject<'static>>,
348 T: RpcConvert,
349{
350 let mut calls: Vec<SimCallResult> = Vec::with_capacity(results.len());
351
352 let mut log_index = 0;
353 for (index, (result, tx)) in results.into_iter().zip(block.body().transactions()).enumerate() {
354 let call = match result {
355 ExecutionResult::Halt { reason, gas_used } => {
356 let error = Err::from_evm_halt(reason, tx.gas_limit());
357 #[allow(clippy::needless_update)]
358 SimCallResult {
359 return_data: Bytes::new(),
360 error: Some(SimulateError {
361 message: error.to_string(),
362 code: SIMULATE_VM_ERROR_CODE,
363 ..SimulateError::invalid_params()
364 }),
365 gas_used,
366 logs: Vec::new(),
367 status: false,
368 }
369 }
370 ExecutionResult::Revert { output, gas_used } => {
371 let error = Err::from_revert(output.clone());
372 #[allow(clippy::needless_update)]
373 SimCallResult {
374 return_data: output,
375 error: Some(SimulateError {
376 message: error.to_string(),
377 code: SIMULATE_REVERT_CODE,
378 ..SimulateError::invalid_params()
379 }),
380 gas_used,
381 status: false,
382 logs: Vec::new(),
383 }
384 }
385 ExecutionResult::Success { output, gas_used, logs, .. } => SimCallResult {
386 return_data: output.into_data(),
387 error: None,
388 gas_used,
389 logs: logs
390 .into_iter()
391 .map(|log| {
392 log_index += 1;
393 alloy_rpc_types_eth::Log {
394 inner: log,
395 log_index: Some(log_index - 1),
396 transaction_index: Some(index as u64),
397 transaction_hash: Some(*tx.tx_hash()),
398 block_hash: Some(block.hash()),
399 block_number: Some(block.header().number()),
400 block_timestamp: Some(block.header().timestamp()),
401 ..Default::default()
402 }
403 })
404 .collect(),
405 status: true,
406 },
407 };
408
409 calls.push(call);
410 }
411
412 let block = block.into_rpc_block(
413 txs_kind,
414 |tx, tx_info| converter.fill(tx, tx_info),
415 |header, size| converter.convert_header(header, size),
416 )?;
417 Ok(SimulatedBlock { inner: block, calls })
418}