1#![doc(
4 html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
5 html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
6 issue_tracker_base_url = "https://github.com/SeismicSystems/seismic-reth/issues/"
7)]
8#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
9#![cfg_attr(not(feature = "std"), no_std)]
10
11extern crate alloc;
12
13use alloc::{borrow::Cow, sync::Arc};
14use alloy_consensus::{BlockHeader, Header};
15use alloy_eips::eip1559::INITIAL_BASE_FEE;
16use alloy_evm::eth::EthBlockExecutionCtx;
17use alloy_primitives::{Bytes, U256};
18use build::SeismicBlockAssembler;
19use core::fmt::Debug;
20use reth_chainspec::{ChainSpec, EthChainSpec};
21use reth_ethereum_forks::EthereumHardfork;
22use reth_evm::{ConfigureEvm, EvmEnv, NextBlockEnvAttributes};
23use reth_primitives_traits::{SealedBlock, SealedHeader};
24use reth_seismic_primitives::{SeismicBlock, SeismicPrimitives};
25use revm::{
26 context::{BlockEnv, CfgEnv},
27 context_interface::block::BlobExcessGasAndPrice,
28};
29use seismic_enclave::rpc::SyncEnclaveApiClientBuilder;
30use seismic_revm::SeismicSpecId;
31use std::convert::Infallible;
32
33mod receipts;
34pub use receipts::*;
35mod build;
36
37pub mod config;
38use config::revm_spec;
39
40pub use alloy_seismic_evm::{block::SeismicBlockExecutorFactory, SeismicEvm, SeismicEvmFactory};
41
42#[derive(Debug, Clone)]
44pub struct SeismicEvmConfig<CB>
45where
46 CB: SyncEnclaveApiClientBuilder,
47{
48 pub executor_factory: SeismicBlockExecutorFactory<
50 CB,
51 SeismicRethReceiptBuilder,
52 Arc<ChainSpec>,
53 SeismicEvmFactory<CB>,
54 >,
55 pub block_assembler: SeismicBlockAssembler<ChainSpec>,
57 pub live_rng_key: Option<schnorrkel::Keypair>,
59 #[allow(unused)]
60 enclave_client_builder: CB,
62}
63
64impl<CB> SeismicEvmConfig<CB>
65where
66 CB: SyncEnclaveApiClientBuilder,
67{
68 pub fn seismic(chain_spec: Arc<ChainSpec>, enclave_client: CB) -> Self {
70 let live_rng_key = Self::get_live_rng_key_from_enclave(&enclave_client);
71 SeismicEvmConfig::new_with_evm_factory(
72 chain_spec,
73 SeismicEvmFactory::<CB>::new_with_rng_key(live_rng_key.clone()),
74 enclave_client,
75 live_rng_key,
76 )
77 }
78}
79
80impl<CB> SeismicEvmConfig<CB>
81where
82 CB: SyncEnclaveApiClientBuilder,
83{
84 pub fn new(chain_spec: Arc<ChainSpec>, enclave_client: CB) -> Self {
86 Self::seismic(chain_spec, enclave_client)
87 }
88
89 pub fn new_with_evm_factory(
91 chain_spec: Arc<ChainSpec>,
92 evm_factory: SeismicEvmFactory<CB>,
93 client_builder: CB,
94 live_rng_key: Option<schnorrkel::Keypair>,
95 ) -> Self {
96 Self {
97 block_assembler: SeismicBlockAssembler::new(chain_spec.clone()),
98 executor_factory: SeismicBlockExecutorFactory::new(
99 SeismicRethReceiptBuilder::default(),
100 chain_spec,
101 evm_factory,
102 client_builder.clone(),
103 ),
104 live_rng_key,
105 enclave_client_builder: client_builder,
106 }
107 }
108
109 pub const fn chain_spec(&self) -> &Arc<ChainSpec> {
111 self.executor_factory.spec()
112 }
113
114 pub fn with_extra_data(mut self, extra_data: Bytes) -> Self {
116 self.block_assembler.extra_data = extra_data;
117 self
118 }
119
120 fn get_live_rng_key_from_enclave(enclave_client_builder: &CB) -> Option<schnorrkel::Keypair> {
122 use seismic_enclave::{keys::GetPurposeKeysRequest, rpc::SyncEnclaveApiClient};
123
124 let enclave_client = enclave_client_builder.clone().build();
125 let request = GetPurposeKeysRequest { epoch: 0 };
126
127 match enclave_client.get_purpose_keys(request) {
128 Ok(response) => Some(response.rng_keypair),
129 Err(_) => None,
130 }
131 }
132
133 pub fn live_rng_key(&self) -> Option<&schnorrkel::Keypair> {
135 self.live_rng_key.as_ref()
136 }
137
138 pub fn evm_with_env_and_live_key<DB>(
140 &self,
141 db: DB,
142 evm_env: EvmEnv<SeismicSpecId>,
143 ) -> SeismicEvm<DB, revm::inspector::NoOpInspector>
144 where
145 DB: alloy_evm::Database,
146 {
147 self.executor_factory.evm_factory().create_evm_with_rng_key(
148 db,
149 evm_env,
150 self.live_rng_key.clone(),
151 )
152 }
153}
154
155impl<CB> ConfigureEvm for SeismicEvmConfig<CB>
156where
157 CB: SyncEnclaveApiClientBuilder + 'static,
158{
159 type Primitives = SeismicPrimitives;
160 type Error = Infallible;
161 type NextBlockEnvCtx = NextBlockEnvAttributes;
162 type BlockExecutorFactory = SeismicBlockExecutorFactory<
163 CB,
164 SeismicRethReceiptBuilder,
165 Arc<ChainSpec>,
166 SeismicEvmFactory<CB>,
167 >;
168 type BlockAssembler = SeismicBlockAssembler<ChainSpec>;
169
170 fn block_executor_factory(&self) -> &Self::BlockExecutorFactory {
171 &self.executor_factory
172 }
173
174 fn block_assembler(&self) -> &Self::BlockAssembler {
175 &self.block_assembler
176 }
177
178 fn evm_env(&self, header: &Header) -> EvmEnv<SeismicSpecId> {
179 let spec = SeismicSpecId::MERCURY;
181
182 let cfg_env = CfgEnv::new().with_chain_id(self.chain_spec().chain().id()).with_spec(spec);
184
185 let block_env = BlockEnv {
186 number: header.number(),
187 beneficiary: header.beneficiary(),
188 timestamp: header.timestamp(),
189 difficulty: U256::ZERO,
190 prevrandao: header.mix_hash(), gas_limit: header.gas_limit(),
193 basefee: header.base_fee_per_gas().unwrap_or_default(),
194 blob_excess_gas_and_price: header
196 .excess_blob_gas
197 .map(|excess_blob_gas| BlobExcessGasAndPrice::new(excess_blob_gas, true)),
198 };
199
200 EvmEnv { cfg_env, block_env }
201 }
202
203 fn next_evm_env(
204 &self,
205 parent: &Header,
206 attributes: &NextBlockEnvAttributes,
207 ) -> Result<EvmEnv<SeismicSpecId>, Self::Error> {
208 let spec_id = revm_spec(self.chain_spec(), parent);
209
210 let cfg = CfgEnv::new().with_chain_id(self.chain_spec().chain().id()).with_spec(spec_id);
212
213 let blob_excess_gas_and_price = parent
216 .maybe_next_block_excess_blob_gas(
217 self.chain_spec().blob_params_at_timestamp(attributes.timestamp),
218 )
219 .map(|gas| BlobExcessGasAndPrice::new(gas, spec_id >= SeismicSpecId::MERCURY));
220
221 let mut basefee = parent.next_block_base_fee(
222 self.chain_spec().base_fee_params_at_timestamp(attributes.timestamp),
223 );
224
225 let mut gas_limit = attributes.gas_limit;
226
227 if self.chain_spec().fork(EthereumHardfork::London).transitions_at_block(parent.number + 1)
230 {
231 let elasticity_multiplier = self
232 .chain_spec()
233 .base_fee_params_at_timestamp(attributes.timestamp)
234 .elasticity_multiplier;
235
236 gas_limit *= elasticity_multiplier as u64;
238
239 basefee = Some(INITIAL_BASE_FEE)
241 }
242
243 let block_env = BlockEnv {
244 number: parent.number + 1,
245 beneficiary: attributes.suggested_fee_recipient,
246 timestamp: attributes.timestamp,
247 difficulty: U256::ZERO,
248 prevrandao: Some(attributes.prev_randao),
249 gas_limit,
250 basefee: basefee.unwrap_or_default(),
252 blob_excess_gas_and_price,
254 };
255
256 Ok((cfg, block_env).into())
257 }
258
259 fn context_for_block<'a>(
260 &self,
261 block: &'a SealedBlock<SeismicBlock>,
262 ) -> EthBlockExecutionCtx<'a> {
263 EthBlockExecutionCtx {
264 parent_hash: block.header().parent_hash,
265 parent_beacon_block_root: block.header().parent_beacon_block_root,
266 ommers: &block.body().ommers,
267 withdrawals: block.body().withdrawals.as_ref().map(Cow::Borrowed),
268 }
269 }
270
271 fn context_for_next_block(
272 &self,
273 parent: &SealedHeader,
274 attributes: Self::NextBlockEnvCtx,
275 ) -> EthBlockExecutionCtx<'_> {
276 EthBlockExecutionCtx {
277 parent_hash: parent.hash(),
278 parent_beacon_block_root: attributes.parent_beacon_block_root,
279 ommers: &[],
280 withdrawals: attributes.withdrawals.map(Cow::Owned),
281 }
282 }
283
284 fn evm_with_env<DB: alloy_evm::Database>(
286 &self,
287 db: DB,
288 evm_env: EvmEnv<SeismicSpecId>,
289 ) -> SeismicEvm<DB, revm::inspector::NoOpInspector> {
290 self.evm_with_env_and_live_key(db, evm_env)
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use alloy_consensus::{Header, Receipt};
298 use alloy_eips::eip7685::Requests;
299 use alloy_evm::Evm;
300 use alloy_primitives::{bytes, map::HashMap, Address, LogData, B256};
301 use reth_chainspec::ChainSpec;
302 use reth_evm::execute::ProviderError;
303 use reth_execution_types::{
304 AccountRevertInit, BundleStateInit, Chain, ExecutionOutcome, RevertsInit,
305 };
306 use reth_primitives_traits::{Account, RecoveredBlock};
307 use reth_seismic_chainspec::SEISMIC_MAINNET;
308 use reth_seismic_primitives::{SeismicBlock, SeismicPrimitives, SeismicReceipt};
309 use revm::{
310 database::{BundleState, CacheDB},
311 database_interface::EmptyDBTyped,
312 handler::PrecompileProvider,
313 inspector::NoOpInspector,
314 precompile::u64_to_address,
315 primitives::Log,
316 state::AccountInfo,
317 };
318 use seismic_alloy_genesis::Genesis;
319 use seismic_enclave::MockEnclaveClientBuilder;
320 use std::sync::Arc;
321
322 fn test_evm_config() -> SeismicEvmConfig<MockEnclaveClientBuilder> {
323 SeismicEvmConfig::seismic(SEISMIC_MAINNET.clone(), MockEnclaveClientBuilder::new())
324 }
325
326 #[test]
327 fn test_fill_cfg_and_block_env() {
328 let header = Header::default();
330
331 let chain_spec = ChainSpec::builder()
334 .chain(0.into())
335 .genesis(Genesis::default())
336 .london_activated()
337 .paris_activated()
338 .shanghai_activated()
339 .build();
340
341 let EvmEnv { cfg_env, .. } = SeismicEvmConfig::seismic(
344 Arc::new(chain_spec.clone()),
345 MockEnclaveClientBuilder::new(),
346 )
347 .evm_env(&header);
348
349 assert_eq!(cfg_env.chain_id, chain_spec.chain().id());
352 }
353
354 #[test]
355 fn test_seismic_evm_with_env_default_spec() {
356 let evm_config = test_evm_config(); let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
359 let evm_env = EvmEnv::default();
360 let evm: SeismicEvm<_, NoOpInspector> = evm_config.evm_with_env(db, evm_env.clone());
361 let precompiles = evm.precompiles().clone();
362
363 assert_eq!(evm.cfg, evm_env.cfg_env);
365 assert_eq!(evm.cfg.spec, SeismicSpecId::MERCURY);
366
367 let precompile_addresses =
369 [u64_to_address(101), u64_to_address(102), u64_to_address(103), u64_to_address(104)];
370 for &addr in &precompile_addresses {
371 let is_contained = precompiles.contains(&addr);
372 assert!(
373 is_contained,
374 "Expected Precompile at address for RETH evm generation {addr:?}"
375 );
376 }
377 }
378
379 #[test]
380 fn test_evm_with_env_custom_cfg() {
381 let evm_config = test_evm_config();
382
383 let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
384
385 let cfg = CfgEnv::new().with_chain_id(111).with_spec(SeismicSpecId::default());
387
388 let evm_env = EvmEnv { cfg_env: cfg.clone(), ..Default::default() };
389
390 let evm = evm_config.evm_with_env(db, evm_env);
391
392 assert_eq!(evm.cfg, cfg);
394 }
395
396 #[test]
397 fn test_evm_with_env_custom_block_and_tx() {
398 let evm_config = test_evm_config();
399
400 let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
401
402 let block =
404 BlockEnv { basefee: 1000, gas_limit: 10_000_000, number: 42, ..Default::default() };
405
406 let evm_env = EvmEnv { block_env: block, ..Default::default() };
407
408 let evm = evm_config.evm_with_env(db, evm_env.clone());
409
410 assert_eq!(evm.block, evm_env.block_env);
412 }
413
414 #[test]
415 fn test_evm_with_spec_id() {
416 let evm_config = test_evm_config();
417
418 let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
419
420 let evm_env = EvmEnv {
421 cfg_env: CfgEnv::new().with_spec(SeismicSpecId::MERCURY),
422 ..Default::default()
423 };
424
425 let evm = evm_config.evm_with_env(db, evm_env.clone());
426
427 assert_eq!(evm.cfg, evm_env.cfg_env);
428 }
429
430 #[test]
431 fn test_evm_with_env_and_default_inspector() {
432 let evm_config = test_evm_config();
433 let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
434
435 let evm_env = EvmEnv { cfg_env: Default::default(), ..Default::default() };
436
437 let evm = evm_config.evm_with_env_and_inspector(db, evm_env.clone(), NoOpInspector {});
438
439 assert_eq!(*evm.block(), evm_env.block_env);
441 assert_eq!(evm.cfg, evm_env.cfg_env);
442 }
443
444 #[test]
445 fn test_evm_with_env_inspector_and_custom_cfg() {
446 let evm_config = test_evm_config();
447 let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
448
449 let cfg = CfgEnv::new().with_chain_id(111).with_spec(SeismicSpecId::MERCURY);
450 let block = BlockEnv::default();
451 let evm_env = EvmEnv { block_env: block, cfg_env: cfg.clone() };
452
453 let evm = evm_config.evm_with_env_and_inspector(db, evm_env.clone(), NoOpInspector {});
454
455 assert_eq!(evm.cfg, cfg);
457 assert_eq!(evm.block, evm_env.block_env);
458 }
459
460 #[test]
461 fn test_evm_with_env_inspector_and_custom_block_tx() {
462 let evm_config = test_evm_config();
463 let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
464
465 let block =
467 BlockEnv { basefee: 1000, gas_limit: 10_000_000, number: 42, ..Default::default() };
468 let evm_env = EvmEnv { block_env: block, ..Default::default() };
469
470 let evm = evm_config.evm_with_env_and_inspector(db, evm_env.clone(), NoOpInspector {});
471
472 assert_eq!(evm.block, evm_env.block_env);
474 }
475
476 #[test]
477 fn test_evm_with_env_inspector_and_spec_id() {
478 let evm_config = test_evm_config();
479 let db = CacheDB::<EmptyDBTyped<ProviderError>>::default();
480
481 let evm_env = EvmEnv {
482 cfg_env: CfgEnv::new().with_spec(SeismicSpecId::MERCURY),
483 ..Default::default()
484 };
485
486 let evm = evm_config.evm_with_env_and_inspector(db, evm_env.clone(), NoOpInspector {});
487
488 assert_eq!(evm.cfg, evm_env.cfg_env);
490 assert_eq!(evm.block, evm_env.block_env);
491 }
492
493 #[test]
494 fn receipts_by_block_hash() {
495 let block: RecoveredBlock<SeismicBlock> = Default::default();
497
498 let block1_hash = B256::new([0x01; 32]);
500 let block2_hash = B256::new([0x02; 32]);
501
502 let mut block1 = block.clone();
504 let mut block2 = block;
505
506 block1.set_block_number(10);
508 block1.set_hash(block1_hash);
509
510 block2.set_block_number(11);
511 block2.set_hash(block2_hash);
512
513 let receipt1 = SeismicReceipt::Legacy(Receipt {
515 cumulative_gas_used: 46913,
516 logs: vec![],
517 status: true.into(),
518 });
519
520 let receipt2 = SeismicReceipt::Legacy(Receipt {
522 cumulative_gas_used: 1325345,
523 logs: vec![],
524 status: true.into(),
525 });
526
527 let receipts = vec![vec![receipt1.clone()], vec![receipt2]];
529
530 let execution_outcome = ExecutionOutcome::<SeismicReceipt> {
533 bundle: Default::default(),
534 receipts,
535 requests: vec![],
536 first_block: 10,
537 };
538
539 let chain: Chain<SeismicPrimitives> =
542 Chain::new([block1, block2], execution_outcome.clone(), None);
543
544 assert_eq!(chain.receipts_by_block_hash(block1_hash), Some(vec![&receipt1]));
546
547 let execution_outcome1 = ExecutionOutcome {
549 bundle: Default::default(),
550 receipts: vec![vec![receipt1]],
551 requests: vec![],
552 first_block: 10,
553 };
554
555 assert_eq!(chain.execution_outcome_at_block(10), Some(execution_outcome1));
557
558 assert_eq!(chain.execution_outcome_at_block(11), Some(execution_outcome));
560 }
561
562 #[test]
563 fn test_initialisation() {
564 let bundle = BundleState::new(
566 vec![(Address::new([2; 20]), None, Some(AccountInfo::default()), HashMap::default())],
567 vec![vec![(Address::new([2; 20]), None, vec![])]],
568 vec![],
569 );
570
571 let receipts = vec![vec![Some(SeismicReceipt::Legacy(Receipt {
573 cumulative_gas_used: 46913,
574 logs: vec![],
575 status: true.into(),
576 }))]];
577
578 let requests = vec![Requests::new(vec![bytes!("dead"), bytes!("beef"), bytes!("beebee")])];
580
581 let first_block = 123;
583
584 let exec_res = ExecutionOutcome {
587 bundle: bundle.clone(),
588 receipts: receipts.clone(),
589 requests: requests.clone(),
590 first_block,
591 };
592
593 assert_eq!(
595 ExecutionOutcome::new(bundle, receipts.clone(), first_block, requests.clone()),
596 exec_res
597 );
598
599 let mut state_init: BundleStateInit = HashMap::default();
601 state_init
602 .insert(Address::new([2; 20]), (None, Some(Account::default()), HashMap::default()));
603
604 let mut revert_inner: HashMap<Address, AccountRevertInit> = HashMap::default();
606 revert_inner.insert(Address::new([2; 20]), (None, vec![]));
607
608 let mut revert_init: RevertsInit = HashMap::default();
610 revert_init.insert(123, revert_inner);
611
612 assert_eq!(
615 ExecutionOutcome::new_init(
616 state_init,
617 revert_init,
618 vec![],
619 receipts,
620 first_block,
621 requests,
622 ),
623 exec_res
624 );
625 }
626
627 #[test]
628 fn test_block_number_to_index() {
629 let receipts = vec![vec![Some(SeismicReceipt::Legacy(Receipt {
631 cumulative_gas_used: 46913,
632 logs: vec![],
633 status: true.into(),
634 }))]];
635
636 let first_block = 123;
638
639 let exec_res = ExecutionOutcome {
642 bundle: Default::default(),
643 receipts,
644 requests: vec![],
645 first_block,
646 };
647
648 assert_eq!(exec_res.block_number_to_index(12), None);
650
651 assert_eq!(exec_res.block_number_to_index(133), None);
653
654 assert_eq!(exec_res.block_number_to_index(123), Some(0));
656 }
657
658 #[test]
659 fn test_get_logs() {
660 let receipts = vec![vec![SeismicReceipt::Legacy(Receipt {
662 cumulative_gas_used: 46913,
663 logs: vec![Log::<LogData>::default()],
664 status: true.into(),
665 })]];
666
667 let first_block = 123;
669
670 let exec_res = ExecutionOutcome {
673 bundle: Default::default(),
674 receipts,
675 requests: vec![],
676 first_block,
677 };
678
679 let logs: Vec<&Log> = exec_res.logs(123).unwrap().collect();
681
682 assert_eq!(logs, vec![&Log::<LogData>::default()]);
684 }
685
686 #[test]
687 fn test_receipts_by_block() {
688 let receipts = vec![vec![Some(SeismicReceipt::Legacy(Receipt {
690 cumulative_gas_used: 46913,
691 logs: vec![Log::<LogData>::default()],
692 status: true.into(),
693 }))]];
694
695 let first_block = 123;
697
698 let exec_res = ExecutionOutcome {
701 bundle: Default::default(), receipts, requests: vec![], first_block, };
706
707 let receipts_by_block: Vec<_> = exec_res.receipts_by_block(123).iter().collect();
709
710 assert_eq!(
712 receipts_by_block,
713 vec![&Some(SeismicReceipt::Legacy(Receipt {
714 cumulative_gas_used: 46913,
715 logs: vec![Log::<LogData>::default()],
716 status: true.into(),
717 }))]
718 );
719 }
720
721 #[test]
722 fn test_receipts_len() {
723 let receipts = vec![vec![Some(SeismicReceipt::Legacy(Receipt {
725 cumulative_gas_used: 46913,
726 logs: vec![Log::<LogData>::default()],
727 status: true.into(),
728 }))]];
729
730 let receipts_empty = vec![];
732
733 let first_block = 123;
735
736 let exec_res = ExecutionOutcome {
739 bundle: Default::default(), receipts, requests: vec![], first_block, };
744
745 assert_eq!(exec_res.len(), 1);
747
748 assert!(!exec_res.is_empty());
750
751 let exec_res_empty_receipts: ExecutionOutcome<SeismicReceipt> = ExecutionOutcome {
753 bundle: Default::default(), receipts: receipts_empty, requests: vec![], first_block, };
758
759 assert_eq!(exec_res_empty_receipts.len(), 0);
761
762 assert!(exec_res_empty_receipts.is_empty());
764 }
765
766 #[test]
767 fn test_revert_to() {
768 let receipt = SeismicReceipt::Legacy(Receipt {
770 cumulative_gas_used: 46913,
771 logs: vec![],
772 status: true.into(),
773 });
774
775 let receipts = vec![vec![Some(receipt.clone())], vec![Some(receipt.clone())]];
777
778 let first_block = 123;
780
781 let request = bytes!("deadbeef");
783
784 let requests =
786 vec![Requests::new(vec![request.clone()]), Requests::new(vec![request.clone()])];
787
788 let mut exec_res =
791 ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
792
793 assert!(exec_res.revert_to(123));
795
796 assert_eq!(exec_res.receipts, vec![vec![Some(receipt)]]);
798
799 assert_eq!(exec_res.requests, vec![Requests::new(vec![request])]);
801
802 assert!(!exec_res.revert_to(133));
805
806 assert!(!exec_res.revert_to(10));
809 }
810
811 #[test]
812 fn test_extend_execution_outcome() {
813 let receipt = SeismicReceipt::Legacy(Receipt {
815 cumulative_gas_used: 46913,
816 logs: vec![],
817 status: true.into(),
818 });
819
820 let receipts = vec![vec![Some(receipt.clone())]];
822
823 let request = bytes!("deadbeef");
825
826 let requests = vec![Requests::new(vec![request.clone()])];
828
829 let first_block = 123;
831
832 let mut exec_res =
834 ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
835
836 exec_res.extend(exec_res.clone());
838
839 assert_eq!(
841 exec_res,
842 ExecutionOutcome {
843 bundle: Default::default(),
844 receipts: vec![vec![Some(receipt.clone())], vec![Some(receipt)]],
845 requests: vec![Requests::new(vec![request.clone()]), Requests::new(vec![request])],
846 first_block: 123,
847 }
848 );
849 }
850
851 #[test]
852 fn test_split_at_execution_outcome() {
853 let receipt = SeismicReceipt::Legacy(Receipt {
855 cumulative_gas_used: 46913,
856 logs: vec![],
857 status: true.into(),
858 });
859
860 let receipts = vec![
862 vec![Some(receipt.clone())],
863 vec![Some(receipt.clone())],
864 vec![Some(receipt.clone())],
865 ];
866
867 let first_block = 123;
869
870 let request = bytes!("deadbeef");
872
873 let requests = vec![
875 Requests::new(vec![request.clone()]),
876 Requests::new(vec![request.clone()]),
877 Requests::new(vec![request.clone()]),
878 ];
879
880 let exec_res =
883 ExecutionOutcome { bundle: Default::default(), receipts, requests, first_block };
884
885 let result = exec_res.clone().split_at(124);
887
888 let lower_execution_outcome = ExecutionOutcome {
890 bundle: Default::default(),
891 receipts: vec![vec![Some(receipt.clone())]],
892 requests: vec![Requests::new(vec![request.clone()])],
893 first_block,
894 };
895
896 let higher_execution_outcome = ExecutionOutcome {
898 bundle: Default::default(),
899 receipts: vec![vec![Some(receipt.clone())], vec![Some(receipt)]],
900 requests: vec![Requests::new(vec![request.clone()]), Requests::new(vec![request])],
901 first_block: 124,
902 };
903
904 assert_eq!(result.0, Some(lower_execution_outcome));
906 assert_eq!(result.1, higher_execution_outcome);
907
908 assert_eq!(exec_res.clone().split_at(123), (None, exec_res));
910 }
911}