1use crate::{ConfigureEvm, Database, OnStateHook, TxEnvFor};
4use alloc::{boxed::Box, sync::Arc, vec::Vec};
5use alloy_consensus::{BlockHeader, Header};
6use alloy_eips::{
7 eip2718::WithEncoded,
8 eip7928::{compute_block_access_list_hash, BlockAccessList},
9};
10pub use alloy_evm::block::{BlockExecutor, BlockExecutorFactory, GasOutput};
11use alloy_evm::{
12 block::{CommitChanges, ExecutableTxParts},
13 Evm, EvmEnv, EvmFactory, RecoveredTx, ToTxEnv,
14};
15use alloy_primitives::{Address, B256};
16pub use reth_execution_errors::{
17 BlockExecutionError, BlockValidationError, InternalBlockExecutionError,
18};
19use reth_execution_types::BlockExecutionResult;
20pub use reth_execution_types::{BlockExecutionOutput, ExecutionOutcome};
21use reth_primitives_traits::{
22 Block, HeaderTy, NodePrimitives, ReceiptTy, Recovered, RecoveredBlock, SealedHeader, TxTy,
23};
24use reth_storage_api::StateProvider;
25pub use reth_storage_errors::provider::ProviderError;
26use reth_trie_common::{updates::TrieUpdates, HashedPostState};
27use revm::{
28 database::{states::bundle_state::BundleRetention, BundleState, State},
29 state::bal::Bal,
30};
31
32pub trait Executor<DB: Database>: Sized {
35 type Primitives: NodePrimitives;
37 type Error;
39
40 fn execute_one(
42 &mut self,
43 block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
44 ) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>;
45
46 fn execute_one_with_state_hook<F>(
49 &mut self,
50 block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
51 state_hook: F,
52 ) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
53 where
54 F: OnStateHook + 'static;
55
56 fn execute(
64 mut self,
65 block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
66 ) -> Result<BlockExecutionOutput<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
67 {
68 let result = self.execute_one(block)?;
69 let mut state = self.into_state();
70 Ok(BlockExecutionOutput { state: state.take_bundle(), result })
71 }
72
73 fn execute_batch<'a, I>(
75 mut self,
76 blocks: I,
77 ) -> Result<ExecutionOutcome<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
78 where
79 I: IntoIterator<Item = &'a RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>>,
80 {
81 let blocks_iter = blocks.into_iter();
82 let capacity = blocks_iter.size_hint().0;
83 let mut results = Vec::with_capacity(capacity);
84 let mut first_block = None;
85 for block in blocks_iter {
86 if first_block.is_none() {
87 first_block = Some(block.header().number());
88 }
89 results.push(self.execute_one(block)?);
90 }
91
92 Ok(ExecutionOutcome::from_blocks(
93 first_block.unwrap_or_default(),
94 self.into_state().take_bundle(),
95 results,
96 ))
97 }
98
99 fn execute_with_state_closure<F>(
102 mut self,
103 block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
104 mut f: F,
105 ) -> Result<BlockExecutionOutput<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
106 where
107 F: FnMut(&State<DB>),
108 {
109 let result = self.execute_one(block)?;
110 let mut state = self.into_state();
111 f(&state);
112 Ok(BlockExecutionOutput { state: state.take_bundle(), result })
113 }
114
115 fn execute_with_state_closure_always<F>(
118 mut self,
119 block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
120 mut f: F,
121 ) -> Result<BlockExecutionOutput<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
122 where
123 F: FnMut(&State<DB>),
124 {
125 let result = self.execute_one(block);
126 let mut state = self.into_state();
127 f(&state);
128
129 Ok(BlockExecutionOutput { state: state.take_bundle(), result: result? })
130 }
131
132 fn execute_with_state_hook<F>(
135 mut self,
136 block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
137 state_hook: F,
138 ) -> Result<BlockExecutionOutput<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
139 where
140 F: OnStateHook + 'static,
141 {
142 let result = self.execute_one_with_state_hook(block, state_hook)?;
143 let mut state = self.into_state();
144 Ok(BlockExecutionOutput { state: state.take_bundle(), result })
145 }
146
147 fn into_state(self) -> State<DB>;
149
150 fn size_hint(&self) -> usize;
154
155 fn take_bal(&mut self) -> Option<BlockAccessList>;
157}
158
159#[derive(derive_more::Debug)]
197#[non_exhaustive]
198pub struct BlockAssemblerInput<'a, 'b, F: BlockExecutorFactory, H = Header> {
199 pub evm_env:
203 EvmEnv<<F::EvmFactory as EvmFactory>::Spec, <F::EvmFactory as EvmFactory>::BlockEnv>,
204 pub execution_ctx: F::ExecutionCtx<'a>,
206 pub parent: &'a SealedHeader<H>,
208 pub transactions: Vec<F::Transaction>,
210 pub output: &'b BlockExecutionResult<F::Receipt>,
212 pub bundle_state: &'a BundleState,
214 #[debug(skip)]
216 pub state_provider: &'b dyn StateProvider,
217 pub state_root: B256,
219 pub block_access_list_hash: Option<B256>,
221}
222
223impl<'a, 'b, F: BlockExecutorFactory, H> BlockAssemblerInput<'a, 'b, F, H> {
224 #[expect(clippy::too_many_arguments)]
226 pub fn new(
227 evm_env: EvmEnv<
228 <F::EvmFactory as EvmFactory>::Spec,
229 <F::EvmFactory as EvmFactory>::BlockEnv,
230 >,
231 execution_ctx: F::ExecutionCtx<'a>,
232 parent: &'a SealedHeader<H>,
233 transactions: Vec<F::Transaction>,
234 output: &'b BlockExecutionResult<F::Receipt>,
235 bundle_state: &'a BundleState,
236 state_provider: &'b dyn StateProvider,
237 state_root: B256,
238 block_access_list_hash: Option<B256>,
239 ) -> Self {
240 Self {
241 evm_env,
242 execution_ctx,
243 parent,
244 transactions,
245 output,
246 bundle_state,
247 state_provider,
248 state_root,
249 block_access_list_hash,
250 }
251 }
252}
253
254#[auto_impl::auto_impl(&, Arc)]
297pub trait BlockAssembler<F: BlockExecutorFactory> {
298 type Block: Block;
300
301 fn assemble_block(
303 &self,
304 input: BlockAssemblerInput<'_, '_, F, <Self::Block as Block>::Header>,
305 ) -> Result<Self::Block, BlockExecutionError>;
306}
307
308#[derive(Debug, Clone)]
310pub struct BlockBuilderOutcome<N: NodePrimitives> {
311 pub execution_result: BlockExecutionResult<N::Receipt>,
313 pub hashed_state: HashedPostState,
315 pub trie_updates: TrieUpdates,
317 pub block: RecoveredBlock<N::Block>,
319 pub block_access_list: Option<BlockAccessList>,
321}
322
323pub trait BlockBuilder {
330 type Primitives: NodePrimitives;
332 type Executor: BlockExecutor<
334 Transaction = TxTy<Self::Primitives>,
335 Receipt = ReceiptTy<Self::Primitives>,
336 >;
337
338 fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError>;
340
341 fn execute_transaction_with_commit_condition(
344 &mut self,
345 tx: impl ExecutorTx<Self::Executor>,
346 f: impl FnOnce(&<Self::Executor as BlockExecutor>::Result) -> CommitChanges,
347 ) -> Result<Option<GasOutput>, BlockExecutionError>;
348
349 fn execute_transaction_with_result_closure(
352 &mut self,
353 tx: impl ExecutorTx<Self::Executor>,
354 f: impl FnOnce(&<Self::Executor as BlockExecutor>::Result),
355 ) -> Result<GasOutput, BlockExecutionError> {
356 self.execute_transaction_with_commit_condition(tx, |res| {
357 f(res);
358 CommitChanges::Yes
359 })
360 .map(Option::unwrap_or_default)
361 }
362
363 fn execute_transaction(
366 &mut self,
367 tx: impl ExecutorTx<Self::Executor>,
368 ) -> Result<GasOutput, BlockExecutionError> {
369 self.execute_transaction_with_result_closure(tx, |_| ())
370 }
371
372 fn finish(
378 self,
379 state_provider: impl StateProvider,
380 state_root_precomputed: Option<(B256, TrieUpdates)>,
381 ) -> Result<BlockBuilderOutcome<Self::Primitives>, BlockExecutionError>;
382
383 fn executor_mut(&mut self) -> &mut Self::Executor;
385
386 fn executor(&self) -> &Self::Executor;
388
389 fn evm_mut(&mut self) -> &mut <Self::Executor as BlockExecutor>::Evm {
391 self.executor_mut().evm_mut()
392 }
393
394 fn evm(&self) -> &<Self::Executor as BlockExecutor>::Evm {
396 self.executor().evm()
397 }
398
399 fn into_executor(self) -> Self::Executor;
401}
402
403#[derive(Debug)]
405pub struct BasicBlockBuilder<'a, F, Executor, Builder, N: NodePrimitives>
406where
407 F: BlockExecutorFactory,
408{
409 pub executor: Executor,
411 pub transactions: Vec<Recovered<TxTy<N>>>,
413 pub ctx: F::ExecutionCtx<'a>,
415 pub parent: &'a SealedHeader<HeaderTy<N>>,
417 pub assembler: Builder,
419}
420
421pub trait ExecutorTx<Executor: BlockExecutor> {
423 fn into_parts(self) -> (<Executor::Evm as Evm>::Tx, Recovered<Executor::Transaction>);
425}
426
427impl<Executor: BlockExecutor> ExecutorTx<Executor>
428 for WithEncoded<Recovered<Executor::Transaction>>
429{
430 fn into_parts(self) -> (<Executor::Evm as Evm>::Tx, Recovered<Executor::Transaction>) {
431 (self.to_tx_env(), self.1)
432 }
433}
434
435impl<Executor: BlockExecutor> ExecutorTx<Executor> for Recovered<Executor::Transaction> {
436 fn into_parts(self) -> (<Executor::Evm as Evm>::Tx, Self) {
437 (self.to_tx_env(), self)
438 }
439}
440
441impl<Executor> ExecutorTx<Executor>
442 for WithTxEnv<<Executor::Evm as Evm>::Tx, Recovered<Executor::Transaction>>
443where
444 Executor: BlockExecutor<Transaction: Clone>,
445{
446 fn into_parts(self) -> (<Executor::Evm as Evm>::Tx, Recovered<Executor::Transaction>) {
447 (self.tx_env, Arc::unwrap_or_clone(self.tx))
448 }
449}
450
451impl<'a, F, DB, Executor, Builder, N> BlockBuilder
452 for BasicBlockBuilder<'a, F, Executor, Builder, N>
453where
454 F: BlockExecutorFactory<Transaction = N::SignedTx, Receipt = N::Receipt>,
455 Executor: BlockExecutor<
456 Evm: Evm<
457 Spec = <F::EvmFactory as EvmFactory>::Spec,
458 HaltReason = <F::EvmFactory as EvmFactory>::HaltReason,
459 BlockEnv = <F::EvmFactory as EvmFactory>::BlockEnv,
460 DB = &'a mut State<DB>,
461 >,
462 Transaction = N::SignedTx,
463 Receipt = N::Receipt,
464 >,
465 DB: Database + 'a,
466 Builder: BlockAssembler<F, Block = N::Block>,
467 N: NodePrimitives,
468{
469 type Primitives = N;
470 type Executor = Executor;
471
472 fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
473 self.executor.apply_pre_execution_changes()?;
474 self.executor.evm_mut().db_mut().bump_bal_index();
475
476 Ok(())
477 }
478
479 fn execute_transaction_with_commit_condition(
480 &mut self,
481 tx: impl ExecutorTx<Self::Executor>,
482 f: impl FnOnce(&<Self::Executor as BlockExecutor>::Result) -> CommitChanges,
483 ) -> Result<Option<GasOutput>, BlockExecutionError> {
484 let (tx_env, tx) = tx.into_parts();
485 if let Some(gas_used) =
486 self.executor.execute_transaction_with_commit_condition((tx_env, &tx), f)?
487 {
488 self.transactions.push(tx);
489 self.executor.evm_mut().db_mut().bump_bal_index();
490 Ok(Some(gas_used))
491 } else {
492 Ok(None)
493 }
494 }
495
496 fn finish(
497 self,
498 state: impl StateProvider,
499 state_root_precomputed: Option<(B256, TrieUpdates)>,
500 ) -> Result<BlockBuilderOutcome<N>, BlockExecutionError> {
501 let (evm, result) = self.executor.finish()?;
502 let (db, evm_env) = evm.finish();
503
504 db.merge_transitions(BundleRetention::Reverts);
506
507 let block_access_list = db.take_built_alloy_bal();
508 let block_access_list_hash =
509 block_access_list.as_ref().map(|bal| compute_block_access_list_hash(bal));
510
511 let hashed_state = state.hashed_post_state(&db.bundle_state);
512 let (state_root, trie_updates) = match state_root_precomputed {
513 Some(precomputed) => precomputed,
514 None => state
515 .state_root_with_updates(hashed_state.clone())
516 .map_err(BlockExecutionError::other)?,
517 };
518
519 let (transactions, senders) =
520 self.transactions.into_iter().map(|tx| tx.into_parts()).unzip();
521
522 let block = self.assembler.assemble_block(BlockAssemblerInput {
523 evm_env,
524 execution_ctx: self.ctx,
525 parent: self.parent,
526 transactions,
527 output: &result,
528 bundle_state: &db.bundle_state,
529 state_provider: &state,
530 state_root,
531 block_access_list_hash,
532 })?;
533
534 let block = RecoveredBlock::new_unhashed(block, senders);
535
536 Ok(BlockBuilderOutcome {
537 execution_result: result,
538 hashed_state,
539 trie_updates,
540 block,
541 block_access_list,
542 })
543 }
544
545 fn executor_mut(&mut self) -> &mut Self::Executor {
546 &mut self.executor
547 }
548
549 fn executor(&self) -> &Self::Executor {
550 &self.executor
551 }
552
553 fn into_executor(self) -> Self::Executor {
554 self.executor
555 }
556}
557
558#[expect(missing_debug_implementations)]
561pub struct BasicBlockExecutor<F, DB> {
562 pub(crate) strategy_factory: F,
564 pub(crate) db: State<DB>,
566}
567
568impl<F, DB: Database> BasicBlockExecutor<F, DB> {
569 pub fn new(strategy_factory: F, db: DB) -> Self {
571 let db = State::builder().with_database(db).with_bundle_update().build();
572 Self { strategy_factory, db }
573 }
574}
575
576impl<F, DB> Executor<DB> for BasicBlockExecutor<F, DB>
577where
578 F: ConfigureEvm,
579 DB: Database,
580{
581 type Primitives = F::Primitives;
582 type Error = BlockExecutionError;
583
584 fn execute_one(
585 &mut self,
586 block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
587 ) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
588 {
589 let mut executor = self
590 .strategy_factory
591 .executor_for_block(&mut self.db, block)
592 .map_err(BlockExecutionError::other)?;
593
594 let has_bal = block.header().block_access_list_hash().is_some();
595
596 if has_bal {
597 executor.evm_mut().db_mut().bal_state.bal_builder = Some(Bal::new());
598 } else {
599 executor.evm_mut().db_mut().bal_state.bal_builder = None;
600 }
601
602 executor.apply_pre_execution_changes()?;
603
604 if has_bal {
605 executor.evm_mut().db_mut().bump_bal_index();
606 }
607
608 for tx in block.transactions_recovered() {
609 executor.execute_transaction(tx)?;
610 if has_bal {
611 executor.evm_mut().db_mut().bump_bal_index();
612 }
613 }
614
615 let result = executor.apply_post_execution_changes()?;
616
617 self.db.merge_transitions(BundleRetention::Reverts);
618
619 Ok(result)
620 }
621
622 fn execute_one_with_state_hook<H>(
623 &mut self,
624 block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
625 state_hook: H,
626 ) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
627 where
628 H: OnStateHook + 'static,
629 {
630 let result = self
631 .strategy_factory
632 .executor_for_block(&mut self.db, block)
633 .map_err(BlockExecutionError::other)?
634 .with_state_hook(Some(Box::new(state_hook)))
635 .execute_block(block.transactions_recovered())?;
636
637 self.db.merge_transitions(BundleRetention::Reverts);
638
639 Ok(result)
640 }
641
642 fn into_state(self) -> State<DB> {
643 self.db
644 }
645
646 fn size_hint(&self) -> usize {
647 self.db.bundle_state.size_hint()
648 }
649
650 fn take_bal(&mut self) -> Option<BlockAccessList> {
651 self.db.take_built_alloy_bal()
652 }
653}
654
655pub trait ExecutableTxFor<Evm: ConfigureEvm>:
658 ExecutableTxParts<TxEnvFor<Evm>, TxTy<Evm::Primitives>> + RecoveredTx<TxTy<Evm::Primitives>>
659{
660}
661
662impl<T, Evm: ConfigureEvm> ExecutableTxFor<Evm> for T where
663 T: ExecutableTxParts<TxEnvFor<Evm>, TxTy<Evm::Primitives>> + RecoveredTx<TxTy<Evm::Primitives>>
664{
665}
666
667#[derive(Debug)]
669pub struct WithTxEnv<TxEnv, T> {
670 pub tx_env: TxEnv,
672 pub tx: Arc<T>,
674}
675
676impl<TxEnv: Clone, T> Clone for WithTxEnv<TxEnv, T> {
677 fn clone(&self) -> Self {
678 Self { tx_env: self.tx_env.clone(), tx: self.tx.clone() }
679 }
680}
681
682impl<TxEnv, Tx, T: RecoveredTx<Tx>> RecoveredTx<Tx> for WithTxEnv<TxEnv, T> {
683 fn tx(&self) -> &Tx {
684 self.tx.tx()
685 }
686
687 fn signer(&self) -> &Address {
688 self.tx.signer()
689 }
690}
691
692impl<TxEnv, T: RecoveredTx<Tx>, Tx> ExecutableTxParts<TxEnv, Tx> for WithTxEnv<TxEnv, T> {
693 type Recovered = Arc<T>;
694
695 fn into_parts(self) -> (TxEnv, Self::Recovered) {
696 (self.tx_env, self.tx)
697 }
698}
699
700#[cfg(test)]
701mod tests {
702 use super::*;
703 use crate::Address;
704 use alloy_consensus::constants::KECCAK_EMPTY;
705 use alloy_evm::block::state_changes::balance_increment_state;
706 use alloy_primitives::{address, map::HashMap, U256};
707 use core::marker::PhantomData;
708 use reth_ethereum_primitives::EthPrimitives;
709 use revm::{
710 database::{CacheDB, EmptyDB},
711 state::AccountInfo,
712 };
713
714 #[derive(Clone, Debug, Default)]
715 struct TestExecutorProvider;
716
717 impl TestExecutorProvider {
718 fn executor<DB>(&self, _db: DB) -> TestExecutor<DB>
719 where
720 DB: Database,
721 {
722 TestExecutor(PhantomData)
723 }
724 }
725
726 struct TestExecutor<DB>(PhantomData<DB>);
727
728 impl<DB: Database> Executor<DB> for TestExecutor<DB> {
729 type Primitives = EthPrimitives;
730 type Error = BlockExecutionError;
731
732 fn execute_one(
733 &mut self,
734 _block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
735 ) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
736 {
737 Err(BlockExecutionError::msg("execution unavailable for tests"))
738 }
739
740 fn execute_one_with_state_hook<F>(
741 &mut self,
742 _block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
743 _state_hook: F,
744 ) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
745 where
746 F: OnStateHook + 'static,
747 {
748 Err(BlockExecutionError::msg("execution unavailable for tests"))
749 }
750
751 fn into_state(self) -> State<DB> {
752 unreachable!()
753 }
754
755 fn size_hint(&self) -> usize {
756 0
757 }
758
759 fn take_bal(&mut self) -> Option<BlockAccessList> {
760 None
761 }
762 }
763
764 #[test]
765 fn test_provider() {
766 let provider = TestExecutorProvider;
767 let db = CacheDB::<EmptyDB>::default();
768 let executor = provider.executor(db);
769 let _ = executor.execute(&Default::default());
770 }
771
772 fn setup_state_with_account(
773 addr: Address,
774 balance: u128,
775 nonce: u64,
776 ) -> State<CacheDB<EmptyDB>> {
777 let db = CacheDB::<EmptyDB>::default();
778 let mut state = State::builder().with_database(db).with_bundle_update().build();
779
780 let account_info = AccountInfo {
781 balance: U256::from(balance),
782 nonce,
783 code_hash: KECCAK_EMPTY,
784 code: None,
785 account_id: None,
786 };
787 state.insert_account(addr, account_info);
788 state
789 }
790
791 #[test]
792 fn test_balance_increment_state_zero() {
793 let addr = address!("0x1000000000000000000000000000000000000000");
794 let mut state = setup_state_with_account(addr, 100, 1);
795
796 let mut increments = HashMap::default();
797 increments.insert(addr, 0);
798
799 let result = balance_increment_state(&increments, &mut state).unwrap();
800 assert!(result.is_empty(), "Zero increments should be ignored");
801 }
802
803 #[test]
804 fn test_balance_increment_state_empty_increments_map() {
805 let mut state = State::builder()
806 .with_database(CacheDB::<EmptyDB>::default())
807 .with_bundle_update()
808 .build();
809
810 let increments = HashMap::default();
811 let result = balance_increment_state(&increments, &mut state).unwrap();
812 assert!(result.is_empty(), "Empty increments map should return empty state");
813 }
814
815 #[test]
816 fn test_balance_increment_state_multiple_valid_increments() {
817 let addr1 = address!("0x1000000000000000000000000000000000000000");
818 let addr2 = address!("0x2000000000000000000000000000000000000000");
819
820 let mut state = setup_state_with_account(addr1, 100, 1);
821
822 let account2 = AccountInfo {
823 balance: U256::from(200),
824 nonce: 1,
825 code_hash: KECCAK_EMPTY,
826 code: None,
827 account_id: None,
828 };
829 state.insert_account(addr2, account2);
830
831 let mut increments = HashMap::default();
832 increments.insert(addr1, 50);
833 increments.insert(addr2, 100);
834
835 let result = balance_increment_state(&increments, &mut state).unwrap();
836
837 assert_eq!(result.len(), 2);
838 assert_eq!(result.get(&addr1).unwrap().info.balance, U256::from(100));
839 assert_eq!(result.get(&addr2).unwrap().info.balance, U256::from(200));
840 }
841
842 #[test]
843 fn test_balance_increment_state_mixed_zero_and_nonzero_increments() {
844 let addr1 = address!("0x1000000000000000000000000000000000000000");
845 let addr2 = address!("0x2000000000000000000000000000000000000000");
846
847 let mut state = setup_state_with_account(addr1, 100, 1);
848
849 let account2 = AccountInfo {
850 balance: U256::from(200),
851 nonce: 1,
852 code_hash: KECCAK_EMPTY,
853 code: None,
854 account_id: None,
855 };
856 state.insert_account(addr2, account2);
857
858 let mut increments = HashMap::default();
859 increments.insert(addr1, 0);
860 increments.insert(addr2, 100);
861
862 let result = balance_increment_state(&increments, &mut state).unwrap();
863
864 assert_eq!(result.len(), 1, "Only non-zero increments should be included");
865 assert!(!result.contains_key(&addr1), "Zero increment account should not be included");
866 assert_eq!(result.get(&addr2).unwrap().info.balance, U256::from(200));
867 }
868}