reth_optimism_evm/
execute.rs

1//! Optimism block execution strategy.
2
3/// Helper type with backwards compatible methods to obtain executor providers.
4pub type OpExecutorProvider = crate::OpEvmConfig;
5
6#[cfg(test)]
7mod tests {
8    use crate::{OpEvmConfig, OpRethReceiptBuilder};
9    use alloc::sync::Arc;
10    use alloy_consensus::{Block, BlockBody, Header, SignableTransaction, TxEip1559};
11    use alloy_primitives::{b256, Address, Signature, StorageKey, StorageValue, U256};
12    use op_alloy_consensus::TxDeposit;
13    use op_revm::constants::L1_BLOCK_CONTRACT;
14    use reth_chainspec::MIN_TRANSACTION_GAS;
15    use reth_evm::execute::{BasicBlockExecutor, Executor};
16    use reth_optimism_chainspec::{OpChainSpec, OpChainSpecBuilder};
17    use reth_optimism_primitives::{OpReceipt, OpTransactionSigned};
18    use reth_primitives_traits::{Account, RecoveredBlock};
19    use reth_revm::{database::StateProviderDatabase, test_utils::StateProviderTest};
20    use std::{collections::HashMap, str::FromStr};
21
22    fn create_op_state_provider() -> StateProviderTest {
23        let mut db = StateProviderTest::default();
24
25        let l1_block_contract_account =
26            Account { balance: U256::ZERO, bytecode_hash: None, nonce: 1 };
27
28        let mut l1_block_storage = HashMap::default();
29        // base fee
30        l1_block_storage.insert(StorageKey::with_last_byte(1), StorageValue::from(1000000000));
31        // l1 fee overhead
32        l1_block_storage.insert(StorageKey::with_last_byte(5), StorageValue::from(188));
33        // l1 fee scalar
34        l1_block_storage.insert(StorageKey::with_last_byte(6), StorageValue::from(684000));
35        // l1 free scalars post ecotone
36        l1_block_storage.insert(
37            StorageKey::with_last_byte(3),
38            StorageValue::from_str(
39                "0x0000000000000000000000000000000000001db0000d27300000000000000005",
40            )
41            .unwrap(),
42        );
43
44        db.insert_account(L1_BLOCK_CONTRACT, l1_block_contract_account, None, l1_block_storage);
45
46        db
47    }
48
49    fn evm_config(chain_spec: Arc<OpChainSpec>) -> OpEvmConfig {
50        OpEvmConfig::new(chain_spec, OpRethReceiptBuilder::default())
51    }
52
53    #[test]
54    fn op_deposit_fields_pre_canyon() {
55        let header = Header {
56            timestamp: 1,
57            number: 1,
58            gas_limit: 1_000_000,
59            gas_used: 42_000,
60            receipts_root: b256!(
61                "0x83465d1e7d01578c0d609be33570f91242f013e9e295b0879905346abbd63731"
62            ),
63            ..Default::default()
64        };
65
66        let mut db = create_op_state_provider();
67
68        let addr = Address::ZERO;
69        let account = Account { balance: U256::MAX, ..Account::default() };
70        db.insert_account(addr, account, None, HashMap::default());
71
72        let chain_spec = Arc::new(OpChainSpecBuilder::base_mainnet().regolith_activated().build());
73
74        let tx: OpTransactionSigned = TxEip1559 {
75            chain_id: chain_spec.chain.id(),
76            nonce: 0,
77            gas_limit: MIN_TRANSACTION_GAS,
78            to: addr.into(),
79            ..Default::default()
80        }
81        .into_signed(Signature::test_signature())
82        .into();
83
84        let tx_deposit: OpTransactionSigned = TxDeposit {
85            from: addr,
86            to: addr.into(),
87            gas_limit: MIN_TRANSACTION_GAS,
88            ..Default::default()
89        }
90        .into();
91
92        let provider = evm_config(chain_spec);
93        let mut executor = BasicBlockExecutor::new(provider, StateProviderDatabase::new(&db));
94
95        // make sure the L1 block contract state is preloaded.
96        executor.with_state_mut(|state| {
97            state.load_cache_account(L1_BLOCK_CONTRACT).unwrap();
98        });
99
100        // Attempt to execute a block with one deposit and one non-deposit transaction
101        let output = executor
102            .execute(&RecoveredBlock::new_unhashed(
103                Block {
104                    header,
105                    body: BlockBody { transactions: vec![tx, tx_deposit], ..Default::default() },
106                },
107                vec![addr, addr],
108            ))
109            .unwrap();
110
111        let receipts = &output.receipts;
112        let tx_receipt = &receipts[0];
113        let deposit_receipt = &receipts[1];
114
115        assert!(!matches!(tx_receipt, OpReceipt::Deposit(_)));
116        // deposit_nonce is present only in deposit transactions
117        let OpReceipt::Deposit(deposit_receipt) = deposit_receipt else {
118            panic!("expected deposit")
119        };
120        assert!(deposit_receipt.deposit_nonce.is_some());
121        // deposit_receipt_version is not present in pre canyon transactions
122        assert!(deposit_receipt.deposit_receipt_version.is_none());
123    }
124
125    #[test]
126    fn op_deposit_fields_post_canyon() {
127        // ensure_create2_deployer will fail if timestamp is set to less than 2
128        let header = Header {
129            timestamp: 2,
130            number: 1,
131            gas_limit: 1_000_000,
132            gas_used: 42_000,
133            receipts_root: b256!(
134                "0xfffc85c4004fd03c7bfbe5491fae98a7473126c099ac11e8286fd0013f15f908"
135            ),
136            ..Default::default()
137        };
138
139        let mut db = create_op_state_provider();
140        let addr = Address::ZERO;
141        let account = Account { balance: U256::MAX, ..Account::default() };
142
143        db.insert_account(addr, account, None, HashMap::default());
144
145        let chain_spec = Arc::new(OpChainSpecBuilder::base_mainnet().canyon_activated().build());
146
147        let tx: OpTransactionSigned = TxEip1559 {
148            chain_id: chain_spec.chain.id(),
149            nonce: 0,
150            gas_limit: MIN_TRANSACTION_GAS,
151            to: addr.into(),
152            ..Default::default()
153        }
154        .into_signed(Signature::test_signature())
155        .into();
156
157        let tx_deposit: OpTransactionSigned = TxDeposit {
158            from: addr,
159            to: addr.into(),
160            gas_limit: MIN_TRANSACTION_GAS,
161            ..Default::default()
162        }
163        .into();
164
165        let provider = evm_config(chain_spec);
166        let mut executor = BasicBlockExecutor::new(provider, StateProviderDatabase::new(&db));
167
168        // make sure the L1 block contract state is preloaded.
169        executor.with_state_mut(|state| {
170            state.load_cache_account(L1_BLOCK_CONTRACT).unwrap();
171        });
172
173        // attempt to execute an empty block with parent beacon block root, this should not fail
174        let output = executor
175            .execute(&RecoveredBlock::new_unhashed(
176                Block {
177                    header,
178                    body: BlockBody { transactions: vec![tx, tx_deposit], ..Default::default() },
179                },
180                vec![addr, addr],
181            ))
182            .expect("Executing a block while canyon is active should not fail");
183
184        let receipts = &output.receipts;
185        let tx_receipt = &receipts[0];
186        let deposit_receipt = &receipts[1];
187
188        // deposit_receipt_version is set to 1 for post canyon deposit transactions
189        assert!(!matches!(tx_receipt, OpReceipt::Deposit(_)));
190        let OpReceipt::Deposit(deposit_receipt) = deposit_receipt else {
191            panic!("expected deposit")
192        };
193        assert_eq!(deposit_receipt.deposit_receipt_version, Some(1));
194
195        // deposit_nonce is present only in deposit transactions
196        assert!(deposit_receipt.deposit_nonce.is_some());
197    }
198}