#![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)]
#![cfg(feature = "optimism")]
extern crate alloc;
use alloc::{sync::Arc, vec::Vec};
use alloy_primitives::{Address, U256};
use reth_evm::{ConfigureEvm, ConfigureEvmEnv, NextBlockEnvAttributes};
use reth_optimism_chainspec::{DecodeError, OpChainSpec};
use reth_primitives::{
revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg, TxEnv},
transaction::FillTxEnv,
Head, Header, TransactionSigned,
};
use reth_revm::{inspector_handle_register, 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::OptimismBlockExecutionError;
use revm_primitives::{
BlobExcessGasAndPrice, BlockEnv, Bytes, CfgEnv, Env, HandlerCfg, OptimismFields, SpecId, TxKind,
};
#[derive(Debug, Clone)]
pub struct OpEvmConfig {
chain_spec: Arc<OpChainSpec>,
}
impl OpEvmConfig {
pub const fn new(chain_spec: Arc<OpChainSpec>) -> Self {
Self { chain_spec }
}
pub fn chain_spec(&self) -> &OpChainSpec {
&self.chain_spec
}
}
impl ConfigureEvmEnv for OpEvmConfig {
type Header = Header;
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),
nonce: None,
gas_limit: 30_000_000,
value: U256::ZERO,
data,
gas_price: U256::ZERO,
chain_id: None,
gas_priority_fee: None,
access_list: Vec::new(),
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),
enveloped_tx: Some(Bytes::default()),
},
};
env.block.gas_limit = U256::from(env.tx.gas_limit);
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> {
let cfg = CfgEnv::default().with_chain_id(self.chain_spec.chain().id());
let spec_id = revm_spec_by_timestamp_after_bedrock(&self.chain_spec, attributes.timestamp);
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(parent.gas_limit),
basefee: self.chain_spec.next_block_base_fee(parent, attributes.timestamp)?,
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<DB: Database>(&self, db: DB) -> Evm<'_, Self::DefaultExternalContext<'_>, DB> {
EvmBuilder::default().with_db(db).optimism().build()
}
fn evm_with_inspector<DB, I>(&self, db: DB, inspector: I) -> Evm<'_, I, DB>
where
DB: Database,
I: GetInspector<DB>,
{
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;
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_primitives::{
revm_primitives::{AccountInfo, BlockEnv, CfgEnv, SpecId},
Account, Header, Log, Receipt, Receipts, SealedBlockWithSenders, TxType,
};
use reth_revm::{
db::{BundleState, CacheDB, EmptyDBTyped},
inspectors::NoOpInspector,
JournaledState,
};
use revm_primitives::{CfgEnvWithHandlerCfg, 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() {
let mut cfg_env = CfgEnvWithHandlerCfg::new_with_spec_id(CfgEnv::default(), SpecId::LATEST);
let mut block_env = BlockEnv::default();
let header = Header::default();
let chain_spec = ChainSpec::builder()
.chain(0.into())
.genesis(Genesis::default())
.london_activated()
.paris_activated()
.shanghai_activated()
.build();
let total_difficulty = U256::ZERO;
OpEvmConfig::new(Arc::new(OpChainSpec { inner: chain_spec.clone() }))
.fill_cfg_and_block_env(&mut cfg_env, &mut block_env, &header, total_difficulty);
assert_eq!(cfg_env.chain_id, chain_spec.chain().id());
}
#[test]
fn test_evm_configure() {
let evm_config = test_evm_config();
let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
let evm = evm_config.evm(db);
assert_eq!(evm.context.evm.inner.env, Box::default());
assert_eq!(
evm.context.evm.inner.journaled_state,
JournaledState::new(SpecId::LATEST, HashSet::default())
);
assert!(evm.context.evm.inner.db.accounts.is_empty());
assert!(evm.context.evm.inner.db.block_hashes.is_empty());
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));
assert!(evm.context.evm.inner.db.logs.is_empty());
assert_eq!(evm.handler.cfg, HandlerCfg { spec_id: SpecId::LATEST, is_optimism: true });
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::<EmptyDBTyped<ProviderError>>::default();
let env_with_handler = EnvWithHandlerCfg::default();
let evm = evm_config.evm_with_env(db, env_with_handler.clone());
assert_eq!(evm.context.evm.env, env_with_handler.env);
assert_eq!(evm.handler.spec_id(), SpecId::LATEST);
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::<EmptyDBTyped<ProviderError>>::default();
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);
assert_eq!(evm.context.evm.inner.env.cfg, cfg);
assert_eq!(evm.handler.spec_id(), SpecId::LATEST);
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::<EmptyDBTyped<ProviderError>>::default();
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());
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.handler.spec_id(), SpecId::LATEST);
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::<EmptyDBTyped<ProviderError>>::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);
assert_eq!(evm.handler.spec_id(), SpecId::ECOTONE);
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::<EmptyDBTyped<ProviderError>>::default();
let noop = NoOpInspector;
let evm = evm_config.evm_with_inspector(db, noop);
assert_eq!(evm.context.external, noop);
assert_eq!(evm.context.evm.inner.env, Box::default());
assert_eq!(
evm.context.evm.inner.journaled_state,
JournaledState::new(SpecId::LATEST, HashSet::default())
);
assert!(evm.context.evm.inner.db.accounts.is_empty());
assert!(evm.context.evm.inner.db.block_hashes.is_empty());
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));
assert!(evm.context.evm.inner.db.logs.is_empty());
assert_eq!(evm.handler.spec_id(), SpecId::LATEST);
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::<EmptyDBTyped<ProviderError>>::default();
let env_with_handler = EnvWithHandlerCfg::default();
let evm =
evm_config.evm_with_env_and_inspector(db, env_with_handler.clone(), NoOpInspector);
assert_eq!(evm.context.evm.env, env_with_handler.env);
assert_eq!(evm.context.external, NoOpInspector);
assert_eq!(evm.handler.spec_id(), SpecId::LATEST);
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::<EmptyDBTyped<ProviderError>>::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);
assert_eq!(evm.context.evm.env.cfg, cfg);
assert_eq!(evm.context.external, NoOpInspector);
assert_eq!(evm.handler.spec_id(), SpecId::LATEST);
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::<EmptyDBTyped<ProviderError>>::default();
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);
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);
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::<EmptyDBTyped<ProviderError>>::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);
assert_eq!(evm.handler.spec_id(), SpecId::ECOTONE);
assert_eq!(evm.context.evm.env, env_with_handler.env);
assert_eq!(evm.context.external, NoOpInspector);
assert_eq!(evm.handler.spec_id(), SpecId::ECOTONE);
assert_eq!(evm.handler.cfg, HandlerCfg { spec_id: SpecId::ECOTONE, is_optimism: true });
}
#[test]
fn receipts_by_block_hash() {
let block = SealedBlockWithSenders::default();
let block1_hash = B256::new([0x01; 32]);
let block2_hash = B256::new([0x02; 32]);
let mut block1 = block.clone();
let mut block2 = block;
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);
let receipt1 = Receipt {
tx_type: TxType::Legacy,
cumulative_gas_used: 46913,
logs: vec![],
success: true,
deposit_nonce: Some(18),
deposit_receipt_version: Some(34),
};
let receipt2 = Receipt {
tx_type: TxType::Legacy,
cumulative_gas_used: 1325345,
logs: vec![],
success: true,
deposit_nonce: Some(18),
deposit_receipt_version: Some(34),
};
let receipts =
Receipts { receipt_vec: vec![vec![Some(receipt1.clone())], vec![Some(receipt2)]] };
let execution_outcome = ExecutionOutcome {
bundle: Default::default(),
receipts,
requests: vec![],
first_block: 10,
};
let chain = Chain::new([block1, block2], execution_outcome.clone(), None);
assert_eq!(chain.receipts_by_block_hash(block1_hash), Some(vec![&receipt1]));
let execution_outcome1 = ExecutionOutcome {
bundle: Default::default(),
receipts: Receipts { receipt_vec: vec![vec![Some(receipt1)]] },
requests: vec![],
first_block: 10,
};
assert_eq!(chain.execution_outcome_at_block(10), Some(execution_outcome1));
assert_eq!(chain.execution_outcome_at_block(11), Some(execution_outcome));
}
#[test]
fn test_initialisation() {
let bundle = BundleState::new(
vec![(Address::new([2; 20]), None, Some(AccountInfo::default()), HashMap::default())],
vec![vec![(Address::new([2; 20]), None, vec![])]],
vec![],
);
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),
})]],
};
let requests = vec![Requests::new(vec![bytes!("dead"), bytes!("beef"), bytes!("beebee")])];
let first_block = 123;
let exec_res = ExecutionOutcome {
bundle: bundle.clone(),
receipts: receipts.clone(),
requests: requests.clone(),
first_block,
};
assert_eq!(
ExecutionOutcome::new(bundle, receipts.clone(), first_block, requests.clone()),
exec_res
);
let mut state_init: BundleStateInit = HashMap::default();
state_init
.insert(Address::new([2; 20]), (None, Some(Account::default()), HashMap::default()));
let mut revert_inner: HashMap<Address, AccountRevertInit> = HashMap::default();
revert_inner.insert(Address::new([2; 20]), (None, vec![]));
let mut revert_init: RevertsInit = HashMap::default();
revert_init.insert(123, revert_inner);
assert_eq!(
ExecutionOutcome::new_init(
state_init,
revert_init,
vec![],
receipts,
first_block,
requests,
),
exec_res
);
}
#[test]
fn test_block_number_to_index() {
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),
})]],
};
let first_block = 123;
let exec_res = ExecutionOutcome {
bundle: Default::default(),
receipts,
requests: vec![],
first_block,
};
assert_eq!(exec_res.block_number_to_index(12), None);
assert_eq!(exec_res.block_number_to_index(133), None);
assert_eq!(exec_res.block_number_to_index(123), Some(0));
}
#[test]
fn test_get_logs() {
let receipts = Receipts {
receipt_vec: vec![vec![Some(Receipt {
tx_type: TxType::Legacy,
cumulative_gas_used: 46913,
logs: vec![Log::<LogData>::default()],
success: true,
deposit_nonce: Some(18),
deposit_receipt_version: Some(34),
})]],
};
let first_block = 123;
let exec_res = ExecutionOutcome {
bundle: Default::default(),
receipts,
requests: vec![],
first_block,
};
let logs: Vec<&Log> = exec_res.logs(123).unwrap().collect();
assert_eq!(logs, vec![&Log::<LogData>::default()]);
}
#[test]
fn test_receipts_by_block() {
let receipts = Receipts {
receipt_vec: vec![vec![Some(Receipt {
tx_type: TxType::Legacy,
cumulative_gas_used: 46913,
logs: vec![Log::<LogData>::default()],
success: true,
deposit_nonce: Some(18),
deposit_receipt_version: Some(34),
})]],
};
let first_block = 123;
let exec_res = ExecutionOutcome {
bundle: Default::default(), receipts, requests: vec![], first_block, };
let receipts_by_block: Vec<_> = exec_res.receipts_by_block(123).iter().collect();
assert_eq!(
receipts_by_block,
vec![&Some(Receipt {
tx_type: TxType::Legacy,
cumulative_gas_used: 46913,
logs: vec![Log::<LogData>::default()],
success: true,
deposit_nonce: Some(18),
deposit_receipt_version: Some(34),
})]
);
}
#[test]
fn test_receipts_len() {
let receipts = Receipts {
receipt_vec: vec![vec![Some(Receipt {
tx_type: TxType::Legacy,
cumulative_gas_used: 46913,
logs: vec![Log::<LogData>::default()],
success: true,
deposit_nonce: Some(18),
deposit_receipt_version: Some(34),
})]],
};
let receipts_empty = Receipts { receipt_vec: vec![] };
let first_block = 123;
let exec_res = ExecutionOutcome {
bundle: Default::default(), receipts, requests: vec![], first_block, };
assert_eq!(exec_res.len(), 1);
assert!(!exec_res.is_empty());
let exec_res_empty_receipts = ExecutionOutcome {
bundle: Default::default(), receipts: receipts_empty, requests: vec![], first_block, };
assert_eq!(exec_res_empty_receipts.len(), 0);
assert!(exec_res_empty_receipts.is_empty());
}
#[test]
fn test_revert_to() {
let receipt = Receipt {
tx_type: TxType::Legacy,
cumulative_gas_used: 46913,
logs: vec![],
success: true,
deposit_nonce: Some(18),
deposit_receipt_version: Some(34),
};
let receipts = Receipts {
receipt_vec: vec![vec![Some(receipt.clone())], vec![Some(receipt.clone())]],
};
let first_block = 123;
let request = bytes!("deadbeef");
let requests =
vec![Requests::new(vec![request.clone()]), Requests::new(vec![request.clone()])];
let mut exec_res =
ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
assert!(exec_res.revert_to(123));
assert_eq!(exec_res.receipts, Receipts { receipt_vec: vec![vec![Some(receipt)]] });
assert_eq!(exec_res.requests, vec![Requests::new(vec![request])]);
assert!(!exec_res.revert_to(133));
assert!(!exec_res.revert_to(10));
}
#[test]
fn test_extend_execution_outcome() {
let receipt = Receipt {
tx_type: TxType::Legacy,
cumulative_gas_used: 46913,
logs: vec![],
success: true,
deposit_nonce: Some(18),
deposit_receipt_version: Some(34),
};
let receipts = Receipts { receipt_vec: vec![vec![Some(receipt.clone())]] };
let request = bytes!("deadbeef");
let requests = vec![Requests::new(vec![request.clone()])];
let first_block = 123;
let mut exec_res =
ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
exec_res.extend(exec_res.clone());
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() {
let receipt = Receipt {
tx_type: TxType::Legacy,
cumulative_gas_used: 46913,
logs: vec![],
success: true,
deposit_nonce: Some(18),
deposit_receipt_version: Some(34),
};
let receipts = Receipts {
receipt_vec: vec![
vec![Some(receipt.clone())],
vec![Some(receipt.clone())],
vec![Some(receipt.clone())],
],
};
let first_block = 123;
let request = bytes!("deadbeef");
let requests = vec![
Requests::new(vec![request.clone()]),
Requests::new(vec![request.clone()]),
Requests::new(vec![request.clone()]),
];
let exec_res =
ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
let result = exec_res.clone().split_at(124);
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,
};
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_eq!(result.0, Some(lower_execution_outcome));
assert_eq!(result.1, higher_execution_outcome);
assert_eq!(exec_res.clone().split_at(123), (None, exec_res));
}
}