//! EVM config for vanilla optimism. #![doc( html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(feature = "std"), no_std)] // The `optimism` feature must be enabled to use this crate. #![cfg(feature = "optimism")] extern crate alloc; use alloc::{sync::Arc, vec::Vec}; use alloy_consensus::Header; use alloy_primitives::{Address, U256}; use reth_evm::{ConfigureEvm, ConfigureEvmEnv, NextBlockEnvAttributes}; use reth_optimism_chainspec::{DecodeError, OpChainSpec}; use reth_primitives::{transaction::FillTxEnv, Head, TransactionSigned}; use reth_revm::{ inspector_handle_register, primitives::{AnalysisKind, CfgEnvWithHandlerCfg, TxEnv}, Database, Evm, EvmBuilder, GetInspector, }; mod config; pub use config::{revm_spec, revm_spec_by_timestamp_after_bedrock}; mod execute; pub use execute::*; pub mod l1; pub use l1::*; mod error; pub use error::OpBlockExecutionError; use revm_primitives::{ BlobExcessGasAndPrice, BlockEnv, Bytes, CfgEnv, Env, HandlerCfg, OptimismFields, SpecId, TxKind, }; /// Optimism-related EVM configuration. #[derive(Debug, Clone)] pub struct OpEvmConfig { chain_spec: Arc, } impl OpEvmConfig { /// Creates a new [`OpEvmConfig`] with the given chain spec. pub const fn new(chain_spec: Arc) -> Self { Self { chain_spec } } /// Returns the chain spec associated with this configuration. pub const fn chain_spec(&self) -> &Arc { &self.chain_spec } } impl ConfigureEvmEnv for OpEvmConfig { type Header = Header; type Transaction = TransactionSigned; type Error = DecodeError; fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { transaction.fill_tx_env(tx_env, sender); } fn fill_tx_env_system_contract_call( &self, env: &mut Env, caller: Address, contract: Address, data: Bytes, ) { env.tx = TxEnv { caller, transact_to: TxKind::Call(contract), // Explicitly set nonce to None so revm does not do any nonce checks nonce: None, gas_limit: 30_000_000, value: U256::ZERO, data, // Setting the gas price to zero enforces that no value is transferred as part of the // call, and that the call will not count against the block's gas limit gas_price: U256::ZERO, // The chain ID check is not relevant here and is disabled if set to None chain_id: None, // Setting the gas priority fee to None ensures the effective gas price is derived from // the `gas_price` field, which we need to be zero gas_priority_fee: None, access_list: Vec::new(), // blob fields can be None for this tx blob_hashes: Vec::new(), max_fee_per_blob_gas: None, authorization_list: None, optimism: OptimismFields { source_hash: None, mint: None, is_system_transaction: Some(false), // The L1 fee is not charged for the EIP-4788 transaction, submit zero bytes for the // enveloped tx size. enveloped_tx: Some(Bytes::default()), }, }; // ensure the block gas limit is >= the tx env.block.gas_limit = U256::from(env.tx.gas_limit); // disable the base fee check for this call by setting the base fee to zero env.block.basefee = U256::ZERO; } fn fill_cfg_env( &self, cfg_env: &mut CfgEnvWithHandlerCfg, header: &Self::Header, total_difficulty: U256, ) { let spec_id = revm_spec( self.chain_spec(), &Head { number: header.number, timestamp: header.timestamp, difficulty: header.difficulty, total_difficulty, hash: Default::default(), }, ); cfg_env.chain_id = self.chain_spec.chain().id(); cfg_env.perf_analyse_created_bytecodes = AnalysisKind::Analyse; cfg_env.handler_cfg.spec_id = spec_id; cfg_env.handler_cfg.is_optimism = true; } fn next_cfg_and_block_env( &self, parent: &Self::Header, attributes: NextBlockEnvAttributes, ) -> Result<(CfgEnvWithHandlerCfg, BlockEnv), Self::Error> { // configure evm env based on parent block let cfg = CfgEnv::default().with_chain_id(self.chain_spec.chain().id()); // ensure we're not missing any timestamp based hardforks let spec_id = revm_spec_by_timestamp_after_bedrock(&self.chain_spec, attributes.timestamp); // if the parent block did not have excess blob gas (i.e. it was pre-cancun), but it is // cancun now, we need to set the excess blob gas to the default value(0) let blob_excess_gas_and_price = parent .next_block_excess_blob_gas() .or_else(|| (spec_id.is_enabled_in(SpecId::CANCUN)).then_some(0)) .map(BlobExcessGasAndPrice::new); let block_env = BlockEnv { number: U256::from(parent.number + 1), coinbase: attributes.suggested_fee_recipient, timestamp: U256::from(attributes.timestamp), difficulty: U256::ZERO, prevrandao: Some(attributes.prev_randao), gas_limit: U256::from(attributes.gas_limit), // calculate basefee based on parent block's gas usage basefee: self.chain_spec.next_block_base_fee(parent, attributes.timestamp)?, // calculate excess gas based on parent block's blob gas usage blob_excess_gas_and_price, }; let cfg_with_handler_cfg; { cfg_with_handler_cfg = CfgEnvWithHandlerCfg { cfg_env: cfg, handler_cfg: HandlerCfg { spec_id, is_optimism: true }, }; } Ok((cfg_with_handler_cfg, block_env)) } } impl ConfigureEvm for OpEvmConfig { type DefaultExternalContext<'a> = (); fn evm(&self, db: DB) -> Evm<'_, Self::DefaultExternalContext<'_>, DB> { EvmBuilder::default().with_db(db).optimism().build() } fn evm_with_inspector(&self, db: DB, inspector: I) -> Evm<'_, I, DB> where DB: Database, I: GetInspector, { EvmBuilder::default() .with_db(db) .with_external_context(inspector) .optimism() .append_handler_register(inspector_handle_register) .build() } fn default_external_context<'a>(&self) -> Self::DefaultExternalContext<'a> {} } #[cfg(test)] mod tests { use super::*; use alloy_consensus::{constants::KECCAK_EMPTY, Header}; use alloy_eips::eip7685::Requests; use alloy_genesis::Genesis; use alloy_primitives::{bytes, Address, LogData, B256, U256}; use reth_chainspec::ChainSpec; use reth_evm::execute::ProviderError; use reth_execution_types::{ AccountRevertInit, BundleStateInit, Chain, ExecutionOutcome, RevertsInit, }; use reth_optimism_chainspec::BASE_MAINNET; use reth_optimism_primitives::OpPrimitives; use reth_primitives::{Account, Log, Receipt, Receipts, SealedBlockWithSenders, TxType}; use reth_revm::{ db::{BundleState, CacheDB, EmptyDBTyped}, inspectors::NoOpInspector, primitives::{AccountInfo, BlockEnv, CfgEnv, SpecId}, JournaledState, }; use revm_primitives::{EnvWithHandlerCfg, HandlerCfg}; use std::{ collections::{HashMap, HashSet}, sync::Arc, }; fn test_evm_config() -> OpEvmConfig { OpEvmConfig::new(BASE_MAINNET.clone()) } #[test] fn test_fill_cfg_and_block_env() { // Create a default header let header = Header::default(); // Build the ChainSpec for Ethereum mainnet, activating London, Paris, and Shanghai // hardforks let chain_spec = ChainSpec::builder() .chain(0.into()) .genesis(Genesis::default()) .london_activated() .paris_activated() .shanghai_activated() .build(); // Define the total difficulty as zero (default) let total_difficulty = U256::ZERO; // Use the `OpEvmConfig` to create the `cfg_env` and `block_env` based on the ChainSpec, // Header, and total difficulty let (cfg_env, _) = OpEvmConfig::new(Arc::new(OpChainSpec { inner: chain_spec.clone() })) .cfg_and_block_env(&header, total_difficulty); // Assert that the chain ID in the `cfg_env` is correctly set to the chain ID of the // ChainSpec assert_eq!(cfg_env.chain_id, chain_spec.chain().id()); } #[test] fn test_evm_configure() { // Create a default `OpEvmConfig` let evm_config = test_evm_config(); // Initialize an empty database wrapped in CacheDB let db = CacheDB::>::default(); // Create an EVM instance using the configuration and the database let evm = evm_config.evm(db); // Check that the EVM environment is initialized with default values assert_eq!(evm.context.evm.inner.env, Box::default()); // Latest spec ID and no warm preloaded addresses assert_eq!( evm.context.evm.inner.journaled_state, JournaledState::new(SpecId::LATEST, HashSet::default()) ); // Ensure that the accounts database is empty assert!(evm.context.evm.inner.db.accounts.is_empty()); // Ensure that the block hashes database is empty assert!(evm.context.evm.inner.db.block_hashes.is_empty()); // Verify that there are two default contracts in the contracts database assert_eq!(evm.context.evm.inner.db.contracts.len(), 2); assert!(evm.context.evm.inner.db.contracts.contains_key(&KECCAK_EMPTY)); assert!(evm.context.evm.inner.db.contracts.contains_key(&B256::ZERO)); // Ensure that the logs database is empty assert!(evm.context.evm.inner.db.logs.is_empty()); // Optimism in handler assert_eq!(evm.handler.cfg, HandlerCfg { spec_id: SpecId::LATEST, is_optimism: true }); // Default spec ID assert_eq!(evm.handler.spec_id(), SpecId::LATEST); } #[test] fn test_evm_with_env_default_spec() { let evm_config = test_evm_config(); let db = CacheDB::>::default(); let env_with_handler = EnvWithHandlerCfg::default(); let evm = evm_config.evm_with_env(db, env_with_handler.clone()); // Check that the EVM environment assert_eq!(evm.context.evm.env, env_with_handler.env); // Default spec ID assert_eq!(evm.handler.spec_id(), SpecId::LATEST); // Optimism in handler assert_eq!(evm.handler.cfg, HandlerCfg { spec_id: SpecId::LATEST, is_optimism: true }); } #[test] fn test_evm_with_env_custom_cfg() { let evm_config = test_evm_config(); let db = CacheDB::>::default(); // Create a custom configuration environment with a chain ID of 111 let cfg = CfgEnv::default().with_chain_id(111); let env_with_handler = EnvWithHandlerCfg { env: Box::new(Env { cfg: cfg.clone(), block: BlockEnv::default(), tx: TxEnv::default(), }), handler_cfg: Default::default(), }; let evm = evm_config.evm_with_env(db, env_with_handler); // Check that the EVM environment is initialized with the custom environment assert_eq!(evm.context.evm.inner.env.cfg, cfg); // Default spec ID assert_eq!(evm.handler.spec_id(), SpecId::LATEST); // Optimism in handler assert_eq!(evm.handler.cfg, HandlerCfg { spec_id: SpecId::LATEST, is_optimism: true }); } #[test] fn test_evm_with_env_custom_block_and_tx() { let evm_config = test_evm_config(); let db = CacheDB::>::default(); // Create customs block and tx env let block = BlockEnv { basefee: U256::from(1000), gas_limit: U256::from(10_000_000), number: U256::from(42), ..Default::default() }; let tx = TxEnv { gas_limit: 5_000_000, gas_price: U256::from(50), ..Default::default() }; let env_with_handler = EnvWithHandlerCfg { env: Box::new(Env { cfg: CfgEnv::default(), block, tx }), handler_cfg: Default::default(), }; let evm = evm_config.evm_with_env(db, env_with_handler.clone()); // Verify that the block and transaction environments are set correctly assert_eq!(evm.context.evm.env.block, env_with_handler.env.block); assert_eq!(evm.context.evm.env.tx, env_with_handler.env.tx); // Default spec ID assert_eq!(evm.handler.spec_id(), SpecId::LATEST); // Optimism in handler assert_eq!(evm.handler.cfg, HandlerCfg { spec_id: SpecId::LATEST, is_optimism: true }); } #[test] fn test_evm_with_spec_id() { let evm_config = test_evm_config(); let db = CacheDB::>::default(); let handler_cfg = HandlerCfg { spec_id: SpecId::ECOTONE, ..Default::default() }; let env_with_handler = EnvWithHandlerCfg { env: Box::new(Env::default()), handler_cfg }; let evm = evm_config.evm_with_env(db, env_with_handler); // Check that the spec ID is setup properly assert_eq!(evm.handler.spec_id(), SpecId::ECOTONE); // Optimism in handler assert_eq!(evm.handler.cfg, HandlerCfg { spec_id: SpecId::ECOTONE, is_optimism: true }); } #[test] fn test_evm_with_inspector() { let evm_config = test_evm_config(); let db = CacheDB::>::default(); // No operation inspector let noop = NoOpInspector; let evm = evm_config.evm_with_inspector(db, noop); // Check that the inspector is set correctly assert_eq!(evm.context.external, noop); // Check that the EVM environment is initialized with default values assert_eq!(evm.context.evm.inner.env, Box::default()); // Latest spec ID and no warm preloaded addresses assert_eq!( evm.context.evm.inner.journaled_state, JournaledState::new(SpecId::LATEST, HashSet::default()) ); // Ensure that the accounts database is empty assert!(evm.context.evm.inner.db.accounts.is_empty()); // Ensure that the block hashes database is empty assert!(evm.context.evm.inner.db.block_hashes.is_empty()); // Verify that there are two default contracts in the contracts database assert_eq!(evm.context.evm.inner.db.contracts.len(), 2); assert!(evm.context.evm.inner.db.contracts.contains_key(&KECCAK_EMPTY)); assert!(evm.context.evm.inner.db.contracts.contains_key(&B256::ZERO)); // Ensure that the logs database is empty assert!(evm.context.evm.inner.db.logs.is_empty()); // Default spec ID assert_eq!(evm.handler.spec_id(), SpecId::LATEST); // Optimism in handler assert_eq!(evm.handler.cfg, HandlerCfg { spec_id: SpecId::LATEST, is_optimism: true }); } #[test] fn test_evm_with_env_and_default_inspector() { let evm_config = test_evm_config(); let db = CacheDB::>::default(); let env_with_handler = EnvWithHandlerCfg::default(); let evm = evm_config.evm_with_env_and_inspector(db, env_with_handler.clone(), NoOpInspector); // Check that the EVM environment is set to default values assert_eq!(evm.context.evm.env, env_with_handler.env); assert_eq!(evm.context.external, NoOpInspector); assert_eq!(evm.handler.spec_id(), SpecId::LATEST); // Optimism in handler assert_eq!(evm.handler.cfg, HandlerCfg { spec_id: SpecId::LATEST, is_optimism: true }); } #[test] fn test_evm_with_env_inspector_and_custom_cfg() { let evm_config = test_evm_config(); let db = CacheDB::>::default(); let cfg = CfgEnv::default().with_chain_id(111); let block = BlockEnv::default(); let tx = TxEnv::default(); let env_with_handler = EnvWithHandlerCfg { env: Box::new(Env { cfg: cfg.clone(), block, tx }), handler_cfg: Default::default(), }; let evm = evm_config.evm_with_env_and_inspector(db, env_with_handler, NoOpInspector); // Check that the EVM environment is set with custom configuration assert_eq!(evm.context.evm.env.cfg, cfg); assert_eq!(evm.context.external, NoOpInspector); assert_eq!(evm.handler.spec_id(), SpecId::LATEST); // Optimism in handler assert_eq!(evm.handler.cfg, HandlerCfg { spec_id: SpecId::LATEST, is_optimism: true }); } #[test] fn test_evm_with_env_inspector_and_custom_block_tx() { let evm_config = test_evm_config(); let db = CacheDB::>::default(); // Create custom block and tx environment let block = BlockEnv { basefee: U256::from(1000), gas_limit: U256::from(10_000_000), number: U256::from(42), ..Default::default() }; let tx = TxEnv { gas_limit: 5_000_000, gas_price: U256::from(50), ..Default::default() }; let env_with_handler = EnvWithHandlerCfg { env: Box::new(Env { cfg: CfgEnv::default(), block, tx }), handler_cfg: Default::default(), }; let evm = evm_config.evm_with_env_and_inspector(db, env_with_handler.clone(), NoOpInspector); // Verify that the block and transaction environments are set correctly assert_eq!(evm.context.evm.env.block, env_with_handler.env.block); assert_eq!(evm.context.evm.env.tx, env_with_handler.env.tx); assert_eq!(evm.context.external, NoOpInspector); assert_eq!(evm.handler.spec_id(), SpecId::LATEST); // Optimism in handler assert_eq!(evm.handler.cfg, HandlerCfg { spec_id: SpecId::LATEST, is_optimism: true }); } #[test] fn test_evm_with_env_inspector_and_spec_id() { let evm_config = test_evm_config(); let db = CacheDB::>::default(); let handler_cfg = HandlerCfg { spec_id: SpecId::ECOTONE, ..Default::default() }; let env_with_handler = EnvWithHandlerCfg { env: Box::new(Env::default()), handler_cfg }; let evm = evm_config.evm_with_env_and_inspector(db, env_with_handler.clone(), NoOpInspector); // Check that the spec ID is set properly assert_eq!(evm.handler.spec_id(), SpecId::ECOTONE); assert_eq!(evm.context.evm.env, env_with_handler.env); assert_eq!(evm.context.external, NoOpInspector); // Check that the spec ID is setup properly assert_eq!(evm.handler.spec_id(), SpecId::ECOTONE); // Optimism in handler assert_eq!(evm.handler.cfg, HandlerCfg { spec_id: SpecId::ECOTONE, is_optimism: true }); } #[test] fn receipts_by_block_hash() { // Create a default SealedBlockWithSenders object let block: SealedBlockWithSenders = Default::default(); // Define block hashes for block1 and block2 let block1_hash = B256::new([0x01; 32]); let block2_hash = B256::new([0x02; 32]); // Clone the default block into block1 and block2 let mut block1 = block.clone(); let mut block2 = block; // Set the hashes of block1 and block2 block1.block.header.set_block_number(10); block1.block.header.set_hash(block1_hash); block2.block.header.set_block_number(11); block2.block.header.set_hash(block2_hash); // Create a random receipt object, receipt1 let receipt1 = Receipt { tx_type: TxType::Legacy, cumulative_gas_used: 46913, logs: vec![], success: true, deposit_nonce: Some(18), deposit_receipt_version: Some(34), }; // Create another random receipt object, receipt2 let receipt2 = Receipt { tx_type: TxType::Legacy, cumulative_gas_used: 1325345, logs: vec![], success: true, deposit_nonce: Some(18), deposit_receipt_version: Some(34), }; // Create a Receipts object with a vector of receipt vectors let receipts = Receipts { receipt_vec: vec![vec![Some(receipt1.clone())], vec![Some(receipt2)]] }; // Create an ExecutionOutcome object with the created bundle, receipts, an empty requests // vector, and first_block set to 10 let execution_outcome = ExecutionOutcome { bundle: Default::default(), receipts, requests: vec![], first_block: 10, }; // Create a Chain object with a BTreeMap of blocks mapped to their block numbers, // including block1_hash and block2_hash, and the execution_outcome let chain: Chain = Chain::new([block1, block2], execution_outcome.clone(), None); // Assert that the proper receipt vector is returned for block1_hash assert_eq!(chain.receipts_by_block_hash(block1_hash), Some(vec![&receipt1])); // Create an ExecutionOutcome object with a single receipt vector containing receipt1 let execution_outcome1 = ExecutionOutcome { bundle: Default::default(), receipts: Receipts { receipt_vec: vec![vec![Some(receipt1)]] }, requests: vec![], first_block: 10, }; // Assert that the execution outcome at the first block contains only the first receipt assert_eq!(chain.execution_outcome_at_block(10), Some(execution_outcome1)); // Assert that the execution outcome at the tip block contains the whole execution outcome assert_eq!(chain.execution_outcome_at_block(11), Some(execution_outcome)); } #[test] fn test_initialisation() { // Create a new BundleState object with initial data let bundle = BundleState::new( vec![(Address::new([2; 20]), None, Some(AccountInfo::default()), HashMap::default())], vec![vec![(Address::new([2; 20]), None, vec![])]], vec![], ); // Create a Receipts object with a vector of receipt vectors let receipts = Receipts { receipt_vec: vec![vec![Some(Receipt { tx_type: TxType::Legacy, cumulative_gas_used: 46913, logs: vec![], success: true, deposit_nonce: Some(18), deposit_receipt_version: Some(34), })]], }; // Create a Requests object with a vector of requests let requests = vec![Requests::new(vec![bytes!("dead"), bytes!("beef"), bytes!("beebee")])]; // Define the first block number let first_block = 123; // Create a ExecutionOutcome object with the created bundle, receipts, requests, and // first_block let exec_res = ExecutionOutcome { bundle: bundle.clone(), receipts: receipts.clone(), requests: requests.clone(), first_block, }; // Assert that creating a new ExecutionOutcome using the constructor matches exec_res assert_eq!( ExecutionOutcome::new(bundle, receipts.clone(), first_block, requests.clone()), exec_res ); // Create a BundleStateInit object and insert initial data let mut state_init: BundleStateInit = HashMap::default(); state_init .insert(Address::new([2; 20]), (None, Some(Account::default()), HashMap::default())); // Create a HashMap for account reverts and insert initial data let mut revert_inner: HashMap = HashMap::default(); revert_inner.insert(Address::new([2; 20]), (None, vec![])); // Create a RevertsInit object and insert the revert_inner data let mut revert_init: RevertsInit = HashMap::default(); revert_init.insert(123, revert_inner); // Assert that creating a new ExecutionOutcome using the new_init method matches // exec_res assert_eq!( ExecutionOutcome::new_init( state_init, revert_init, vec![], receipts, first_block, requests, ), exec_res ); } #[test] fn test_block_number_to_index() { // Create a Receipts object with a vector of receipt vectors let receipts = Receipts { receipt_vec: vec![vec![Some(Receipt { tx_type: TxType::Legacy, cumulative_gas_used: 46913, logs: vec![], success: true, deposit_nonce: Some(18), deposit_receipt_version: Some(34), })]], }; // Define the first block number let first_block = 123; // Create a ExecutionOutcome object with the created bundle, receipts, requests, and // first_block let exec_res = ExecutionOutcome { bundle: Default::default(), receipts, requests: vec![], first_block, }; // Test before the first block assert_eq!(exec_res.block_number_to_index(12), None); // Test after after the first block but index larger than receipts length assert_eq!(exec_res.block_number_to_index(133), None); // Test after the first block assert_eq!(exec_res.block_number_to_index(123), Some(0)); } #[test] fn test_get_logs() { // Create a Receipts object with a vector of receipt vectors let receipts = Receipts { receipt_vec: vec![vec![Some(Receipt { tx_type: TxType::Legacy, cumulative_gas_used: 46913, logs: vec![Log::::default()], success: true, deposit_nonce: Some(18), deposit_receipt_version: Some(34), })]], }; // Define the first block number let first_block = 123; // Create a ExecutionOutcome object with the created bundle, receipts, requests, and // first_block let exec_res = ExecutionOutcome { bundle: Default::default(), receipts, requests: vec![], first_block, }; // Get logs for block number 123 let logs: Vec<&Log> = exec_res.logs(123).unwrap().collect(); // Assert that the logs match the expected logs assert_eq!(logs, vec![&Log::::default()]); } #[test] fn test_receipts_by_block() { // Create a Receipts object with a vector of receipt vectors let receipts = Receipts { receipt_vec: vec![vec![Some(Receipt { tx_type: TxType::Legacy, cumulative_gas_used: 46913, logs: vec![Log::::default()], success: true, deposit_nonce: Some(18), deposit_receipt_version: Some(34), })]], }; // Define the first block number let first_block = 123; // Create a ExecutionOutcome object with the created bundle, receipts, requests, and // first_block let exec_res = ExecutionOutcome { bundle: Default::default(), // Default value for bundle receipts, // Include the created receipts requests: vec![], // Empty vector for requests first_block, // Set the first block number }; // Get receipts for block number 123 and convert the result into a vector let receipts_by_block: Vec<_> = exec_res.receipts_by_block(123).iter().collect(); // Assert that the receipts for block number 123 match the expected receipts assert_eq!( receipts_by_block, vec![&Some(Receipt { tx_type: TxType::Legacy, cumulative_gas_used: 46913, logs: vec![Log::::default()], success: true, deposit_nonce: Some(18), deposit_receipt_version: Some(34), })] ); } #[test] fn test_receipts_len() { // Create a Receipts object with a vector of receipt vectors let receipts = Receipts { receipt_vec: vec![vec![Some(Receipt { tx_type: TxType::Legacy, cumulative_gas_used: 46913, logs: vec![Log::::default()], success: true, deposit_nonce: Some(18), deposit_receipt_version: Some(34), })]], }; // Create an empty Receipts object let receipts_empty = Receipts:: { receipt_vec: vec![] }; // Define the first block number let first_block = 123; // Create a ExecutionOutcome object with the created bundle, receipts, requests, and // first_block let exec_res = ExecutionOutcome { bundle: Default::default(), // Default value for bundle receipts, // Include the created receipts requests: vec![], // Empty vector for requests first_block, // Set the first block number }; // Assert that the length of receipts in exec_res is 1 assert_eq!(exec_res.len(), 1); // Assert that exec_res is not empty assert!(!exec_res.is_empty()); // Create a ExecutionOutcome object with an empty Receipts object let exec_res_empty_receipts = ExecutionOutcome { bundle: Default::default(), // Default value for bundle receipts: receipts_empty, // Include the empty receipts requests: vec![], // Empty vector for requests first_block, // Set the first block number }; // Assert that the length of receipts in exec_res_empty_receipts is 0 assert_eq!(exec_res_empty_receipts.len(), 0); // Assert that exec_res_empty_receipts is empty assert!(exec_res_empty_receipts.is_empty()); } #[test] fn test_revert_to() { // Create a random receipt object let receipt = Receipt { tx_type: TxType::Legacy, cumulative_gas_used: 46913, logs: vec![], success: true, deposit_nonce: Some(18), deposit_receipt_version: Some(34), }; // Create a Receipts object with a vector of receipt vectors let receipts = Receipts { receipt_vec: vec![vec![Some(receipt.clone())], vec![Some(receipt.clone())]], }; // Define the first block number let first_block = 123; // Create a request. let request = bytes!("deadbeef"); // Create a vector of Requests containing the request. let requests = vec![Requests::new(vec![request.clone()]), Requests::new(vec![request.clone()])]; // Create a ExecutionOutcome object with the created bundle, receipts, requests, and // first_block let mut exec_res = ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block }; // Assert that the revert_to method returns true when reverting to the initial block number. assert!(exec_res.revert_to(123)); // Assert that the receipts are properly cut after reverting to the initial block number. assert_eq!(exec_res.receipts, Receipts { receipt_vec: vec![vec![Some(receipt)]] }); // Assert that the requests are properly cut after reverting to the initial block number. assert_eq!(exec_res.requests, vec![Requests::new(vec![request])]); // Assert that the revert_to method returns false when attempting to revert to a block // number greater than the initial block number. assert!(!exec_res.revert_to(133)); // Assert that the revert_to method returns false when attempting to revert to a block // number less than the initial block number. assert!(!exec_res.revert_to(10)); } #[test] fn test_extend_execution_outcome() { // Create a Receipt object with specific attributes. let receipt = Receipt { tx_type: TxType::Legacy, cumulative_gas_used: 46913, logs: vec![], success: true, deposit_nonce: Some(18), deposit_receipt_version: Some(34), }; // Create a Receipts object containing the receipt. let receipts = Receipts { receipt_vec: vec![vec![Some(receipt.clone())]] }; // Create a request. let request = bytes!("deadbeef"); // Create a vector of Requests containing the request. let requests = vec![Requests::new(vec![request.clone()])]; // Define the initial block number. let first_block = 123; // Create an ExecutionOutcome object. let mut exec_res = ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block }; // Extend the ExecutionOutcome object by itself. exec_res.extend(exec_res.clone()); // Assert the extended ExecutionOutcome matches the expected outcome. assert_eq!( exec_res, ExecutionOutcome { bundle: Default::default(), receipts: Receipts { receipt_vec: vec![vec![Some(receipt.clone())], vec![Some(receipt)]] }, requests: vec![Requests::new(vec![request.clone()]), Requests::new(vec![request])], first_block: 123, } ); } #[test] fn test_split_at_execution_outcome() { // Create a random receipt object let receipt = Receipt { tx_type: TxType::Legacy, cumulative_gas_used: 46913, logs: vec![], success: true, deposit_nonce: Some(18), deposit_receipt_version: Some(34), }; // Create a Receipts object with a vector of receipt vectors let receipts = Receipts { receipt_vec: vec![ vec![Some(receipt.clone())], vec![Some(receipt.clone())], vec![Some(receipt.clone())], ], }; // Define the first block number let first_block = 123; // Create a request. let request = bytes!("deadbeef"); // Create a vector of Requests containing the request. let requests = vec![ Requests::new(vec![request.clone()]), Requests::new(vec![request.clone()]), Requests::new(vec![request.clone()]), ]; // Create a ExecutionOutcome object with the created bundle, receipts, requests, and // first_block let exec_res = ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block }; // Split the ExecutionOutcome at block number 124 let result = exec_res.clone().split_at(124); // Define the expected lower ExecutionOutcome after splitting let lower_execution_outcome = ExecutionOutcome { bundle: Default::default(), receipts: Receipts { receipt_vec: vec![vec![Some(receipt.clone())]] }, requests: vec![Requests::new(vec![request.clone()])], first_block, }; // Define the expected higher ExecutionOutcome after splitting let higher_execution_outcome = ExecutionOutcome { bundle: Default::default(), receipts: Receipts { receipt_vec: vec![vec![Some(receipt.clone())], vec![Some(receipt)]], }, requests: vec![Requests::new(vec![request.clone()]), Requests::new(vec![request])], first_block: 124, }; // Assert that the split result matches the expected lower and higher outcomes assert_eq!(result.0, Some(lower_execution_outcome)); assert_eq!(result.1, higher_execution_outcome); // Assert that splitting at the first block number returns None for the lower outcome assert_eq!(exec_res.clone().split_at(123), (None, exec_res)); } }