1use crate::EthEvmConfig;
4use alloc::sync::Arc;
5use reth_chainspec::ChainSpec;
6use reth_evm::execute::BasicBlockExecutorProvider;
7
8#[derive(Debug)]
11pub struct EthExecutorProvider;
12
13impl EthExecutorProvider {
14 pub fn ethereum(chain_spec: Arc<ChainSpec>) -> BasicBlockExecutorProvider<EthEvmConfig> {
16 BasicBlockExecutorProvider::new(EthEvmConfig::new(chain_spec))
17 }
18
19 pub fn mainnet() -> BasicBlockExecutorProvider<EthEvmConfig> {
21 BasicBlockExecutorProvider::new(EthEvmConfig::mainnet())
22 }
23}
24
25#[cfg(test)]
26mod tests {
27 use super::*;
28 use alloy_consensus::{constants::ETH_TO_WEI, Header, TxLegacy};
29 use alloy_eips::{
30 eip2935::{HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE},
31 eip4788::{BEACON_ROOTS_ADDRESS, BEACON_ROOTS_CODE, SYSTEM_ADDRESS},
32 eip4895::Withdrawal,
33 eip7002::{WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_PREDEPLOY_CODE},
34 eip7685::EMPTY_REQUESTS_HASH,
35 };
36 use alloy_evm::block::BlockValidationError;
37 use alloy_primitives::{b256, fixed_bytes, keccak256, Bytes, TxKind, B256, U256};
38 use reth_chainspec::{ChainSpecBuilder, EthereumHardfork, ForkCondition, MAINNET};
39 use reth_ethereum_primitives::{Block, BlockBody, Transaction};
40 use reth_evm::execute::{BasicBlockExecutorProvider, BlockExecutorProvider, Executor};
41 use reth_execution_types::BlockExecutionResult;
42 use reth_primitives_traits::{
43 crypto::secp256k1::public_key_to_address, Block as _, RecoveredBlock,
44 };
45 use reth_testing_utils::generators::{self, sign_tx_with_key_pair};
46 use revm::{
47 database::{CacheDB, EmptyDB, TransitionState},
48 primitives::{address, BLOCKHASH_SERVE_WINDOW},
49 state::{AccountInfo, Bytecode, EvmState},
50 Database,
51 };
52 use secp256k1::{Keypair, Secp256k1};
53 use std::sync::mpsc;
54
55 fn create_database_with_beacon_root_contract() -> CacheDB<EmptyDB> {
56 let mut db = CacheDB::new(Default::default());
57
58 let beacon_root_contract_account = AccountInfo {
59 balance: U256::ZERO,
60 code_hash: keccak256(BEACON_ROOTS_CODE.clone()),
61 nonce: 1,
62 code: Some(Bytecode::new_raw(BEACON_ROOTS_CODE.clone())),
63 };
64
65 db.insert_account_info(BEACON_ROOTS_ADDRESS, beacon_root_contract_account);
66
67 db
68 }
69
70 fn create_database_with_withdrawal_requests_contract() -> CacheDB<EmptyDB> {
71 let mut db = CacheDB::new(Default::default());
72
73 let withdrawal_requests_contract_account = AccountInfo {
74 nonce: 1,
75 balance: U256::ZERO,
76 code_hash: keccak256(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone()),
77 code: Some(Bytecode::new_raw(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone())),
78 };
79
80 db.insert_account_info(
81 WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
82 withdrawal_requests_contract_account,
83 );
84
85 db
86 }
87
88 fn executor_provider(chain_spec: Arc<ChainSpec>) -> BasicBlockExecutorProvider<EthEvmConfig> {
89 BasicBlockExecutorProvider::new(EthEvmConfig::new(chain_spec))
90 }
91
92 #[test]
93 fn eip_4788_non_genesis_call() {
94 let mut header =
95 Header { timestamp: 1, number: 1, excess_blob_gas: Some(0), ..Header::default() };
96
97 let db = create_database_with_beacon_root_contract();
98
99 let chain_spec = Arc::new(
100 ChainSpecBuilder::from(&*MAINNET)
101 .shanghai_activated()
102 .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1))
103 .build(),
104 );
105
106 let provider = executor_provider(chain_spec);
107
108 let mut executor = provider.executor(db);
109
110 let err = executor
112 .execute_one(&RecoveredBlock::new_unhashed(
113 Block {
114 header: header.clone(),
115 body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None },
116 },
117 vec![],
118 ))
119 .expect_err(
120 "Executing cancun block without parent beacon block root field should fail",
121 );
122
123 assert!(matches!(
124 err.as_validation().unwrap(),
125 BlockValidationError::MissingParentBeaconBlockRoot
126 ));
127
128 header.parent_beacon_block_root = Some(B256::with_last_byte(0x69));
130
131 executor
133 .execute_one(&RecoveredBlock::new_unhashed(
134 Block {
135 header: header.clone(),
136 body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None },
137 },
138 vec![],
139 ))
140 .unwrap();
141
142 let history_buffer_length = 8191u64;
148 let timestamp_index = header.timestamp % history_buffer_length;
149 let parent_beacon_block_root_index =
150 timestamp_index % history_buffer_length + history_buffer_length;
151
152 let timestamp_storage = executor.with_state_mut(|state| {
153 state.storage(BEACON_ROOTS_ADDRESS, U256::from(timestamp_index)).unwrap()
154 });
155 assert_eq!(timestamp_storage, U256::from(header.timestamp));
156
157 let parent_beacon_block_root_storage = executor.with_state_mut(|state| {
159 state
160 .storage(BEACON_ROOTS_ADDRESS, U256::from(parent_beacon_block_root_index))
161 .expect("storage value should exist")
162 });
163 assert_eq!(parent_beacon_block_root_storage, U256::from(0x69));
164 }
165
166 #[test]
167 fn eip_4788_no_code_cancun() {
168 let header = Header {
171 timestamp: 1,
172 number: 1,
173 parent_beacon_block_root: Some(B256::with_last_byte(0x69)),
174 excess_blob_gas: Some(0),
175 ..Header::default()
176 };
177
178 let db = CacheDB::new(EmptyDB::default());
179
180 let chain_spec = Arc::new(
182 ChainSpecBuilder::from(&*MAINNET)
183 .shanghai_activated()
184 .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1))
185 .build(),
186 );
187
188 let provider = executor_provider(chain_spec);
189
190 provider
192 .executor(db)
193 .execute_one(&RecoveredBlock::new_unhashed(
194 Block {
195 header,
196 body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None },
197 },
198 vec![],
199 ))
200 .expect(
201 "Executing a block with no transactions while cancun is active should not fail",
202 );
203 }
204
205 #[test]
206 fn eip_4788_empty_account_call() {
207 let mut db = create_database_with_beacon_root_contract();
211
212 db.insert_account_info(SYSTEM_ADDRESS, Default::default());
214
215 let chain_spec = Arc::new(
216 ChainSpecBuilder::from(&*MAINNET)
217 .shanghai_activated()
218 .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1))
219 .build(),
220 );
221
222 let provider = executor_provider(chain_spec);
223
224 let header = Header {
226 timestamp: 1,
227 number: 1,
228 parent_beacon_block_root: Some(B256::with_last_byte(0x69)),
229 excess_blob_gas: Some(0),
230 ..Header::default()
231 };
232
233 let mut executor = provider.executor(db);
234
235 executor
237 .execute_one(&RecoveredBlock::new_unhashed(
238 Block {
239 header,
240 body: BlockBody { transactions: vec![], ommers: vec![], withdrawals: None },
241 },
242 vec![],
243 ))
244 .expect(
245 "Executing a block with no transactions while cancun is active should not fail",
246 );
247
248 let nonce =
250 executor.with_state_mut(|state| state.basic(SYSTEM_ADDRESS).unwrap().unwrap().nonce);
251 assert_eq!(nonce, 0);
252 }
253
254 #[test]
255 fn eip_4788_genesis_call() {
256 let db = create_database_with_beacon_root_contract();
257
258 let chain_spec = Arc::new(
260 ChainSpecBuilder::from(&*MAINNET)
261 .shanghai_activated()
262 .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(0))
263 .build(),
264 );
265
266 let mut header = chain_spec.genesis_header().clone();
267 let provider = executor_provider(chain_spec);
268 let mut executor = provider.executor(db);
269
270 header.parent_beacon_block_root = Some(B256::with_last_byte(0x69));
272 let _err = executor
273 .execute_one(&RecoveredBlock::new_unhashed(
274 Block { header: header.clone(), body: Default::default() },
275 vec![],
276 ))
277 .expect_err(
278 "Executing genesis cancun block with non-zero parent beacon block root field
279 should fail",
280 );
281
282 header.parent_beacon_block_root = Some(B256::ZERO);
284
285 executor
288 .execute_one(&RecoveredBlock::new_unhashed(
289 Block { header, body: Default::default() },
290 vec![],
291 ))
292 .unwrap();
293
294 let transition_state = executor.with_state_mut(|state| {
297 state
298 .transition_state
299 .take()
300 .expect("the evm should be initialized with bundle updates")
301 });
302
303 assert_eq!(transition_state, TransitionState::default());
305 }
306
307 #[test]
308 fn eip_4788_high_base_fee() {
309 let header = Header {
312 timestamp: 1,
313 number: 1,
314 parent_beacon_block_root: Some(B256::with_last_byte(0x69)),
315 base_fee_per_gas: Some(u64::MAX),
316 excess_blob_gas: Some(0),
317 ..Header::default()
318 };
319
320 let db = create_database_with_beacon_root_contract();
321
322 let chain_spec = Arc::new(
323 ChainSpecBuilder::from(&*MAINNET)
324 .shanghai_activated()
325 .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(1))
326 .build(),
327 );
328
329 let provider = executor_provider(chain_spec);
330
331 let mut executor = provider.executor(db);
333
334 executor
336 .execute_one(&RecoveredBlock::new_unhashed(
337 Block { header: header.clone(), body: Default::default() },
338 vec![],
339 ))
340 .unwrap();
341
342 let history_buffer_length = 8191u64;
348 let timestamp_index = header.timestamp % history_buffer_length;
349 let parent_beacon_block_root_index =
350 timestamp_index % history_buffer_length + history_buffer_length;
351
352 let timestamp_storage = executor.with_state_mut(|state| {
354 state.storage(BEACON_ROOTS_ADDRESS, U256::from(timestamp_index)).unwrap()
355 });
356 assert_eq!(timestamp_storage, U256::from(header.timestamp));
357
358 let parent_beacon_block_root_storage = executor.with_state_mut(|state| {
360 state.storage(BEACON_ROOTS_ADDRESS, U256::from(parent_beacon_block_root_index)).unwrap()
361 });
362 assert_eq!(parent_beacon_block_root_storage, U256::from(0x69));
363 }
364
365 fn create_database_with_block_hashes(latest_block: u64) -> CacheDB<EmptyDB> {
367 let mut db = CacheDB::new(Default::default());
368 for block_number in 0..=latest_block {
369 db.cache
370 .block_hashes
371 .insert(U256::from(block_number), keccak256(block_number.to_string()));
372 }
373
374 let blockhashes_contract_account = AccountInfo {
375 balance: U256::ZERO,
376 code_hash: keccak256(HISTORY_STORAGE_CODE.clone()),
377 code: Some(Bytecode::new_raw(HISTORY_STORAGE_CODE.clone())),
378 nonce: 1,
379 };
380
381 db.insert_account_info(HISTORY_STORAGE_ADDRESS, blockhashes_contract_account);
382
383 db
384 }
385 #[test]
386 fn eip_2935_pre_fork() {
387 let db = create_database_with_block_hashes(1);
388
389 let chain_spec = Arc::new(
390 ChainSpecBuilder::from(&*MAINNET)
391 .shanghai_activated()
392 .with_fork(EthereumHardfork::Prague, ForkCondition::Never)
393 .build(),
394 );
395
396 let provider = executor_provider(chain_spec);
397 let mut executor = provider.executor(db);
398
399 let header = Header { timestamp: 1, number: 1, ..Header::default() };
401
402 executor
404 .execute_one(&RecoveredBlock::new_unhashed(
405 Block { header, body: Default::default() },
406 vec![],
407 ))
408 .expect(
409 "Executing a block with no transactions while Prague is active should not fail",
410 );
411
412 executor.with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap());
418 assert!(executor.with_state_mut(|state| state
419 .storage(HISTORY_STORAGE_ADDRESS, U256::ZERO)
420 .unwrap()
421 .is_zero()));
422 }
423
424 #[test]
425 fn eip_2935_fork_activation_genesis() {
426 let db = create_database_with_block_hashes(0);
427
428 let chain_spec = Arc::new(
429 ChainSpecBuilder::from(&*MAINNET)
430 .shanghai_activated()
431 .cancun_activated()
432 .prague_activated()
433 .build(),
434 );
435
436 let header = chain_spec.genesis_header().clone();
437 let provider = executor_provider(chain_spec);
438 let mut executor = provider.executor(db);
439
440 executor
442 .execute_one(&RecoveredBlock::new_unhashed(
443 Block { header, body: Default::default() },
444 vec![],
445 ))
446 .expect(
447 "Executing a block with no transactions while Prague is active should not fail",
448 );
449
450 executor.with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap());
456 assert!(executor.with_state_mut(|state| state
457 .storage(HISTORY_STORAGE_ADDRESS, U256::ZERO)
458 .unwrap()
459 .is_zero()));
460 }
461
462 #[test]
463 fn eip_2935_fork_activation_within_window_bounds() {
464 let fork_activation_block = (BLOCKHASH_SERVE_WINDOW - 10) as u64;
465 let db = create_database_with_block_hashes(fork_activation_block);
466
467 let chain_spec = Arc::new(
468 ChainSpecBuilder::from(&*MAINNET)
469 .shanghai_activated()
470 .cancun_activated()
471 .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(1))
472 .build(),
473 );
474
475 let header = Header {
476 parent_hash: B256::random(),
477 timestamp: 1,
478 number: fork_activation_block,
479 requests_hash: Some(EMPTY_REQUESTS_HASH),
480 excess_blob_gas: Some(0),
481 parent_beacon_block_root: Some(B256::random()),
482 ..Header::default()
483 };
484 let provider = executor_provider(chain_spec);
485 let mut executor = provider.executor(db);
486
487 executor
489 .execute_one(&RecoveredBlock::new_unhashed(
490 Block { header, body: Default::default() },
491 vec![],
492 ))
493 .expect(
494 "Executing a block with no transactions while Prague is active should not fail",
495 );
496
497 assert!(executor
499 .with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some()));
500 assert_ne!(
501 executor.with_state_mut(|state| state
502 .storage(HISTORY_STORAGE_ADDRESS, U256::from(fork_activation_block - 1))
503 .unwrap()),
504 U256::ZERO
505 );
506
507 assert!(executor.with_state_mut(|state| state
509 .storage(HISTORY_STORAGE_ADDRESS, U256::from(fork_activation_block))
510 .unwrap()
511 .is_zero()));
512 }
513
514 #[test]
516 fn eip_2935_fork_activation_outside_window_bounds() {
517 let fork_activation_block = (BLOCKHASH_SERVE_WINDOW + 256) as u64;
518 let db = create_database_with_block_hashes(fork_activation_block);
519
520 let chain_spec = Arc::new(
521 ChainSpecBuilder::from(&*MAINNET)
522 .shanghai_activated()
523 .cancun_activated()
524 .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(1))
525 .build(),
526 );
527
528 let provider = executor_provider(chain_spec);
529 let mut executor = provider.executor(db);
530
531 let header = Header {
532 parent_hash: B256::random(),
533 timestamp: 1,
534 number: fork_activation_block,
535 requests_hash: Some(EMPTY_REQUESTS_HASH),
536 excess_blob_gas: Some(0),
537 parent_beacon_block_root: Some(B256::random()),
538 ..Header::default()
539 };
540
541 executor
543 .execute_one(&RecoveredBlock::new_unhashed(
544 Block { header, body: Default::default() },
545 vec![],
546 ))
547 .expect(
548 "Executing a block with no transactions while Prague is active should not fail",
549 );
550
551 assert!(executor
553 .with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some()));
554 }
555
556 #[test]
557 fn eip_2935_state_transition_inside_fork() {
558 let db = create_database_with_block_hashes(2);
559
560 let chain_spec = Arc::new(
561 ChainSpecBuilder::from(&*MAINNET)
562 .shanghai_activated()
563 .cancun_activated()
564 .prague_activated()
565 .build(),
566 );
567
568 let header = chain_spec.genesis_header().clone();
569 let header_hash = header.hash_slow();
570
571 let provider = executor_provider(chain_spec);
572 let mut executor = provider.executor(db);
573
574 executor
576 .execute_one(&RecoveredBlock::new_unhashed(
577 Block { header, body: Default::default() },
578 vec![],
579 ))
580 .expect(
581 "Executing a block with no transactions while Prague is active should not fail",
582 );
583
584 executor.with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap());
589 assert!(executor.with_state_mut(|state| state
590 .storage(HISTORY_STORAGE_ADDRESS, U256::ZERO)
591 .unwrap()
592 .is_zero()));
593
594 let header = Header {
596 parent_hash: header_hash,
597 timestamp: 1,
598 number: 1,
599 requests_hash: Some(EMPTY_REQUESTS_HASH),
600 excess_blob_gas: Some(0),
601 parent_beacon_block_root: Some(B256::random()),
602 ..Header::default()
603 };
604 let header_hash = header.hash_slow();
605
606 executor
607 .execute_one(&RecoveredBlock::new_unhashed(
608 Block { header, body: Default::default() },
609 vec![],
610 ))
611 .expect(
612 "Executing a block with no transactions while Prague is active should not fail",
613 );
614
615 assert!(executor
617 .with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some()));
618 assert_ne!(
619 executor.with_state_mut(|state| state
620 .storage(HISTORY_STORAGE_ADDRESS, U256::ZERO)
621 .unwrap()),
622 U256::ZERO
623 );
624 assert!(executor.with_state_mut(|state| state
625 .storage(HISTORY_STORAGE_ADDRESS, U256::from(1))
626 .unwrap()
627 .is_zero()));
628
629 let header = Header {
631 parent_hash: header_hash,
632 timestamp: 1,
633 number: 2,
634 requests_hash: Some(EMPTY_REQUESTS_HASH),
635 excess_blob_gas: Some(0),
636 parent_beacon_block_root: Some(B256::random()),
637 ..Header::default()
638 };
639
640 executor
641 .execute_one(&RecoveredBlock::new_unhashed(
642 Block { header, body: Default::default() },
643 vec![],
644 ))
645 .expect(
646 "Executing a block with no transactions while Prague is active should not fail",
647 );
648
649 assert!(executor
651 .with_state_mut(|state| state.basic(HISTORY_STORAGE_ADDRESS).unwrap().is_some()));
652 assert_ne!(
653 executor.with_state_mut(|state| state
654 .storage(HISTORY_STORAGE_ADDRESS, U256::ZERO)
655 .unwrap()),
656 U256::ZERO
657 );
658 assert_ne!(
659 executor.with_state_mut(|state| state
660 .storage(HISTORY_STORAGE_ADDRESS, U256::from(1))
661 .unwrap()),
662 U256::ZERO
663 );
664 assert!(executor.with_state_mut(|state| state
665 .storage(HISTORY_STORAGE_ADDRESS, U256::from(2))
666 .unwrap()
667 .is_zero()));
668 }
669
670 #[test]
671 fn eip_7002() {
672 let chain_spec = Arc::new(
673 ChainSpecBuilder::from(&*MAINNET)
674 .shanghai_activated()
675 .cancun_activated()
676 .prague_activated()
677 .build(),
678 );
679
680 let mut db = create_database_with_withdrawal_requests_contract();
681
682 let secp = Secp256k1::new();
683 let sender_key_pair = Keypair::new(&secp, &mut generators::rng());
684 let sender_address = public_key_to_address(sender_key_pair.public_key());
685
686 db.insert_account_info(
687 sender_address,
688 AccountInfo { nonce: 1, balance: U256::from(ETH_TO_WEI), ..Default::default() },
689 );
690
691 let validator_public_key = fixed_bytes!("111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
693 let withdrawal_amount = fixed_bytes!("0203040506070809");
694 let input: Bytes = [&validator_public_key[..], &withdrawal_amount[..]].concat().into();
695 assert_eq!(input.len(), 56);
696
697 let mut header = chain_spec.genesis_header().clone();
698 header.gas_limit = 1_500_000;
699 header.gas_used = 135_856;
701 header.receipts_root =
702 b256!("0xb31a3e47b902e9211c4d349af4e4c5604ce388471e79ca008907ae4616bb0ed3");
703
704 let tx = sign_tx_with_key_pair(
705 sender_key_pair,
706 Transaction::Legacy(TxLegacy {
707 chain_id: Some(chain_spec.chain.id()),
708 nonce: 1,
709 gas_price: header.base_fee_per_gas.unwrap().into(),
710 gas_limit: header.gas_used,
711 to: TxKind::Call(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS),
712 value: U256::from(2),
714 input,
715 }),
716 );
717
718 let provider = executor_provider(chain_spec);
719
720 let mut executor = provider.executor(db);
721
722 let BlockExecutionResult { receipts, requests, .. } = executor
723 .execute_one(
724 &Block { header, body: BlockBody { transactions: vec![tx], ..Default::default() } }
725 .try_into_recovered()
726 .unwrap(),
727 )
728 .unwrap();
729
730 let receipt = receipts.first().unwrap();
731 assert!(receipt.success);
732
733 assert_eq!(requests.len(), 1);
735 assert_eq!(requests[0][0], 1);
736 }
737
738 #[test]
739 fn block_gas_limit_error() {
740 let chain_spec = Arc::new(
742 ChainSpecBuilder::from(&*MAINNET)
743 .shanghai_activated()
744 .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(0))
745 .build(),
746 );
747
748 let mut db = create_database_with_withdrawal_requests_contract();
750
751 let secp = Secp256k1::new();
753 let sender_key_pair = Keypair::new(&secp, &mut generators::rng());
755 let sender_address = public_key_to_address(sender_key_pair.public_key());
757
758 db.insert_account_info(
760 sender_address,
761 AccountInfo { nonce: 1, balance: U256::from(ETH_TO_WEI), ..Default::default() },
762 );
763
764 let validator_public_key = fixed_bytes!("111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
766 let withdrawal_amount = fixed_bytes!("2222222222222222");
767 let input: Bytes = [&validator_public_key[..], &withdrawal_amount[..]].concat().into();
769 assert_eq!(input.len(), 56);
771
772 let mut header = chain_spec.genesis_header().clone();
774 header.gas_limit = 1_500_000;
775 header.gas_used = 134_807;
776 header.receipts_root =
777 b256!("0xb31a3e47b902e9211c4d349af4e4c5604ce388471e79ca008907ae4616bb0ed3");
778
779 let tx = sign_tx_with_key_pair(
781 sender_key_pair,
782 Transaction::Legacy(TxLegacy {
783 chain_id: Some(chain_spec.chain.id()),
784 nonce: 1,
785 gas_price: header.base_fee_per_gas.unwrap().into(),
786 gas_limit: 2_500_000, to: TxKind::Call(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS),
788 value: U256::from(1),
789 input,
790 }),
791 );
792
793 let mut executor = executor_provider(chain_spec).executor(db);
795
796 let exec_result = executor.execute_one(
798 &Block { header, body: BlockBody { transactions: vec![tx], ..Default::default() } }
799 .try_into_recovered()
800 .unwrap(),
801 );
802
803 match exec_result {
805 Ok(_) => panic!("Expected block gas limit error"),
806 Err(err) => assert!(matches!(
807 *err.as_validation().unwrap(),
808 BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
809 transaction_gas_limit: 2_500_000,
810 block_available_gas: 1_500_000,
811 }
812 )),
813 }
814 }
815
816 #[test]
817 fn test_balance_increment_not_duplicated() {
818 let chain_spec = Arc::new(
819 ChainSpecBuilder::from(&*MAINNET)
820 .shanghai_activated()
821 .cancun_activated()
822 .prague_activated()
823 .build(),
824 );
825
826 let withdrawal_recipient = address!("0x1000000000000000000000000000000000000000");
827
828 let mut db = CacheDB::new(EmptyDB::default());
829 let initial_balance = 100;
830 db.insert_account_info(
831 withdrawal_recipient,
832 AccountInfo { balance: U256::from(initial_balance), nonce: 1, ..Default::default() },
833 );
834
835 let withdrawal =
836 Withdrawal { index: 0, validator_index: 0, address: withdrawal_recipient, amount: 1 };
837
838 let header = Header {
839 timestamp: 1,
840 number: 1,
841 excess_blob_gas: Some(0),
842 parent_beacon_block_root: Some(B256::random()),
843 ..Header::default()
844 };
845
846 let block = &RecoveredBlock::new_unhashed(
847 Block {
848 header,
849 body: BlockBody {
850 transactions: vec![],
851 ommers: vec![],
852 withdrawals: Some(vec![withdrawal].into()),
853 },
854 },
855 vec![],
856 );
857
858 let provider = executor_provider(chain_spec);
859 let executor = provider.executor(db);
860
861 let (tx, rx) = mpsc::channel();
862 let tx_clone = tx.clone();
863
864 let _output = executor
865 .execute_with_state_hook(block, move |_, state: &EvmState| {
866 if let Some(account) = state.get(&withdrawal_recipient) {
867 let _ = tx_clone.send(account.info.balance);
868 }
869 })
870 .expect("Block execution should succeed");
871
872 drop(tx);
873 let balance_changes: Vec<U256> = rx.try_iter().collect();
874
875 if let Some(final_balance) = balance_changes.last() {
876 let expected_final_balance = U256::from(initial_balance) + U256::from(1_000_000_000); assert_eq!(
878 *final_balance, expected_final_balance,
879 "Final balance should match expected value after withdrawal"
880 );
881 }
882 }
883}