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_network::TransactionBuilder;
10use alloy_rpc_types_eth::{
11 simulate::{SimCallResult, SimulateError, SimulatedBlock},
12 BlockTransactionsKind,
13};
14use jsonrpsee_types::ErrorObject;
15use reth_evm::{
16 execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutor},
17 Evm, HaltReasonFor,
18};
19use reth_primitives_traits::{BlockBody as _, BlockTy, NodePrimitives, Recovered, RecoveredBlock};
20use reth_rpc_convert::{RpcBlock, RpcConvert, RpcTxReq};
21use reth_rpc_server_types::result::rpc_err;
22use reth_storage_api::noop::NoopProvider;
23use revm::{
24 context::Block,
25 context_interface::result::ExecutionResult,
26 primitives::{Address, Bytes, TxKind, U256},
27 Database,
28};
29
30#[derive(Debug, thiserror::Error)]
32pub enum EthSimulateError {
33 #[error("Block gas limit exceeded by the block's transactions")]
35 BlockGasLimitExceeded,
36 #[error("Client adjustable limit reached")]
38 GasLimitReached,
39 #[error("Block number in sequence did not increase")]
41 BlockNumberInvalid,
42 #[error("Block timestamp in sequence did not increase")]
44 BlockTimestampInvalid,
45 #[error("nonce too low: next nonce {state}, tx nonce {tx}")]
47 NonceTooLow {
48 tx: u64,
50 state: u64,
52 },
53 #[error("nonce too high")]
55 NonceTooHigh,
56 #[error("max fee per gas less than block base fee")]
58 BaseFeePerGasTooLow,
59 #[error("intrinsic gas too low")]
61 IntrinsicGasTooLow,
62 #[error("insufficient funds for gas * price + value: have {balance} want {cost}")]
64 InsufficientFunds {
65 cost: U256,
67 balance: U256,
69 },
70 #[error("sender is not an EOA")]
72 SenderNotEOA,
73 #[error("max initcode size exceeded")]
75 MaxInitCodeSizeExceeded,
76 #[error("MovePrecompileToAddress referenced itself")]
78 PrecompileSelfReference,
79 #[error("Multiple MovePrecompileToAddress referencing the same address")]
81 PrecompileDuplicateAddress,
82}
83
84impl EthSimulateError {
85 pub const fn error_code(&self) -> i32 {
87 match self {
88 Self::NonceTooLow { .. } => -38010,
89 Self::NonceTooHigh => -38011,
90 Self::BaseFeePerGasTooLow => -38012,
91 Self::IntrinsicGasTooLow => -38013,
92 Self::InsufficientFunds { .. } => -38014,
93 Self::BlockGasLimitExceeded => -38015,
94 Self::BlockNumberInvalid => -38020,
95 Self::BlockTimestampInvalid => -38021,
96 Self::PrecompileSelfReference => -38022,
97 Self::PrecompileDuplicateAddress => -38023,
98 Self::SenderNotEOA => -38024,
99 Self::MaxInitCodeSizeExceeded => -38025,
100 Self::GasLimitReached => -38026,
101 }
102 }
103}
104
105impl ToRpcError for EthSimulateError {
106 fn to_rpc_error(&self) -> ErrorObject<'static> {
107 rpc_err(self.error_code(), self.to_string(), None)
108 }
109}
110
111#[expect(clippy::type_complexity)]
118pub fn execute_transactions<S, T>(
119 mut builder: S,
120 calls: Vec<RpcTxReq<T::Network>>,
121 default_gas_limit: u64,
122 chain_id: u64,
123 converter: &T,
124) -> Result<
125 (
126 BlockBuilderOutcome<S::Primitives>,
127 Vec<ExecutionResult<<<S::Executor as BlockExecutor>::Evm as Evm>::HaltReason>>,
128 ),
129 EthApiError,
130>
131where
132 S: BlockBuilder<Executor: BlockExecutor<Evm: Evm<DB: Database<Error: Into<EthApiError>>>>>,
133 T: RpcConvert<Primitives = S::Primitives>,
134{
135 builder.apply_pre_execution_changes()?;
136
137 let mut results = Vec::with_capacity(calls.len());
138 for call in calls {
139 let tx = resolve_transaction(
142 call,
143 default_gas_limit,
144 builder.evm().block().basefee(),
145 chain_id,
146 builder.evm_mut().db_mut(),
147 converter,
148 )?;
149 let tx = WithEncoded::new(Default::default(), tx);
152
153 builder
154 .execute_transaction_with_result_closure(tx, |result| results.push(result.clone()))?;
155 }
156
157 let result = builder.finish(NoopProvider::default())?;
159
160 Ok((result, results))
161}
162
163pub fn resolve_transaction<DB: Database, Tx, T>(
170 mut tx: RpcTxReq<T::Network>,
171 default_gas_limit: u64,
172 block_base_fee_per_gas: u64,
173 chain_id: u64,
174 db: &mut DB,
175 converter: &T,
176) -> Result<Recovered<Tx>, EthApiError>
177where
178 DB::Error: Into<EthApiError>,
179 T: RpcConvert<Primitives: NodePrimitives<SignedTx = Tx>>,
180{
181 let tx_type = tx.as_ref().output_tx_type();
184
185 let from = if let Some(from) = tx.as_ref().from() {
186 from
187 } else {
188 tx.as_mut().set_from(Address::ZERO);
189 Address::ZERO
190 };
191
192 if tx.as_ref().nonce().is_none() {
193 tx.as_mut().set_nonce(
194 db.basic(from).map_err(Into::into)?.map(|acc| acc.nonce).unwrap_or_default(),
195 );
196 }
197
198 if tx.as_ref().gas_limit().is_none() {
199 tx.as_mut().set_gas_limit(default_gas_limit);
200 }
201
202 if tx.as_ref().chain_id().is_none() {
203 tx.as_mut().set_chain_id(chain_id);
204 }
205
206 if tx.as_ref().kind().is_none() {
207 tx.as_mut().set_kind(TxKind::Create);
208 }
209
210 if tx.as_ref().output_tx_type_checked().is_none() {
212 if tx_type.is_legacy() || tx_type.is_eip2930() {
213 if tx.as_ref().gas_price().is_none() {
214 tx.as_mut().set_gas_price(block_base_fee_per_gas as u128);
215 }
216 } else {
217 if tx.as_ref().max_fee_per_gas().is_none() {
219 let mut max_fee_per_gas = block_base_fee_per_gas as u128;
220 if let Some(prio_fee) = tx.as_ref().max_priority_fee_per_gas() {
221 max_fee_per_gas = prio_fee.max(max_fee_per_gas);
224 }
225 tx.as_mut().set_max_fee_per_gas(max_fee_per_gas);
226 }
227 if tx.as_ref().max_priority_fee_per_gas().is_none() {
228 tx.as_mut().set_max_priority_fee_per_gas(0);
229 }
230 }
231 }
232
233 let tx =
234 converter.build_simulate_v1_transaction(tx).map_err(|e| EthApiError::other(e.into()))?;
235
236 Ok(Recovered::new_unchecked(tx, from))
237}
238
239pub fn build_simulated_block<Err, T>(
241 block: RecoveredBlock<BlockTy<T::Primitives>>,
242 results: Vec<ExecutionResult<HaltReasonFor<T::Evm>>>,
243 txs_kind: BlockTransactionsKind,
244 converter: &T,
245) -> Result<SimulatedBlock<RpcBlock<T::Network>>, Err>
246where
247 Err: std::error::Error
248 + FromEthApiError
249 + FromEvmError<T::Evm>
250 + From<T::Error>
251 + Into<jsonrpsee_types::ErrorObject<'static>>,
252 T: RpcConvert,
253{
254 let mut calls: Vec<SimCallResult> = Vec::with_capacity(results.len());
255
256 let mut log_index = 0;
257 for (index, (result, tx)) in results.into_iter().zip(block.body().transactions()).enumerate() {
258 let call = match result {
259 ExecutionResult::Halt { reason, gas_used } => {
260 let error = Err::from_evm_halt(reason, tx.gas_limit());
261 SimCallResult {
262 return_data: Bytes::new(),
263 error: Some(SimulateError {
264 message: error.to_string(),
265 code: error.into().code(),
266 }),
267 gas_used,
268 logs: Vec::new(),
269 status: false,
270 }
271 }
272 ExecutionResult::Revert { output, gas_used } => {
273 let error = Err::from_revert(output.clone());
274 SimCallResult {
275 return_data: output,
276 error: Some(SimulateError {
277 message: error.to_string(),
278 code: error.into().code(),
279 }),
280 gas_used,
281 status: false,
282 logs: Vec::new(),
283 }
284 }
285 ExecutionResult::Success { output, gas_used, logs, .. } => SimCallResult {
286 return_data: output.into_data(),
287 error: None,
288 gas_used,
289 logs: logs
290 .into_iter()
291 .map(|log| {
292 log_index += 1;
293 alloy_rpc_types_eth::Log {
294 inner: log,
295 log_index: Some(log_index - 1),
296 transaction_index: Some(index as u64),
297 transaction_hash: Some(*tx.tx_hash()),
298 block_number: Some(block.header().number()),
299 block_timestamp: Some(block.header().timestamp()),
300 ..Default::default()
301 }
302 })
303 .collect(),
304 status: true,
305 },
306 };
307
308 calls.push(call);
309 }
310
311 let block = block.into_rpc_block(
312 txs_kind,
313 |tx, tx_info| converter.fill(tx, tx_info),
314 |header, size| converter.convert_header(header, size),
315 )?;
316 Ok(SimulatedBlock { inner: block, calls })
317}