1use crate::{
4 error::{api::FromEthApiError, FromEvmError, ToRpcError},
5 EthApiError,
6};
7use alloy_chains::Chain;
8use alloy_consensus::{transaction::TxHashRef, BlockHeader, Transaction as _};
9use alloy_eips::eip2718::WithEncoded;
10use alloy_evm::{block::TxResult, precompiles::PrecompilesMap};
11use alloy_network::{NetworkTransactionBuilder, TransactionBuilder};
12use alloy_rpc_types_eth::{
13 simulate::{SimBlock, SimCallResult, SimulateError, SimulatedBlock},
14 state::StateOverride,
15 BlockOverrides, BlockTransactionsKind,
16};
17use jsonrpsee_types::ErrorObject;
18use reth_evm::{
19 execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutor},
20 Evm, HaltReasonFor,
21};
22use reth_primitives_traits::{
23 BlockBody as _, BlockTy, NodePrimitives, Recovered, RecoveredBlock, SealedHeader,
24};
25use reth_rpc_convert::{RpcBlock, RpcConvert, RpcTxReq};
26use reth_rpc_server_types::result::rpc_err;
27use reth_storage_api::noop::NoopProvider;
28use revm::{
29 context::Block,
30 context_interface::result::ExecutionResult,
31 primitives::{Address, Bytes, TxKind, U256},
32 Database,
33};
34
35const SIMULATE_FALLBACK_TIMESTAMP_INCREMENT: u64 = 12;
38
39pub const SIMULATE_REVERT_CODE: i32 = 3;
45
46pub const SIMULATE_VM_ERROR_CODE: i32 = -32015;
50
51#[derive(Debug, thiserror::Error)]
53pub enum EthSimulateError {
54 #[error("Block gas limit exceeded by the block's transactions")]
56 BlockGasLimitExceeded,
57 #[error("too many blocks")]
59 TooManyBlocks,
60 #[error("Client adjustable limit reached")]
62 GasLimitReached,
63 #[error("block numbers must be in order: {got} <= {parent}")]
65 BlockNumberInvalid {
66 got: u64,
68 parent: u64,
70 },
71 #[error("block timestamps must be in order: {got} <= {parent}")]
73 BlockTimestampInvalid {
74 got: u64,
76 parent: u64,
78 },
79 #[error("nonce too low: next nonce {state}, tx nonce {tx}")]
81 NonceTooLow {
82 tx: u64,
84 state: u64,
86 },
87 #[error("nonce too high")]
89 NonceTooHigh,
90 #[error("max fee per gas less than block base fee")]
92 BaseFeePerGasTooLow,
93 #[error("intrinsic gas too low")]
95 IntrinsicGasTooLow,
96 #[error("insufficient funds for gas * price + value: have {balance} want {cost}")]
98 InsufficientFunds {
99 cost: U256,
101 balance: U256,
103 },
104 #[error("sender is not an EOA")]
106 SenderNotEOA,
107 #[error("max initcode size exceeded")]
109 MaxInitCodeSizeExceeded,
110 #[error("account {0} is not a precompile")]
112 NotAPrecompile(Address),
113 #[error("cannot move precompile {0} to itself")]
115 MovePrecompileToSelf(Address),
116}
117
118impl EthSimulateError {
119 pub const fn error_code(&self) -> i32 {
121 match self {
122 Self::NonceTooLow { .. } => -38010,
123 Self::NonceTooHigh => -38011,
124 Self::BaseFeePerGasTooLow => -38012,
125 Self::IntrinsicGasTooLow => -38013,
126 Self::InsufficientFunds { .. } => -38014,
127 Self::BlockGasLimitExceeded => -38015,
128 Self::BlockNumberInvalid { .. } => -38020,
129 Self::BlockTimestampInvalid { .. } => -38021,
130 Self::SenderNotEOA => -38024,
131 Self::MaxInitCodeSizeExceeded => -38025,
132 Self::TooManyBlocks | Self::GasLimitReached => -38026,
133 Self::MovePrecompileToSelf(_) => -38022,
134 Self::NotAPrecompile(_) => -32000,
135 }
136 }
137}
138
139impl ToRpcError for EthSimulateError {
140 fn to_rpc_error(&self) -> ErrorObject<'static> {
141 rpc_err(self.error_code(), self.to_string(), None)
142 }
143}
144
145pub fn sanitize_chain<TxReq, H>(
162 blocks: Vec<SimBlock<TxReq>>,
163 parent: &SealedHeader<H>,
164 chain_id: u64,
165 max_simulate_blocks: u64,
166) -> Result<Vec<SimBlock<TxReq>>, EthApiError>
167where
168 H: BlockHeader,
169{
170 let timestamp_increment = Chain::from(chain_id)
171 .average_blocktime_hint()
172 .map(|d| d.as_secs().saturating_add(u64::from(d.subsec_nanos() > 0)))
173 .filter(|&s| s > 0)
174 .unwrap_or(SIMULATE_FALLBACK_TIMESTAMP_INCREMENT);
175
176 let mut out = Vec::with_capacity(blocks.len());
177 let base_number = parent.number();
178 let mut prev_number = base_number;
179 let mut prev_timestamp = parent.timestamp();
180
181 for mut block in blocks {
182 let overrides = block.block_overrides.get_or_insert_with(BlockOverrides::default);
183
184 let target_number = if let Some(n) = overrides.number {
186 u64::try_from(n).unwrap_or(u64::MAX)
187 } else {
188 let n = prev_number.saturating_add(1);
189 overrides.number = Some(U256::from(n));
190 n
191 };
192
193 if target_number <= prev_number {
194 return Err(EthApiError::other(EthSimulateError::BlockNumberInvalid {
195 got: target_number,
196 parent: prev_number,
197 }));
198 }
199
200 if target_number.saturating_sub(base_number) > max_simulate_blocks {
201 return Err(EthApiError::other(EthSimulateError::TooManyBlocks));
202 }
203
204 let gap = target_number - prev_number;
206 if gap > 1 {
207 for i in 1..gap {
208 let filler_number = prev_number + i;
209 let filler_time = prev_timestamp + timestamp_increment;
210 out.push(SimBlock {
211 block_overrides: Some(BlockOverrides {
212 number: Some(U256::from(filler_number)),
213 time: Some(filler_time),
214 ..Default::default()
215 }),
216 state_overrides: None,
217 calls: Vec::new(),
218 });
219 prev_timestamp = filler_time;
220 }
221 }
222
223 prev_number = target_number;
224 let block_time = if let Some(t) = overrides.time {
226 if t <= prev_timestamp {
227 return Err(EthApiError::other(EthSimulateError::BlockTimestampInvalid {
228 got: t,
229 parent: prev_timestamp,
230 }));
231 }
232 t
233 } else {
234 let t = prev_timestamp + timestamp_increment;
235 overrides.time = Some(t);
236 t
237 };
238 prev_timestamp = block_time;
239
240 out.push(block);
241 }
242
243 Ok(out)
244}
245
246pub fn apply_precompile_overrides(
252 state_overrides: &StateOverride,
253 precompiles: &mut PrecompilesMap,
254) -> Result<(), EthSimulateError> {
255 let moves: Vec<_> = state_overrides
256 .iter()
257 .filter_map(|(source, account_override)| {
258 account_override.move_precompile_to.map(|dest| (*source, dest))
259 })
260 .collect();
261
262 for (source, dest) in &moves {
263 if source == dest {
264 if precompiles.get(source).is_none() {
265 return Err(EthSimulateError::NotAPrecompile(*source))
266 }
267 return Err(EthSimulateError::MovePrecompileToSelf(*source))
268 }
269 }
270
271 precompiles.move_precompiles(moves).map_err(
272 |alloy_evm::precompiles::MovePrecompileError::NotAPrecompile(addr)| {
273 EthSimulateError::NotAPrecompile(addr)
274 },
275 )?;
276
277 Ok(())
278}
279
280#[expect(clippy::type_complexity)]
292pub fn execute_transactions<S, T>(
293 mut builder: S,
294 calls: Vec<RpcTxReq<T::Network>>,
295 remaining_call_gas_limit: &mut Option<u64>,
296 chain_id: u64,
297 converter: &T,
298) -> Result<
299 (
300 BlockBuilderOutcome<S::Primitives>,
301 Vec<ExecutionResult<<<S::Executor as BlockExecutor>::Evm as Evm>::HaltReason>>,
302 ),
303 EthApiError,
304>
305where
306 S: BlockBuilder<Executor: BlockExecutor<Evm: Evm<DB: Database<Error: Into<EthApiError>>>>>,
307 T: RpcConvert<Primitives = S::Primitives>,
308{
309 builder.apply_pre_execution_changes()?;
310
311 let mut results = Vec::with_capacity(calls.len());
312 let mut cumulative_tx_gas_used: u64 = 0;
313 let mut block_regular_gas_used: u64 = 0;
314 let mut block_state_gas_used: u64 = 0;
315 let block_gas_limit = builder.evm().block().gas_limit();
316 let is_amsterdam = builder.evm().cfg_env().enable_amsterdam_eip8037;
317 let tx_gas_limit_cap = builder.evm().cfg_env().tx_gas_limit_cap.unwrap_or(u64::MAX);
318 for mut call in calls {
319 let block_gas_remaining = if is_amsterdam {
320 block_gas_limit
321 .saturating_sub(block_regular_gas_used)
322 .min(block_gas_limit.saturating_sub(block_state_gas_used))
323 } else {
324 block_gas_limit.saturating_sub(cumulative_tx_gas_used)
325 };
326 let mut default_gas_limit = block_gas_remaining;
327
328 if let Some(gas_limit) = call.as_ref().gas_limit() {
329 let exceeds_gas_limit = if is_amsterdam {
330 let regular_available_gas = block_gas_limit.saturating_sub(block_regular_gas_used);
331 let state_available_gas = block_gas_limit.saturating_sub(block_state_gas_used);
332 let regular_tx_gas_limit = gas_limit.min(tx_gas_limit_cap);
333
334 regular_tx_gas_limit > regular_available_gas || gas_limit > state_available_gas
335 } else {
336 gas_limit > block_gas_remaining
337 };
338
339 if exceeds_gas_limit {
340 return Err(EthApiError::other(EthSimulateError::BlockGasLimitExceeded))
341 }
342 }
343
344 if let Some(remaining_call_gas_limit) = *remaining_call_gas_limit {
345 if let Some(gas_limit) = call.as_ref().gas_limit() {
346 if gas_limit > remaining_call_gas_limit {
347 call.as_mut().set_gas_limit(remaining_call_gas_limit);
348 }
349 } else {
350 default_gas_limit = default_gas_limit.min(remaining_call_gas_limit);
351 }
352 }
353
354 let tx = resolve_transaction(
357 call,
358 default_gas_limit,
359 builder.evm().block().basefee(),
360 chain_id,
361 builder.evm_mut().db_mut(),
362 converter,
363 )?;
364 let tx = WithEncoded::new(Default::default(), tx);
367
368 let mut tx_regular_gas_used = 0;
369 let gas_output = builder.execute_transaction_with_result_closure(tx, |result| {
370 tx_regular_gas_used = result.result().result.gas().block_regular_gas_used();
371 results.push(result.result().result.clone())
372 })?;
373
374 let gas_used = gas_output.tx_gas_used();
375 if let Some(remaining_call_gas_limit) = remaining_call_gas_limit.as_mut() {
376 if gas_used > *remaining_call_gas_limit {
377 return Err(EthApiError::other(EthSimulateError::GasLimitReached))
378 }
379 *remaining_call_gas_limit -= gas_used;
380 }
381
382 cumulative_tx_gas_used = cumulative_tx_gas_used.saturating_add(gas_used);
383 block_regular_gas_used = block_regular_gas_used.saturating_add(tx_regular_gas_used);
384 block_state_gas_used = block_state_gas_used.saturating_add(gas_output.state_gas_used());
385 }
386
387 let result = builder.finish(NoopProvider::default(), None)?;
389
390 Ok((result, results))
391}
392
393pub fn resolve_transaction<DB: Database, Tx, T>(
400 mut tx: RpcTxReq<T::Network>,
401 default_gas_limit: u64,
402 block_base_fee_per_gas: u64,
403 chain_id: u64,
404 db: &mut DB,
405 converter: &T,
406) -> Result<Recovered<Tx>, EthApiError>
407where
408 DB::Error: Into<EthApiError>,
409 T: RpcConvert<Primitives: NodePrimitives<SignedTx = Tx>>,
410{
411 let tx_type = tx.as_ref().output_tx_type();
414
415 let from = if let Some(from) = tx.as_ref().from() {
416 from
417 } else {
418 tx.as_mut().set_from(Address::ZERO);
419 Address::ZERO
420 };
421
422 if tx.as_ref().nonce().is_none() {
423 tx.as_mut().set_nonce(
424 db.basic(from).map_err(Into::into)?.map(|acc| acc.nonce).unwrap_or_default(),
425 );
426 }
427
428 if tx.as_ref().gas_limit().is_none() {
429 tx.as_mut().set_gas_limit(default_gas_limit);
430 }
431
432 if tx.as_ref().chain_id().is_none() {
433 tx.as_mut().set_chain_id(chain_id);
434 }
435
436 if tx.as_ref().kind().is_none() {
437 tx.as_mut().set_kind(TxKind::Create);
438 }
439
440 let _ = block_base_fee_per_gas;
447 if tx.as_ref().output_tx_type_checked().is_none() {
448 if tx_type.is_legacy() || tx_type.is_eip2930() {
449 if tx.as_ref().gas_price().is_none() {
450 tx.as_mut().set_gas_price(0);
451 }
452 } else {
453 if tx.as_ref().max_fee_per_gas().is_none() {
454 tx.as_mut().set_max_fee_per_gas(0);
455 }
456 if tx.as_ref().max_priority_fee_per_gas().is_none() {
457 tx.as_mut().set_max_priority_fee_per_gas(0);
458 }
459 }
460 }
461
462 let tx =
463 converter.build_simulate_v1_transaction(tx).map_err(|e| EthApiError::other(e.into()))?;
464
465 Ok(Recovered::new_unchecked(tx, from))
466}
467
468pub fn build_simulated_block<Err, T>(
470 block: RecoveredBlock<BlockTy<T::Primitives>>,
471 results: Vec<ExecutionResult<HaltReasonFor<T::Evm>>>,
472 txs_kind: BlockTransactionsKind,
473 converter: &T,
474) -> Result<SimulatedBlock<RpcBlock<T::Network>>, Err>
475where
476 Err: std::error::Error
477 + FromEthApiError
478 + FromEvmError<T::Evm>
479 + From<T::Error>
480 + Into<jsonrpsee_types::ErrorObject<'static>>,
481 T: RpcConvert,
482{
483 let mut calls: Vec<SimCallResult> = Vec::with_capacity(results.len());
484
485 let mut log_index = 0;
486 for (index, (result, tx)) in results.into_iter().zip(block.body().transactions()).enumerate() {
487 let call = match result {
488 ExecutionResult::Halt { reason, gas, .. } => {
489 let error = Err::from_evm_halt(reason, tx.gas_limit());
490 SimCallResult {
491 return_data: Bytes::new(),
492 error: Some(SimulateError {
493 message: error.to_string(),
494 code: SIMULATE_VM_ERROR_CODE,
495 ..SimulateError::invalid_params()
496 }),
497 gas_used: gas.tx_gas_used(),
498 max_used_gas: Some(gas.total_gas_spent().max(gas.floor_gas())),
499 logs: Vec::new(),
500 status: false,
501 }
502 }
503 ExecutionResult::Revert { output, gas, .. } => {
504 let error = Err::from_revert(output.clone());
505 SimCallResult {
506 return_data: Bytes::new(),
507 error: Some(SimulateError {
508 message: error.to_string(),
509 code: SIMULATE_REVERT_CODE,
510 data: Some(output),
511 }),
512 gas_used: gas.tx_gas_used(),
513 max_used_gas: Some(gas.total_gas_spent().max(gas.floor_gas())),
514 status: false,
515 logs: Vec::new(),
516 }
517 }
518 ExecutionResult::Success { output, gas, logs, .. } => SimCallResult {
519 return_data: output.into_data(),
520 error: None,
521 gas_used: gas.tx_gas_used(),
522 max_used_gas: Some(gas.total_gas_spent().max(gas.floor_gas())),
523 logs: logs
524 .into_iter()
525 .map(|log| {
526 log_index += 1;
527 alloy_rpc_types_eth::Log {
528 inner: log,
529 log_index: Some(log_index - 1),
530 transaction_index: Some(index as u64),
531 transaction_hash: Some(*tx.tx_hash()),
532 block_hash: Some(block.hash()),
533 block_number: Some(block.header().number()),
534 block_timestamp: Some(block.header().timestamp()),
535 ..Default::default()
536 }
537 })
538 .collect(),
539 status: true,
540 },
541 };
542
543 calls.push(call);
544 }
545
546 let block = block.into_rpc_block(
547 txs_kind,
548 |tx, tx_info| converter.fill(tx, tx_info),
549 |header, size| converter.convert_header(header, size),
550 )?;
551 Ok(SimulatedBlock { inner: block, calls })
552}
553
554#[cfg(test)]
555mod tests {
556 use super::{apply_precompile_overrides, sanitize_chain, EthSimulateError};
557 use crate::EthApiError;
558 use alloy_chains::Chain;
559 use alloy_consensus::Header;
560 use alloy_evm::precompiles::PrecompilesMap;
561 use alloy_primitives::{address, U256};
562 use alloy_rpc_types_eth::{
563 simulate::SimBlock,
564 state::{AccountOverride, StateOverride},
565 BlockOverrides, TransactionRequest,
566 };
567 use reth_primitives_traits::SealedHeader;
568 use revm::precompile::Precompiles;
569
570 fn parent_at(number: u64, timestamp: u64) -> SealedHeader<Header> {
571 SealedHeader::seal_slow(Header { number, timestamp, ..Default::default() })
572 }
573
574 fn block_with_number(number: u64) -> SimBlock<TransactionRequest> {
575 SimBlock {
576 block_overrides: Some(BlockOverrides {
577 number: Some(U256::from(number)),
578 ..Default::default()
579 }),
580 ..Default::default()
581 }
582 }
583
584 #[test]
585 fn precompile_self_move_requires_existing_precompile() {
586 let address = address!("c100000000000000000000000000000000000000");
587 let mut state_overrides = StateOverride::default();
588 state_overrides.insert(
589 address,
590 AccountOverride { move_precompile_to: Some(address), ..Default::default() },
591 );
592 let mut precompiles = PrecompilesMap::from_static(Precompiles::prague());
593
594 let err = apply_precompile_overrides(&state_overrides, &mut precompiles).unwrap_err();
595
596 assert!(matches!(err, EthSimulateError::NotAPrecompile(addr) if addr == address));
597 }
598
599 #[test]
600 fn precompile_self_move_errors_for_existing_precompile() {
601 let address = address!("0000000000000000000000000000000000000001");
602 let mut state_overrides = StateOverride::default();
603 state_overrides.insert(
604 address,
605 AccountOverride { move_precompile_to: Some(address), ..Default::default() },
606 );
607 let mut precompiles = PrecompilesMap::from_static(Precompiles::prague());
608
609 let err = apply_precompile_overrides(&state_overrides, &mut precompiles).unwrap_err();
610
611 assert!(matches!(err, EthSimulateError::MovePrecompileToSelf(addr) if addr == address));
612 }
613
614 #[test]
615 fn moved_precompile_is_callable() {
616 let source = address!("0000000000000000000000000000000000000001");
617 let dest = address!("0000000000000000000000000000000000123456");
618 let mut state_overrides = StateOverride::default();
619 state_overrides.insert(
620 source,
621 AccountOverride { move_precompile_to: Some(dest), ..Default::default() },
622 );
623 let mut precompiles = PrecompilesMap::from_static(Precompiles::prague());
624
625 apply_precompile_overrides(&state_overrides, &mut precompiles).unwrap();
626
627 assert!(precompiles.get(&source).is_none());
628 assert!(precompiles.get(&dest).is_some());
629 }
630
631 #[test]
632 fn sanitize_chain_fills_gaps_with_empty_blocks() {
633 let parent = parent_at(5, 100);
636 let blocks = vec![block_with_number(8)];
637
638 let out = sanitize_chain(blocks, &parent, Chain::mainnet().id(), 256).unwrap();
639 assert_eq!(out.len(), 3);
640
641 let numbers: Vec<u64> = out
642 .iter()
643 .map(|b| b.block_overrides.as_ref().unwrap().number.unwrap().try_into().unwrap())
644 .collect();
645 assert_eq!(numbers, vec![6, 7, 8]);
646
647 assert!(out[0].calls.is_empty());
648 assert!(out[1].calls.is_empty());
649
650 let times: Vec<u64> =
652 out.iter().map(|b| b.block_overrides.as_ref().unwrap().time.unwrap()).collect();
653 assert_eq!(times, vec![112, 124, 136]);
654 }
655
656 #[test]
657 fn sanitize_chain_defaults_missing_number_and_time() {
658 let parent = parent_at(10, 1000);
659 let blocks: Vec<SimBlock<TransactionRequest>> =
660 vec![SimBlock::default(), SimBlock::default()];
661
662 let out = sanitize_chain(blocks, &parent, Chain::mainnet().id(), 256).unwrap();
663 assert_eq!(out.len(), 2);
664
665 let overrides = out[0].block_overrides.as_ref().unwrap();
666 assert_eq!(overrides.number.unwrap(), U256::from(11));
667 assert_eq!(overrides.time, Some(1012));
668
669 let overrides = out[1].block_overrides.as_ref().unwrap();
670 assert_eq!(overrides.number.unwrap(), U256::from(12));
671 assert_eq!(overrides.time, Some(1024));
672 }
673
674 #[test]
675 fn sanitize_chain_uses_chain_blocktime_hint() {
676 let parent = parent_at(0, 0);
678 let blocks = vec![block_with_number(3)];
679
680 let out = sanitize_chain(blocks, &parent, Chain::optimism_mainnet().id(), 256).unwrap();
681 let times: Vec<u64> =
682 out.iter().map(|b| b.block_overrides.as_ref().unwrap().time.unwrap()).collect();
683 assert_eq!(times, vec![2, 4, 6]);
684 }
685
686 #[test]
687 fn sanitize_chain_rounds_subsecond_blocktime_hint_up() {
688 let parent = parent_at(0, 0);
691 let blocks = vec![block_with_number(3)];
692
693 let out = sanitize_chain(blocks, &parent, Chain::arbitrum_mainnet().id(), 256).unwrap();
694 let times: Vec<u64> =
695 out.iter().map(|b| b.block_overrides.as_ref().unwrap().time.unwrap()).collect();
696 assert_eq!(times, vec![1, 2, 3]);
697 }
698
699 #[test]
700 fn sanitize_chain_falls_back_when_chain_has_no_hint() {
701 let parent = parent_at(0, 0);
703 let blocks = vec![block_with_number(2)];
704
705 let out = sanitize_chain(blocks, &parent, Chain::from_id(123_456_789).id(), 256).unwrap();
706 let times: Vec<u64> =
707 out.iter().map(|b| b.block_overrides.as_ref().unwrap().time.unwrap()).collect();
708 assert_eq!(times, vec![12, 24]);
709 }
710
711 #[test]
712 fn sanitize_chain_rejects_non_increasing_number() {
713 let parent = parent_at(10, 100);
714 let err = sanitize_chain(vec![block_with_number(10)], &parent, Chain::mainnet().id(), 256)
715 .unwrap_err();
716 assert!(matches!(err, EthApiError::Other(_)));
717 }
718
719 #[test]
720 fn sanitize_chain_enforces_max_blocks() {
721 let parent = parent_at(0, 0);
722 let err = sanitize_chain(vec![block_with_number(257)], &parent, Chain::mainnet().id(), 256)
723 .unwrap_err();
724 assert!(matches!(err, EthApiError::Other(_)));
725 }
726}