1use alloy_consensus::{BlockHeader, Transaction as _, TxType};
4use alloy_rpc_types_eth::{
5 simulate::{SimCallResult, SimulateError, SimulatedBlock},
6 transaction::TransactionRequest,
7 Block, BlockTransactionsKind, Header,
8};
9use jsonrpsee_types::ErrorObject;
10use reth_evm::{
11 execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutor},
12 Evm,
13};
14use reth_primitives_traits::{
15 block::BlockTx, BlockBody as _, Recovered, RecoveredBlock, SignedTransaction, TxTy,
16};
17use reth_rpc_server_types::result::rpc_err;
18use reth_rpc_types_compat::{block::from_block, TransactionCompat};
19use reth_storage_api::noop::NoopProvider;
20use revm::{context_interface::result::ExecutionResult, Database};
21use revm_primitives::{Address, Bytes, TxKind};
22
23use crate::{
24 error::{
25 api::{FromEthApiError, FromEvmHalt},
26 ToRpcError,
27 },
28 EthApiError, RevertError,
29};
30
31#[derive(Debug, thiserror::Error)]
33pub enum EthSimulateError {
34 #[error("Block gas limit exceeded by the block's transactions")]
36 BlockGasLimitExceeded,
37 #[error("Client adjustable limit reached")]
39 GasLimitReached,
40}
41
42impl EthSimulateError {
43 const fn error_code(&self) -> i32 {
44 match self {
45 Self::BlockGasLimitExceeded => -38015,
46 Self::GasLimitReached => -38026,
47 }
48 }
49}
50
51impl ToRpcError for EthSimulateError {
52 fn to_rpc_error(&self) -> ErrorObject<'static> {
53 rpc_err(self.error_code(), self.to_string(), None)
54 }
55}
56
57#[expect(clippy::type_complexity)]
62pub fn execute_transactions<S, T>(
63 mut builder: S,
64 calls: Vec<TransactionRequest>,
65 validation: bool,
66 default_gas_limit: u64,
67 chain_id: u64,
68 tx_resp_builder: &T,
69) -> Result<
70 (
71 BlockBuilderOutcome<S::Primitives>,
72 Vec<ExecutionResult<<<S::Executor as BlockExecutor>::Evm as Evm>::HaltReason>>,
73 ),
74 EthApiError,
75>
76where
77 S: BlockBuilder<Executor: BlockExecutor<Evm: Evm<DB: Database<Error: Into<EthApiError>>>>>,
78 T: TransactionCompat<TxTy<S::Primitives>>,
79{
80 builder.apply_pre_execution_changes()?;
81
82 let mut results = Vec::with_capacity(calls.len());
83 for call in calls {
84 let tx = resolve_transaction(
87 call,
88 validation,
89 default_gas_limit,
90 chain_id,
91 builder.evm_mut().db_mut(),
92 tx_resp_builder,
93 )?;
94
95 builder
96 .execute_transaction_with_result_closure(tx, |result| results.push(result.clone()))?;
97 }
98
99 let result = builder.finish(NoopProvider::default())?;
101
102 Ok((result, results))
103}
104
105pub fn resolve_transaction<DB: Database, Tx, T: TransactionCompat<Tx>>(
111 mut tx: TransactionRequest,
112 validation: bool,
113 default_gas_limit: u64,
114 chain_id: u64,
115 db: &mut DB,
116 tx_resp_builder: &T,
117) -> Result<Recovered<Tx>, EthApiError>
118where
119 DB::Error: Into<EthApiError>,
120{
121 if tx.buildable_type().is_none() && validation {
122 return Err(EthApiError::TransactionConversionError);
123 }
124 let tx_type = tx.preferred_type();
127
128 let from = if let Some(from) = tx.from {
129 from
130 } else {
131 tx.from = Some(Address::ZERO);
132 Address::ZERO
133 };
134
135 if tx.nonce.is_none() {
136 tx.nonce =
137 Some(db.basic(from).map_err(Into::into)?.map(|acc| acc.nonce).unwrap_or_default());
138 }
139
140 if tx.gas.is_none() {
141 tx.gas = Some(default_gas_limit);
142 }
143
144 if tx.chain_id.is_none() {
145 tx.chain_id = Some(chain_id);
146 }
147
148 if tx.to.is_none() {
149 tx.to = Some(TxKind::Create);
150 }
151
152 match tx_type {
153 TxType::Legacy | TxType::Eip2930 => {
154 if tx.gas_price.is_none() {
155 tx.gas_price = Some(0);
156 }
157 }
158 _ => {
159 if tx.max_fee_per_gas.is_none() {
160 tx.max_fee_per_gas = Some(0);
161 tx.max_priority_fee_per_gas = Some(0);
162 }
163 }
164 }
165
166 let tx = tx_resp_builder
167 .build_simulate_v1_transaction(tx)
168 .map_err(|e| EthApiError::other(e.into()))?;
169
170 Ok(Recovered::new_unchecked(tx, from))
171}
172
173#[expect(clippy::type_complexity)]
175pub fn build_simulated_block<T, B, Halt: Clone>(
176 block: RecoveredBlock<B>,
177 results: Vec<ExecutionResult<Halt>>,
178 full_transactions: bool,
179 tx_resp_builder: &T,
180) -> Result<SimulatedBlock<Block<T::Transaction, Header<B::Header>>>, T::Error>
181where
182 T: TransactionCompat<BlockTx<B>, Error: FromEthApiError + FromEvmHalt<Halt>>,
183 B: reth_primitives_traits::Block,
184{
185 let mut calls: Vec<SimCallResult> = Vec::with_capacity(results.len());
186
187 let mut log_index = 0;
188 for (index, (result, tx)) in results.iter().zip(block.body().transactions()).enumerate() {
189 let call = match result {
190 ExecutionResult::Halt { reason, gas_used } => {
191 let error = T::Error::from_evm_halt(reason.clone(), tx.gas_limit());
192 SimCallResult {
193 return_data: Bytes::new(),
194 error: Some(SimulateError {
195 message: error.to_string(),
196 code: error.into().code(),
197 }),
198 gas_used: *gas_used,
199 logs: Vec::new(),
200 status: false,
201 }
202 }
203 ExecutionResult::Revert { output, gas_used } => {
204 let error = RevertError::new(output.clone());
205 SimCallResult {
206 return_data: output.clone(),
207 error: Some(SimulateError {
208 code: error.error_code(),
209 message: error.to_string(),
210 }),
211 gas_used: *gas_used,
212 status: false,
213 logs: Vec::new(),
214 }
215 }
216 ExecutionResult::Success { output, gas_used, logs, .. } => SimCallResult {
217 return_data: output.clone().into_data(),
218 error: None,
219 gas_used: *gas_used,
220 logs: logs
221 .iter()
222 .map(|log| {
223 log_index += 1;
224 alloy_rpc_types_eth::Log {
225 inner: log.clone(),
226 log_index: Some(log_index - 1),
227 transaction_index: Some(index as u64),
228 transaction_hash: Some(*tx.tx_hash()),
229 block_number: Some(block.header().number()),
230 block_timestamp: Some(block.header().timestamp()),
231 ..Default::default()
232 }
233 })
234 .collect(),
235 status: true,
236 },
237 };
238
239 calls.push(call);
240 }
241
242 let txs_kind =
243 if full_transactions { BlockTransactionsKind::Full } else { BlockTransactionsKind::Hashes };
244
245 let block = from_block(block, txs_kind, tx_resp_builder)?;
246 Ok(SimulatedBlock { inner: block, calls })
247}