reth_evm/
execute.rs

1//! Traits for execution.
2
3use crate::{ConfigureEvm, Database, OnStateHook};
4use alloc::{boxed::Box, vec::Vec};
5use alloy_consensus::{BlockHeader, Header};
6use alloy_eips::eip2718::WithEncoded;
7pub use alloy_evm::block::{BlockExecutor, BlockExecutorFactory};
8use alloy_evm::{block::ExecutableTx, Evm, EvmEnv, EvmFactory};
9use alloy_primitives::B256;
10use core::fmt::Debug;
11pub use reth_execution_errors::{
12    BlockExecutionError, BlockValidationError, InternalBlockExecutionError,
13};
14use reth_execution_types::BlockExecutionResult;
15pub use reth_execution_types::{BlockExecutionOutput, ExecutionOutcome};
16use reth_primitives_traits::{
17    Block, HeaderTy, NodePrimitives, ReceiptTy, Recovered, RecoveredBlock, SealedHeader, TxTy,
18};
19use reth_storage_api::StateProvider;
20pub use reth_storage_errors::provider::ProviderError;
21use reth_trie_common::{updates::TrieUpdates, HashedPostState};
22use revm::{
23    context::result::ExecutionResult,
24    database::{states::bundle_state::BundleRetention, BundleState, State},
25};
26
27/// A type that knows how to execute a block. It is assumed to operate on a
28/// [`crate::Evm`] internally and use [`State`] as database.
29pub trait Executor<DB: Database>: Sized {
30    /// The primitive types used by the executor.
31    type Primitives: NodePrimitives;
32    /// The error type returned by the executor.
33    type Error;
34
35    /// Executes a single block and returns [`BlockExecutionResult`], without the state changes.
36    fn execute_one(
37        &mut self,
38        block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
39    ) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>;
40
41    /// Executes the EVM with the given input and accepts a state hook closure that is invoked with
42    /// the EVM state after execution.
43    fn execute_one_with_state_hook<F>(
44        &mut self,
45        block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
46        state_hook: F,
47    ) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
48    where
49        F: OnStateHook + 'static;
50
51    /// Consumes the type and executes the block.
52    ///
53    /// # Note
54    /// Execution happens without any validation of the output.
55    ///
56    /// # Returns
57    /// The output of the block execution.
58    fn execute(
59        mut self,
60        block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
61    ) -> Result<BlockExecutionOutput<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
62    {
63        let result = self.execute_one(block)?;
64        let mut state = self.into_state();
65        Ok(BlockExecutionOutput { state: state.take_bundle(), result })
66    }
67
68    /// Executes multiple inputs in the batch, and returns an aggregated [`ExecutionOutcome`].
69    fn execute_batch<'a, I>(
70        mut self,
71        blocks: I,
72    ) -> Result<ExecutionOutcome<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
73    where
74        I: IntoIterator<Item = &'a RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>>,
75    {
76        let mut results = Vec::new();
77        let mut first_block = None;
78        for block in blocks {
79            if first_block.is_none() {
80                first_block = Some(block.header().number());
81            }
82            results.push(self.execute_one(block)?);
83        }
84
85        Ok(ExecutionOutcome::from_blocks(
86            first_block.unwrap_or_default(),
87            self.into_state().take_bundle(),
88            results,
89        ))
90    }
91
92    /// Executes the EVM with the given input and accepts a state closure that is invoked with
93    /// the EVM state after execution.
94    fn execute_with_state_closure<F>(
95        mut self,
96        block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
97        mut f: F,
98    ) -> Result<BlockExecutionOutput<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
99    where
100        F: FnMut(&State<DB>),
101    {
102        let result = self.execute_one(block)?;
103        let mut state = self.into_state();
104        f(&state);
105        Ok(BlockExecutionOutput { state: state.take_bundle(), result })
106    }
107
108    /// Executes the EVM with the given input and accepts a state hook closure that is invoked with
109    /// the EVM state after execution.
110    fn execute_with_state_hook<F>(
111        mut self,
112        block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
113        state_hook: F,
114    ) -> Result<BlockExecutionOutput<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
115    where
116        F: OnStateHook + 'static,
117    {
118        let result = self.execute_one_with_state_hook(block, state_hook)?;
119        let mut state = self.into_state();
120        Ok(BlockExecutionOutput { state: state.take_bundle(), result })
121    }
122
123    /// Consumes the executor and returns the [`State`] containing all state changes.
124    fn into_state(self) -> State<DB>;
125
126    /// The size hint of the batch's tracked state size.
127    ///
128    /// This is used to optimize DB commits depending on the size of the state.
129    fn size_hint(&self) -> usize;
130}
131
132/// A type that can create a new executor for block execution.
133pub trait BlockExecutorProvider: Clone + Debug + Send + Sync + Unpin + 'static {
134    /// Receipt type.
135    type Primitives: NodePrimitives;
136
137    /// An executor that can execute a single block given a database.
138    ///
139    /// # Verification
140    ///
141    /// The on [`Executor::execute`] the executor is expected to validate the execution output of
142    /// the input, this includes:
143    /// - Cumulative gas used must match the input's gas used.
144    /// - Receipts must match the input's receipts root.
145    ///
146    /// It is not expected to validate the state trie root, this must be done by the caller using
147    /// the returned state.
148    type Executor<DB: Database>: Executor<
149        DB,
150        Primitives = Self::Primitives,
151        Error = BlockExecutionError,
152    >;
153
154    /// Creates a new executor for single block execution.
155    ///
156    /// This is used to execute a single block and get the changed state.
157    fn executor<DB>(&self, db: DB) -> Self::Executor<DB>
158    where
159        DB: Database;
160}
161
162/// Helper type for the output of executing a block.
163#[derive(Debug, Clone)]
164pub struct ExecuteOutput<R> {
165    /// Receipts obtained after executing a block.
166    pub receipts: Vec<R>,
167    /// Cumulative gas used in the block execution.
168    pub gas_used: u64,
169}
170
171/// Input for block building. Consumed by [`BlockAssembler`].
172#[derive(derive_more::Debug)]
173#[non_exhaustive]
174pub struct BlockAssemblerInput<'a, 'b, F: BlockExecutorFactory, H = Header> {
175    /// Configuration of EVM used when executing the block.
176    ///
177    /// Contains context relevant to EVM such as [`revm::context::BlockEnv`].
178    pub evm_env: EvmEnv<<F::EvmFactory as EvmFactory>::Spec>,
179    /// [`BlockExecutorFactory::ExecutionCtx`] used to execute the block.
180    pub execution_ctx: F::ExecutionCtx<'a>,
181    /// Parent block header.
182    pub parent: &'a SealedHeader<H>,
183    /// Transactions that were executed in this block.
184    pub transactions: Vec<F::Transaction>,
185    /// Output of block execution.
186    pub output: &'b BlockExecutionResult<F::Receipt>,
187    /// [`BundleState`] after the block execution.
188    pub bundle_state: &'a BundleState,
189    /// Provider with access to state.
190    #[debug(skip)]
191    pub state_provider: &'b dyn StateProvider,
192    /// State root for this block.
193    pub state_root: B256,
194}
195
196/// A type that knows how to assemble a block.
197#[auto_impl::auto_impl(&, Arc)]
198pub trait BlockAssembler<F: BlockExecutorFactory> {
199    /// The block type produced by the assembler.
200    type Block: Block;
201
202    /// Builds a block. see [`BlockAssemblerInput`] documentation for more details.
203    fn assemble_block(
204        &self,
205        input: BlockAssemblerInput<'_, '_, F, <Self::Block as Block>::Header>,
206    ) -> Result<Self::Block, BlockExecutionError>;
207}
208
209/// Output of block building.
210#[derive(Debug, Clone)]
211pub struct BlockBuilderOutcome<N: NodePrimitives> {
212    /// Result of block execution.
213    pub execution_result: BlockExecutionResult<N::Receipt>,
214    /// Hashed state after execution.
215    pub hashed_state: HashedPostState,
216    /// Trie updates collected during state root calculation.
217    pub trie_updates: TrieUpdates,
218    /// The built block.
219    pub block: RecoveredBlock<N::Block>,
220}
221
222/// A type that knows how to execute and build a block.
223///
224/// It wraps an inner [`BlockExecutor`] and provides a way to execute transactions and
225/// construct a block.
226///
227/// This is a helper to erase `BasicBlockBuilder` type.
228pub trait BlockBuilder {
229    /// The primitive types used by the inner [`BlockExecutor`].
230    type Primitives: NodePrimitives;
231    /// Inner [`BlockExecutor`].
232    type Executor: BlockExecutor<
233        Transaction = TxTy<Self::Primitives>,
234        Receipt = ReceiptTy<Self::Primitives>,
235    >;
236
237    /// Invokes [`BlockExecutor::apply_pre_execution_changes`].
238    fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError>;
239
240    /// Invokes [`BlockExecutor::execute_transaction_with_result_closure`] and saves the
241    /// transaction in internal state.
242    fn execute_transaction_with_result_closure(
243        &mut self,
244        tx: impl ExecutorTx<Self::Executor>,
245        f: impl FnOnce(&ExecutionResult<<<Self::Executor as BlockExecutor>::Evm as Evm>::HaltReason>),
246    ) -> Result<u64, BlockExecutionError>;
247
248    /// Invokes [`BlockExecutor::execute_transaction`] and saves the transaction in
249    /// internal state.
250    fn execute_transaction(
251        &mut self,
252        tx: Recovered<TxTy<Self::Primitives>>,
253    ) -> Result<u64, BlockExecutionError> {
254        self.execute_transaction_with_result_closure(tx, |_| ())
255    }
256
257    /// Completes the block building process and returns the [`BlockBuilderOutcome`].
258    fn finish(
259        self,
260        state_provider: impl StateProvider,
261    ) -> Result<BlockBuilderOutcome<Self::Primitives>, BlockExecutionError>;
262
263    /// Provides mutable access to the inner [`BlockExecutor`].
264    fn executor_mut(&mut self) -> &mut Self::Executor;
265
266    /// Provides access to the inner [`BlockExecutor`].
267    fn executor(&self) -> &Self::Executor;
268
269    /// Helper to access inner [`BlockExecutor::Evm`] mutably.
270    fn evm_mut(&mut self) -> &mut <Self::Executor as BlockExecutor>::Evm {
271        self.executor_mut().evm_mut()
272    }
273
274    /// Helper to access inner [`BlockExecutor::Evm`].
275    fn evm(&self) -> &<Self::Executor as BlockExecutor>::Evm {
276        self.executor().evm()
277    }
278
279    /// Consumes the type and returns the underlying [`BlockExecutor`].
280    fn into_executor(self) -> Self::Executor;
281}
282
283pub(crate) struct BasicBlockBuilder<'a, F, Executor, Builder, N: NodePrimitives>
284where
285    F: BlockExecutorFactory,
286{
287    pub(crate) executor: Executor,
288    pub(crate) transactions: Vec<Recovered<TxTy<N>>>,
289    pub(crate) ctx: F::ExecutionCtx<'a>,
290    pub(crate) parent: &'a SealedHeader<HeaderTy<N>>,
291    pub(crate) assembler: Builder,
292}
293
294/// Conversions for executable transactions.
295pub trait ExecutorTx<Executor: BlockExecutor> {
296    /// Converts the transaction into [`ExecutableTx`].
297    fn as_executable(&self) -> impl ExecutableTx<Executor>;
298
299    /// Converts the transaction into [`Recovered`].
300    fn into_recovered(self) -> Recovered<Executor::Transaction>;
301}
302
303impl<Executor: BlockExecutor> ExecutorTx<Executor>
304    for WithEncoded<Recovered<Executor::Transaction>>
305{
306    fn as_executable(&self) -> impl ExecutableTx<Executor> {
307        self
308    }
309
310    fn into_recovered(self) -> Recovered<Executor::Transaction> {
311        self.1
312    }
313}
314
315impl<Executor: BlockExecutor> ExecutorTx<Executor> for Recovered<Executor::Transaction> {
316    fn as_executable(&self) -> impl ExecutableTx<Executor> {
317        self
318    }
319
320    fn into_recovered(self) -> Self {
321        self
322    }
323}
324
325impl<'a, F, DB, Executor, Builder, N> BlockBuilder
326    for BasicBlockBuilder<'a, F, Executor, Builder, N>
327where
328    F: BlockExecutorFactory<Transaction = N::SignedTx, Receipt = N::Receipt>,
329    Executor: BlockExecutor<
330        Evm: Evm<
331            Spec = <F::EvmFactory as EvmFactory>::Spec,
332            HaltReason = <F::EvmFactory as EvmFactory>::HaltReason,
333            DB = &'a mut State<DB>,
334        >,
335        Transaction = N::SignedTx,
336        Receipt = N::Receipt,
337    >,
338    DB: Database + 'a,
339    Builder: BlockAssembler<F, Block = N::Block>,
340    N: NodePrimitives,
341{
342    type Primitives = N;
343    type Executor = Executor;
344
345    fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
346        self.executor.apply_pre_execution_changes()
347    }
348
349    fn execute_transaction_with_result_closure(
350        &mut self,
351        tx: impl ExecutorTx<Self::Executor>,
352        f: impl FnOnce(&ExecutionResult<<F::EvmFactory as EvmFactory>::HaltReason>),
353    ) -> Result<u64, BlockExecutionError> {
354        let gas_used =
355            self.executor.execute_transaction_with_result_closure(tx.as_executable(), f)?;
356        self.transactions.push(tx.into_recovered());
357        Ok(gas_used)
358    }
359
360    fn finish(
361        self,
362        state: impl StateProvider,
363    ) -> Result<BlockBuilderOutcome<N>, BlockExecutionError> {
364        let (evm, result) = self.executor.finish()?;
365        let (db, evm_env) = evm.finish();
366
367        // merge all transitions into bundle state
368        db.merge_transitions(BundleRetention::Reverts);
369
370        // calculate the state root
371        let hashed_state = state.hashed_post_state(&db.bundle_state);
372        let (state_root, trie_updates) = state
373            .state_root_with_updates(hashed_state.clone())
374            .map_err(BlockExecutionError::other)?;
375
376        let (transactions, senders) =
377            self.transactions.into_iter().map(|tx| tx.into_parts()).unzip();
378
379        let block = self.assembler.assemble_block(BlockAssemblerInput {
380            evm_env,
381            execution_ctx: self.ctx,
382            parent: self.parent,
383            transactions,
384            output: &result,
385            bundle_state: &db.bundle_state,
386            state_provider: &state,
387            state_root,
388        })?;
389
390        let block = RecoveredBlock::new_unhashed(block, senders);
391
392        Ok(BlockBuilderOutcome { execution_result: result, hashed_state, trie_updates, block })
393    }
394
395    fn executor_mut(&mut self) -> &mut Self::Executor {
396        &mut self.executor
397    }
398
399    fn executor(&self) -> &Self::Executor {
400        &self.executor
401    }
402
403    fn into_executor(self) -> Self::Executor {
404        self.executor
405    }
406}
407
408impl<F> Clone for BasicBlockExecutorProvider<F>
409where
410    F: Clone,
411{
412    fn clone(&self) -> Self {
413        Self { strategy_factory: self.strategy_factory.clone() }
414    }
415}
416
417/// A generic block executor provider that can create executors using a strategy factory.
418#[derive(Debug)]
419pub struct BasicBlockExecutorProvider<F> {
420    strategy_factory: F,
421}
422
423impl<F> BasicBlockExecutorProvider<F> {
424    /// Creates a new `BasicBlockExecutorProvider` with the given strategy factory.
425    pub const fn new(strategy_factory: F) -> Self {
426        Self { strategy_factory }
427    }
428}
429
430impl<F> BlockExecutorProvider for BasicBlockExecutorProvider<F>
431where
432    F: ConfigureEvm + 'static,
433{
434    type Primitives = F::Primitives;
435
436    type Executor<DB: Database> = BasicBlockExecutor<F, DB>;
437
438    fn executor<DB>(&self, db: DB) -> Self::Executor<DB>
439    where
440        DB: Database,
441    {
442        BasicBlockExecutor::new(self.strategy_factory.clone(), db)
443    }
444}
445
446/// A generic block executor that uses a [`BlockExecutor`] to
447/// execute blocks.
448#[expect(missing_debug_implementations)]
449pub struct BasicBlockExecutor<F, DB> {
450    /// Block execution strategy.
451    pub(crate) strategy_factory: F,
452    /// Database.
453    pub(crate) db: State<DB>,
454}
455
456impl<F, DB: Database> BasicBlockExecutor<F, DB> {
457    /// Creates a new `BasicBlockExecutor` with the given strategy.
458    pub fn new(strategy_factory: F, db: DB) -> Self {
459        let db =
460            State::builder().with_database(db).with_bundle_update().without_state_clear().build();
461        Self { strategy_factory, db }
462    }
463}
464
465impl<F, DB> Executor<DB> for BasicBlockExecutor<F, DB>
466where
467    F: ConfigureEvm,
468    DB: Database,
469{
470    type Primitives = F::Primitives;
471    type Error = BlockExecutionError;
472
473    fn execute_one(
474        &mut self,
475        block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
476    ) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
477    {
478        let mut strategy = self.strategy_factory.executor_for_block(&mut self.db, block);
479
480        strategy.apply_pre_execution_changes()?;
481        for tx in block.transactions_recovered() {
482            strategy.execute_transaction(tx)?;
483        }
484        let result = strategy.apply_post_execution_changes()?;
485
486        self.db.merge_transitions(BundleRetention::Reverts);
487
488        Ok(result)
489    }
490
491    fn execute_one_with_state_hook<H>(
492        &mut self,
493        block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
494        state_hook: H,
495    ) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
496    where
497        H: OnStateHook + 'static,
498    {
499        let mut strategy = self
500            .strategy_factory
501            .executor_for_block(&mut self.db, block)
502            .with_state_hook(Some(Box::new(state_hook)));
503
504        strategy.apply_pre_execution_changes()?;
505        for tx in block.transactions_recovered() {
506            strategy.execute_transaction(tx)?;
507        }
508        let result = strategy.apply_post_execution_changes()?;
509
510        self.db.merge_transitions(BundleRetention::Reverts);
511
512        Ok(result)
513    }
514
515    fn into_state(self) -> State<DB> {
516        self.db
517    }
518
519    fn size_hint(&self) -> usize {
520        self.db.bundle_state.size_hint()
521    }
522}
523
524#[cfg(test)]
525mod tests {
526    use super::*;
527    use crate::Address;
528    use alloy_consensus::constants::KECCAK_EMPTY;
529    use alloy_evm::block::state_changes::balance_increment_state;
530    use alloy_primitives::{address, map::HashMap, U256};
531    use core::marker::PhantomData;
532    use reth_ethereum_primitives::EthPrimitives;
533    use revm::{
534        database::{CacheDB, EmptyDB},
535        state::AccountInfo,
536    };
537
538    #[derive(Clone, Debug, Default)]
539    struct TestExecutorProvider;
540
541    impl BlockExecutorProvider for TestExecutorProvider {
542        type Primitives = EthPrimitives;
543        type Executor<DB: Database> = TestExecutor<DB>;
544
545        fn executor<DB>(&self, _db: DB) -> Self::Executor<DB>
546        where
547            DB: Database,
548        {
549            TestExecutor(PhantomData)
550        }
551    }
552
553    struct TestExecutor<DB>(PhantomData<DB>);
554
555    impl<DB: Database> Executor<DB> for TestExecutor<DB> {
556        type Primitives = EthPrimitives;
557        type Error = BlockExecutionError;
558
559        fn execute_one(
560            &mut self,
561            _block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
562        ) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
563        {
564            Err(BlockExecutionError::msg("execution unavailable for tests"))
565        }
566
567        fn execute_one_with_state_hook<F>(
568            &mut self,
569            _block: &RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>,
570            _state_hook: F,
571        ) -> Result<BlockExecutionResult<<Self::Primitives as NodePrimitives>::Receipt>, Self::Error>
572        where
573            F: OnStateHook + 'static,
574        {
575            Err(BlockExecutionError::msg("execution unavailable for tests"))
576        }
577
578        fn into_state(self) -> State<DB> {
579            unreachable!()
580        }
581
582        fn size_hint(&self) -> usize {
583            0
584        }
585    }
586
587    #[test]
588    fn test_provider() {
589        let provider = TestExecutorProvider;
590        let db = CacheDB::<EmptyDB>::default();
591        let executor = provider.executor(db);
592        let _ = executor.execute(&Default::default());
593    }
594
595    fn setup_state_with_account(
596        addr: Address,
597        balance: u128,
598        nonce: u64,
599    ) -> State<CacheDB<EmptyDB>> {
600        let db = CacheDB::<EmptyDB>::default();
601        let mut state = State::builder().with_database(db).with_bundle_update().build();
602
603        let account_info = AccountInfo {
604            balance: U256::from(balance),
605            nonce,
606            code_hash: KECCAK_EMPTY,
607            code: None,
608        };
609        state.insert_account(addr, account_info);
610        state
611    }
612
613    #[test]
614    fn test_balance_increment_state_zero() {
615        let addr = address!("0x1000000000000000000000000000000000000000");
616        let mut state = setup_state_with_account(addr, 100, 1);
617
618        let mut increments = HashMap::default();
619        increments.insert(addr, 0);
620
621        let result = balance_increment_state(&increments, &mut state).unwrap();
622        assert!(result.is_empty(), "Zero increments should be ignored");
623    }
624
625    #[test]
626    fn test_balance_increment_state_empty_increments_map() {
627        let mut state = State::builder()
628            .with_database(CacheDB::<EmptyDB>::default())
629            .with_bundle_update()
630            .build();
631
632        let increments = HashMap::default();
633        let result = balance_increment_state(&increments, &mut state).unwrap();
634        assert!(result.is_empty(), "Empty increments map should return empty state");
635    }
636
637    #[test]
638    fn test_balance_increment_state_multiple_valid_increments() {
639        let addr1 = address!("0x1000000000000000000000000000000000000000");
640        let addr2 = address!("0x2000000000000000000000000000000000000000");
641
642        let mut state = setup_state_with_account(addr1, 100, 1);
643
644        let account2 =
645            AccountInfo { balance: U256::from(200), nonce: 1, code_hash: KECCAK_EMPTY, code: None };
646        state.insert_account(addr2, account2);
647
648        let mut increments = HashMap::default();
649        increments.insert(addr1, 50);
650        increments.insert(addr2, 100);
651
652        let result = balance_increment_state(&increments, &mut state).unwrap();
653
654        assert_eq!(result.len(), 2);
655        assert_eq!(result.get(&addr1).unwrap().info.balance, U256::from(100));
656        assert_eq!(result.get(&addr2).unwrap().info.balance, U256::from(200));
657    }
658
659    #[test]
660    fn test_balance_increment_state_mixed_zero_and_nonzero_increments() {
661        let addr1 = address!("0x1000000000000000000000000000000000000000");
662        let addr2 = address!("0x2000000000000000000000000000000000000000");
663
664        let mut state = setup_state_with_account(addr1, 100, 1);
665
666        let account2 =
667            AccountInfo { balance: U256::from(200), nonce: 1, code_hash: KECCAK_EMPTY, code: None };
668        state.insert_account(addr2, account2);
669
670        let mut increments = HashMap::default();
671        increments.insert(addr1, 0);
672        increments.insert(addr2, 100);
673
674        let result = balance_increment_state(&increments, &mut state).unwrap();
675
676        assert_eq!(result.len(), 1, "Only non-zero increments should be included");
677        assert!(!result.contains_key(&addr1), "Zero increment account should not be included");
678        assert_eq!(result.get(&addr2).unwrap().info.balance, U256::from(200));
679    }
680}