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