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