Files
reth/crates/optimism/evm/src/execute.rs
2025-03-10 08:52:55 +00:00

226 lines
8.2 KiB
Rust

//! Optimism block execution strategy.
use crate::{OpEvmConfig, OpRethReceiptBuilder};
use alloc::sync::Arc;
use reth_evm::execute::BasicBlockExecutorProvider;
use reth_optimism_chainspec::OpChainSpec;
/// Helper type with backwards compatible methods to obtain executor providers.
#[derive(Debug)]
pub struct OpExecutorProvider;
impl OpExecutorProvider {
/// Creates a new default optimism executor strategy factory.
pub fn optimism(chain_spec: Arc<OpChainSpec>) -> BasicBlockExecutorProvider<OpEvmConfig> {
BasicBlockExecutorProvider::new(OpEvmConfig::new(
chain_spec,
OpRethReceiptBuilder::default(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::OpChainSpec;
use alloy_consensus::{Block, BlockBody, Header, TxEip1559};
use alloy_primitives::{
b256, Address, PrimitiveSignature as Signature, StorageKey, StorageValue, U256,
};
use op_alloy_consensus::{OpTypedTransaction, TxDeposit};
use op_revm::constants::L1_BLOCK_CONTRACT;
use reth_chainspec::MIN_TRANSACTION_GAS;
use reth_evm::execute::{BasicBlockExecutorProvider, BlockExecutorProvider, Executor};
use reth_optimism_chainspec::OpChainSpecBuilder;
use reth_optimism_primitives::{OpReceipt, OpTransactionSigned};
use reth_primitives_traits::{Account, RecoveredBlock};
use reth_revm::{database::StateProviderDatabase, test_utils::StateProviderTest};
use std::{collections::HashMap, str::FromStr};
fn create_op_state_provider() -> StateProviderTest {
let mut db = StateProviderTest::default();
let l1_block_contract_account =
Account { balance: U256::ZERO, bytecode_hash: None, nonce: 1 };
let mut l1_block_storage = HashMap::default();
// base fee
l1_block_storage.insert(StorageKey::with_last_byte(1), StorageValue::from(1000000000));
// l1 fee overhead
l1_block_storage.insert(StorageKey::with_last_byte(5), StorageValue::from(188));
// l1 fee scalar
l1_block_storage.insert(StorageKey::with_last_byte(6), StorageValue::from(684000));
// l1 free scalars post ecotone
l1_block_storage.insert(
StorageKey::with_last_byte(3),
StorageValue::from_str(
"0x0000000000000000000000000000000000001db0000d27300000000000000005",
)
.unwrap(),
);
db.insert_account(L1_BLOCK_CONTRACT, l1_block_contract_account, None, l1_block_storage);
db
}
fn executor_provider(chain_spec: Arc<OpChainSpec>) -> BasicBlockExecutorProvider<OpEvmConfig> {
BasicBlockExecutorProvider::new(OpEvmConfig::new(
chain_spec,
OpRethReceiptBuilder::default(),
))
}
#[test]
fn op_deposit_fields_pre_canyon() {
let header = Header {
timestamp: 1,
number: 1,
gas_limit: 1_000_000,
gas_used: 42_000,
receipts_root: b256!(
"0x83465d1e7d01578c0d609be33570f91242f013e9e295b0879905346abbd63731"
),
..Default::default()
};
let mut db = create_op_state_provider();
let addr = Address::ZERO;
let account = Account { balance: U256::MAX, ..Account::default() };
db.insert_account(addr, account, None, HashMap::default());
let chain_spec = Arc::new(OpChainSpecBuilder::base_mainnet().regolith_activated().build());
let tx = OpTransactionSigned::new_unhashed(
OpTypedTransaction::Eip1559(TxEip1559 {
chain_id: chain_spec.chain.id(),
nonce: 0,
gas_limit: MIN_TRANSACTION_GAS,
to: addr.into(),
..Default::default()
}),
Signature::test_signature(),
);
let tx_deposit = OpTransactionSigned::new_unhashed(
OpTypedTransaction::Deposit(op_alloy_consensus::TxDeposit {
from: addr,
to: addr.into(),
gas_limit: MIN_TRANSACTION_GAS,
..Default::default()
}),
Signature::test_signature(),
);
let provider = executor_provider(chain_spec);
let mut executor = provider.executor(StateProviderDatabase::new(&db));
// make sure the L1 block contract state is preloaded.
executor.with_state_mut(|state| {
state.load_cache_account(L1_BLOCK_CONTRACT).unwrap();
});
// Attempt to execute a block with one deposit and one non-deposit transaction
let output = executor
.execute(&RecoveredBlock::new_unhashed(
Block {
header,
body: BlockBody { transactions: vec![tx, tx_deposit], ..Default::default() },
},
vec![addr, addr],
))
.unwrap();
let receipts = &output.receipts;
let tx_receipt = &receipts[0];
let deposit_receipt = &receipts[1];
assert!(!matches!(tx_receipt, OpReceipt::Deposit(_)));
// deposit_nonce is present only in deposit transactions
let OpReceipt::Deposit(deposit_receipt) = deposit_receipt else {
panic!("expected deposit")
};
assert!(deposit_receipt.deposit_nonce.is_some());
// deposit_receipt_version is not present in pre canyon transactions
assert!(deposit_receipt.deposit_receipt_version.is_none());
}
#[test]
fn op_deposit_fields_post_canyon() {
// ensure_create2_deployer will fail if timestamp is set to less than 2
let header = Header {
timestamp: 2,
number: 1,
gas_limit: 1_000_000,
gas_used: 42_000,
receipts_root: b256!(
"0xfffc85c4004fd03c7bfbe5491fae98a7473126c099ac11e8286fd0013f15f908"
),
..Default::default()
};
let mut db = create_op_state_provider();
let addr = Address::ZERO;
let account = Account { balance: U256::MAX, ..Account::default() };
db.insert_account(addr, account, None, HashMap::default());
let chain_spec = Arc::new(OpChainSpecBuilder::base_mainnet().canyon_activated().build());
let tx = OpTransactionSigned::new_unhashed(
OpTypedTransaction::Eip1559(TxEip1559 {
chain_id: chain_spec.chain.id(),
nonce: 0,
gas_limit: MIN_TRANSACTION_GAS,
to: addr.into(),
..Default::default()
}),
Signature::test_signature(),
);
let tx_deposit = OpTransactionSigned::new_unhashed(
OpTypedTransaction::Deposit(op_alloy_consensus::TxDeposit {
from: addr,
to: addr.into(),
gas_limit: MIN_TRANSACTION_GAS,
..Default::default()
}),
TxDeposit::signature(),
);
let provider = executor_provider(chain_spec);
let mut executor = provider.executor(StateProviderDatabase::new(&db));
// make sure the L1 block contract state is preloaded.
executor.with_state_mut(|state| {
state.load_cache_account(L1_BLOCK_CONTRACT).unwrap();
});
// attempt to execute an empty block with parent beacon block root, this should not fail
let output = executor
.execute(&RecoveredBlock::new_unhashed(
Block {
header,
body: BlockBody { transactions: vec![tx, tx_deposit], ..Default::default() },
},
vec![addr, addr],
))
.expect("Executing a block while canyon is active should not fail");
let receipts = &output.receipts;
let tx_receipt = &receipts[0];
let deposit_receipt = &receipts[1];
// deposit_receipt_version is set to 1 for post canyon deposit transactions
assert!(!matches!(tx_receipt, OpReceipt::Deposit(_)));
let OpReceipt::Deposit(deposit_receipt) = deposit_receipt else {
panic!("expected deposit")
};
assert_eq!(deposit_receipt.deposit_receipt_version, Some(1));
// deposit_nonce is present only in deposit transactions
assert!(deposit_receipt.deposit_nonce.is_some());
}
}