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