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