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