1use crate::{
4 error::{
5 api::{FromEthApiError, FromEvmHalt},
6 ToRpcError,
7 },
8 EthApiError, RevertError,
9};
10use alloy_consensus::{transaction::TxHashRef, BlockHeader, Transaction as _};
11use alloy_eips::eip2718::WithEncoded;
12use alloy_network::TransactionBuilder;
13use alloy_rpc_types_eth::{
14 simulate::{SimCallResult, SimulateError, SimulatedBlock},
15 BlockTransactionsKind,
16};
17use jsonrpsee_types::ErrorObject;
18use reth_evm::{
19 execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutor},
20 Evm,
21};
22use reth_primitives_traits::{BlockBody as _, BlockTy, NodePrimitives, Recovered, RecoveredBlock};
23use reth_rpc_convert::{RpcBlock, RpcConvert, RpcTxReq};
24use reth_rpc_server_types::result::rpc_err;
25use reth_storage_api::noop::NoopProvider;
26use revm::{
27 context_interface::result::ExecutionResult,
28 primitives::{Address, Bytes, TxKind},
29 Database,
30};
31
32#[derive(Debug, thiserror::Error)]
34pub enum EthSimulateError {
35 #[error("Block gas limit exceeded by the block's transactions")]
37 BlockGasLimitExceeded,
38 #[error("Client adjustable limit reached")]
40 GasLimitReached,
41}
42
43impl EthSimulateError {
44 const fn error_code(&self) -> i32 {
45 match self {
46 Self::BlockGasLimitExceeded => -38015,
47 Self::GasLimitReached => -38026,
48 }
49 }
50}
51
52impl ToRpcError for EthSimulateError {
53 fn to_rpc_error(&self) -> ErrorObject<'static> {
54 rpc_err(self.error_code(), self.to_string(), None)
55 }
56}
57
58#[expect(clippy::type_complexity)]
65pub fn execute_transactions<S, T>(
66 mut builder: S,
67 calls: Vec<RpcTxReq<T::Network>>,
68 default_gas_limit: u64,
69 chain_id: u64,
70 tx_resp_builder: &T,
71) -> Result<
72 (
73 BlockBuilderOutcome<S::Primitives>,
74 Vec<ExecutionResult<<<S::Executor as BlockExecutor>::Evm as Evm>::HaltReason>>,
75 ),
76 EthApiError,
77>
78where
79 S: BlockBuilder<Executor: BlockExecutor<Evm: Evm<DB: Database<Error: Into<EthApiError>>>>>,
80 T: RpcConvert<Primitives = S::Primitives>,
81{
82 builder.apply_pre_execution_changes()?;
83
84 let mut results = Vec::with_capacity(calls.len());
85 for call in calls {
86 let tx = resolve_transaction(
89 call,
90 default_gas_limit,
91 builder.evm().block().basefee,
92 chain_id,
93 builder.evm_mut().db_mut(),
94 tx_resp_builder,
95 )?;
96 let tx = WithEncoded::new(Default::default(), tx);
99
100 builder
101 .execute_transaction_with_result_closure(tx, |result| results.push(result.clone()))?;
102 }
103
104 let result = builder.finish(NoopProvider::default())?;
106
107 Ok((result, results))
108}
109
110pub fn resolve_transaction<DB: Database, Tx, T>(
117 mut tx: RpcTxReq<T::Network>,
118 default_gas_limit: u64,
119 block_base_fee_per_gas: u64,
120 chain_id: u64,
121 db: &mut DB,
122 tx_resp_builder: &T,
123) -> Result<Recovered<Tx>, EthApiError>
124where
125 DB::Error: Into<EthApiError>,
126 T: RpcConvert<Primitives: NodePrimitives<SignedTx = Tx>>,
127{
128 let tx_type = tx.as_ref().output_tx_type();
131
132 let from = if let Some(from) = tx.as_ref().from() {
133 from
134 } else {
135 tx.as_mut().set_from(Address::ZERO);
136 Address::ZERO
137 };
138
139 if tx.as_ref().nonce().is_none() {
140 tx.as_mut().set_nonce(
141 db.basic(from).map_err(Into::into)?.map(|acc| acc.nonce).unwrap_or_default(),
142 );
143 }
144
145 if tx.as_ref().gas_limit().is_none() {
146 tx.as_mut().set_gas_limit(default_gas_limit);
147 }
148
149 if tx.as_ref().chain_id().is_none() {
150 tx.as_mut().set_chain_id(chain_id);
151 }
152
153 if tx.as_ref().kind().is_none() {
154 tx.as_mut().set_kind(TxKind::Create);
155 }
156
157 if tx.as_ref().output_tx_type_checked().is_none() {
159 if tx_type.is_legacy() || tx_type.is_eip2930() {
160 if tx.as_ref().gas_price().is_none() {
161 tx.as_mut().set_gas_price(block_base_fee_per_gas as u128);
162 }
163 } else {
164 if tx.as_ref().max_fee_per_gas().is_none() {
166 let mut max_fee_per_gas = block_base_fee_per_gas as u128;
167 if let Some(prio_fee) = tx.as_ref().max_priority_fee_per_gas() {
168 max_fee_per_gas = prio_fee.max(max_fee_per_gas);
171 }
172 tx.as_mut().set_max_fee_per_gas(max_fee_per_gas);
173 }
174 if tx.as_ref().max_priority_fee_per_gas().is_none() {
175 tx.as_mut().set_max_priority_fee_per_gas(0);
176 }
177 }
178 }
179
180 let tx = tx_resp_builder
181 .build_simulate_v1_transaction(tx)
182 .map_err(|e| EthApiError::other(e.into()))?;
183
184 Ok(Recovered::new_unchecked(tx, from))
185}
186
187pub fn build_simulated_block<T, Halt: Clone>(
189 block: RecoveredBlock<BlockTy<T::Primitives>>,
190 results: Vec<ExecutionResult<Halt>>,
191 txs_kind: BlockTransactionsKind,
192 tx_resp_builder: &T,
193) -> Result<SimulatedBlock<RpcBlock<T::Network>>, T::Error>
194where
195 T: RpcConvert<Error: FromEthApiError + FromEvmHalt<Halt>>,
196{
197 let mut calls: Vec<SimCallResult> = Vec::with_capacity(results.len());
198
199 let mut log_index = 0;
200 for (index, (result, tx)) in results.into_iter().zip(block.body().transactions()).enumerate() {
201 let call = match result {
202 ExecutionResult::Halt { reason, gas_used } => {
203 let error = T::Error::from_evm_halt(reason, tx.gas_limit());
204 SimCallResult {
205 return_data: Bytes::new(),
206 error: Some(SimulateError {
207 message: error.to_string(),
208 code: error.into().code(),
209 }),
210 gas_used,
211 logs: Vec::new(),
212 status: false,
213 }
214 }
215 ExecutionResult::Revert { output, gas_used } => {
216 let error = RevertError::new(output.clone());
217 SimCallResult {
218 return_data: output,
219 error: Some(SimulateError {
220 code: error.error_code(),
221 message: error.to_string(),
222 }),
223 gas_used,
224 status: false,
225 logs: Vec::new(),
226 }
227 }
228 ExecutionResult::Success { output, gas_used, logs, .. } => SimCallResult {
229 return_data: output.into_data(),
230 error: None,
231 gas_used,
232 logs: logs
233 .into_iter()
234 .map(|log| {
235 log_index += 1;
236 alloy_rpc_types_eth::Log {
237 inner: log,
238 log_index: Some(log_index - 1),
239 transaction_index: Some(index as u64),
240 transaction_hash: Some(*tx.tx_hash()),
241 block_number: Some(block.header().number()),
242 block_timestamp: Some(block.header().timestamp()),
243 ..Default::default()
244 }
245 })
246 .collect(),
247 status: true,
248 },
249 };
250
251 calls.push(call);
252 }
253
254 let block = block.into_rpc_block(
255 txs_kind,
256 |tx, tx_info| tx_resp_builder.fill(tx, tx_info),
257 |header, size| tx_resp_builder.convert_header(header, size),
258 )?;
259 Ok(SimulatedBlock { inner: block, calls })
260}