reth_optimism_evm/
execute.rs

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