reth_evm_ethereum/
execute.rs

1//! Ethereum block execution strategy.
2
3use crate::EthEvmConfig;
4use alloc::sync::Arc;
5use reth_chainspec::ChainSpec;
6use reth_evm::execute::BasicBlockExecutorProvider;
7
8/// Helper type with backwards compatible methods to obtain Ethereum executor
9/// providers.
10#[derive(Debug)]
11pub struct EthExecutorProvider;
12
13impl EthExecutorProvider {
14    /// Creates a new default ethereum executor provider.
15    pub fn ethereum(chain_spec: Arc<ChainSpec>) -> BasicBlockExecutorProvider<EthEvmConfig> {
16        BasicBlockExecutorProvider::new(EthEvmConfig::new(chain_spec))
17    }
18
19    /// Returns a new provider for the mainnet.
20    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        // attempt to execute a block without parent beacon block root, expect err
110        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        // fix header, set a gas limit
128        header.parent_beacon_block_root = Some(B256::with_last_byte(0x69));
129
130        // Now execute a block with the fixed header, ensure that it does not fail
131        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        // check the actual storage of the contract - it should be:
142        // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH should be
143        // header.timestamp
144        // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH + HISTORY_BUFFER_LENGTH
145        //   // should be parent_beacon_block_root
146        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        // get parent beacon block root storage and compare
157        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        // This test ensures that we "silently fail" when cancun is active and there is no code at
168        // // BEACON_ROOTS_ADDRESS
169        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        // DON'T deploy the contract at genesis
180        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        // attempt to execute an empty block with parent beacon block root, this should not fail
190        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        // This test ensures that we do not increment the nonce of an empty SYSTEM_ADDRESS account
207        // // during the pre-block call
208
209        let mut db = create_database_with_beacon_root_contract();
210
211        // insert an empty SYSTEM_ADDRESS
212        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        // construct the header for block one
224        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        // attempt to execute an empty block with parent beacon block root, this should not fail
235        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        // ensure that the nonce of the system address account has not changed
248        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        // activate cancun at genesis
258        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        // attempt to execute the genesis block with non-zero parent beacon block root, expect err
270        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        // fix header
282        header.parent_beacon_block_root = Some(B256::ZERO);
283
284        // now try to process the genesis block again, this time ensuring that a system contract
285        // call does not occur
286        executor
287            .execute_one(&RecoveredBlock::new_unhashed(
288                Block { header, body: Default::default() },
289                vec![],
290            ))
291            .unwrap();
292
293        // there is no system contract call so there should be NO STORAGE CHANGES
294        // this means we'll check the transition state
295        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 that it is the default (empty) transition state
303        assert_eq!(transition_state, TransitionState::default());
304    }
305
306    #[test]
307    fn eip_4788_high_base_fee() {
308        // This test ensures that if we have a base fee, then we don't return an error when the
309        // system contract is called, due to the gas price being less than the base fee.
310        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        // execute header
331        let mut executor = provider.executor(db);
332
333        // Now execute a block with the fixed header, ensure that it does not fail
334        executor
335            .execute_one(&RecoveredBlock::new_unhashed(
336                Block { header: header.clone(), body: Default::default() },
337                vec![],
338            ))
339            .unwrap();
340
341        // check the actual storage of the contract - it should be:
342        // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH should be
343        // header.timestamp
344        // * The storage value at header.timestamp % HISTORY_BUFFER_LENGTH + HISTORY_BUFFER_LENGTH
345        //   // should be parent_beacon_block_root
346        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        // get timestamp storage and compare
352        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        // get parent beacon block root storage and compare
358        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    /// Create a state provider with blockhashes and the EIP-2935 system contract.
365    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        // construct the header for block one
399        let header = Header { timestamp: 1, number: 1, ..Header::default() };
400
401        // attempt to execute an empty block, this should not fail
402        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        // ensure that the block hash was *not* written to storage, since this is before the fork
412        // was activated
413        //
414        // we load the account first, because revm expects it to be
415        // loaded
416        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        // attempt to execute genesis block, this should not fail
440        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        // ensure that the block hash was *not* written to storage, since there are no blocks
450        // preceding genesis
451        //
452        // we load the account first, because revm expects it to be
453        // loaded
454        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        // attempt to execute the fork activation block, this should not fail
487        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        // the hash for the ancestor of the fork activation block should be present
497        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        // the hash of the block itself should not be in storage
507        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    // <https://github.com/ethereum/EIPs/pull/9144>
514    #[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        // attempt to execute the fork activation block, this should not fail
541        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        // the hash for the ancestor of the fork activation block should be present
551        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        // attempt to execute the genesis block, this should not fail
574        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        // nothing should be written as the genesis has no ancestors
584        //
585        // we load the account first, because revm expects it to be
586        // loaded
587        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        // attempt to execute block 1, this should not fail
594        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        // the block hash of genesis should now be in storage, but not block 1
615        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        // attempt to execute block 2, this should not fail
629        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        // the block hash of genesis and block 1 should now be in storage, but not block 2
649        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        // https://github.com/lightclient/sys-asm/blob/9282bdb9fd64e024e27f60f507486ffb2183cba2/test/Withdrawal.t.sol.in#L36
690        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        // measured
698        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                // `MIN_WITHDRAWAL_REQUEST_FEE`
711                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        // There should be exactly one entry with withdrawal requests
732        assert_eq!(requests.len(), 1);
733        assert_eq!(requests[0][0], 1);
734    }
735
736    #[test]
737    fn block_gas_limit_error() {
738        // Create a chain specification with fork conditions set for Prague
739        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        // Create a state provider with the withdrawal requests contract pre-deployed
747        let mut db = create_database_with_withdrawal_requests_contract();
748
749        // Generate a new key pair for the sender
750        let sender_key_pair = generators::generate_key(&mut generators::rng());
751        // Get the sender's address from the public key
752        let sender_address = public_key_to_address(sender_key_pair.public_key());
753
754        // Insert the sender account into the state with a nonce of 1 and a balance of 1 ETH in Wei
755        db.insert_account_info(
756            sender_address,
757            AccountInfo { nonce: 1, balance: U256::from(ETH_TO_WEI), ..Default::default() },
758        );
759
760        // Define the validator public key and withdrawal amount as fixed bytes
761        let validator_public_key = fixed_bytes!("111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
762        let withdrawal_amount = fixed_bytes!("2222222222222222");
763        // Concatenate the validator public key and withdrawal amount into a single byte array
764        let input: Bytes = [&validator_public_key[..], &withdrawal_amount[..]].concat().into();
765        // Ensure the input length is 56 bytes
766        assert_eq!(input.len(), 56);
767
768        // Create a genesis block header with a specified gas limit and gas used
769        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        // Create a transaction with a gas limit higher than the block gas limit
776        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, // higher than block gas limit
783                to: TxKind::Call(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS),
784                value: U256::from(1),
785                input,
786            }),
787        );
788
789        // Create an executor from the state provider
790        let mut executor = executor_provider(chain_spec).executor(db);
791
792        // Execute the block and capture the result
793        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        // Check if the execution result is an error and assert the specific error type
800        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); // initial + 1 Gwei in Wei
873            assert_eq!(
874                *final_balance, expected_final_balance,
875                "Final balance should match expected value after withdrawal"
876            );
877        }
878    }
879}