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 = 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
202 .execute_transaction_with_result_closure(tx, |result| results.push(result.clone()))?;
203 }
204
205 let result = builder.finish(NoopProvider::default())?;
207
208 Ok((result, results))
209}
210
211pub fn resolve_transaction<DB: Database, Tx, T>(
218 mut tx: RpcTxReq<T::Network>,
219 default_gas_limit: u64,
220 block_base_fee_per_gas: u64,
221 chain_id: u64,
222 db: &mut DB,
223 converter: &T,
224) -> Result<Recovered<Tx>, EthApiError>
225where
226 DB::Error: Into<EthApiError>,
227 T: RpcConvert<Primitives: NodePrimitives<SignedTx = Tx>>,
228{
229 let tx_type = tx.as_ref().output_tx_type();
232
233 let from = if let Some(from) = tx.as_ref().from() {
234 from
235 } else {
236 tx.as_mut().set_from(Address::ZERO);
237 Address::ZERO
238 };
239
240 if tx.as_ref().nonce().is_none() {
241 tx.as_mut().set_nonce(
242 db.basic(from).map_err(Into::into)?.map(|acc| acc.nonce).unwrap_or_default(),
243 );
244 }
245
246 if tx.as_ref().gas_limit().is_none() {
247 tx.as_mut().set_gas_limit(default_gas_limit);
248 }
249
250 if tx.as_ref().chain_id().is_none() {
251 tx.as_mut().set_chain_id(chain_id);
252 }
253
254 if tx.as_ref().kind().is_none() {
255 tx.as_mut().set_kind(TxKind::Create);
256 }
257
258 if tx.as_ref().output_tx_type_checked().is_none() {
260 if tx_type.is_legacy() || tx_type.is_eip2930() {
261 if tx.as_ref().gas_price().is_none() {
262 tx.as_mut().set_gas_price(block_base_fee_per_gas as u128);
263 }
264 } else {
265 if tx.as_ref().max_fee_per_gas().is_none() {
267 let mut max_fee_per_gas = block_base_fee_per_gas as u128;
268 if let Some(prio_fee) = tx.as_ref().max_priority_fee_per_gas() {
269 max_fee_per_gas = prio_fee.max(max_fee_per_gas);
272 }
273 tx.as_mut().set_max_fee_per_gas(max_fee_per_gas);
274 }
275 if tx.as_ref().max_priority_fee_per_gas().is_none() {
276 tx.as_mut().set_max_priority_fee_per_gas(0);
277 }
278 }
279 }
280
281 let tx =
282 converter.build_simulate_v1_transaction(tx).map_err(|e| EthApiError::other(e.into()))?;
283
284 Ok(Recovered::new_unchecked(tx, from))
285}
286
287pub fn build_simulated_block<Err, T>(
289 block: RecoveredBlock<BlockTy<T::Primitives>>,
290 results: Vec<ExecutionResult<HaltReasonFor<T::Evm>>>,
291 txs_kind: BlockTransactionsKind,
292 converter: &T,
293) -> Result<SimulatedBlock<RpcBlock<T::Network>>, Err>
294where
295 Err: std::error::Error
296 + FromEthApiError
297 + FromEvmError<T::Evm>
298 + From<T::Error>
299 + Into<jsonrpsee_types::ErrorObject<'static>>,
300 T: RpcConvert,
301{
302 let mut calls: Vec<SimCallResult> = Vec::with_capacity(results.len());
303
304 let mut log_index = 0;
305 for (index, (result, tx)) in results.into_iter().zip(block.body().transactions()).enumerate() {
306 let call = match result {
307 ExecutionResult::Halt { reason, gas, .. } => {
308 let error = Err::from_evm_halt(reason, tx.gas_limit());
309 #[allow(clippy::needless_update)]
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 #[allow(clippy::needless_update)]
326 SimCallResult {
327 return_data: output,
328 error: Some(SimulateError {
329 message: error.to_string(),
330 code: SIMULATE_REVERT_CODE,
331 ..SimulateError::invalid_params()
332 }),
333 gas_used: gas.used(),
334 status: false,
335 logs: Vec::new(),
336 ..Default::default()
337 }
338 }
339 ExecutionResult::Success { output, gas, logs, .. } =>
340 {
341 #[allow(clippy::needless_update)]
342 SimCallResult {
343 return_data: output.into_data(),
344 error: None,
345 gas_used: gas.used(),
346 logs: logs
347 .into_iter()
348 .map(|log| {
349 log_index += 1;
350 alloy_rpc_types_eth::Log {
351 inner: log,
352 log_index: Some(log_index - 1),
353 transaction_index: Some(index as u64),
354 transaction_hash: Some(*tx.tx_hash()),
355 block_hash: Some(block.hash()),
356 block_number: Some(block.header().number()),
357 block_timestamp: Some(block.header().timestamp()),
358 ..Default::default()
359 }
360 })
361 .collect(),
362 status: true,
363 ..Default::default()
364 }
365 }
366 };
367
368 calls.push(call);
369 }
370
371 let block = block.into_rpc_block(
372 txs_kind,
373 |tx, tx_info| converter.fill(tx, tx_info),
374 |header, size| converter.convert_header(header, size),
375 )?;
376 Ok(SimulatedBlock { inner: block, calls })
377}