@@ -110,17 +110,18 @@ impl<DB: Database, I> BscEvm<DB, I> {
110110 }
111111 }
112112
113- /// Fund the beneficiary with `value` so a value-transferring BSC system tx
114- /// (e.g. the block-reward deposit) does not fail balance checks during trace
115- /// replay — archive state at block start has not yet received that reward.
116- /// Trace-mode only; callers must gate on `tx.is_system_transaction`.
113+ /// Stand in for `post_execution::distribute_incoming`'s SYSTEM_ADDRESS →
114+ /// validator credit, which trace replay skips. Use `incr_balance`, not
115+ /// `set_balance`, so the archive-state balance survives the deposit's
116+ /// `B + V → B` round-trip. Runs before the handler's tx checkpoint, so a
117+ /// (very unlikely) system-tx revert would leave the top-up in place.
117118 pub ( crate ) fn fund_beneficiary_for_system_tx_replay ( & mut self , value : revm:: primitives:: U256 ) {
118- if !self . trace {
119+ if !self . trace || value . is_zero ( ) {
119120 return ;
120121 }
121122 let beneficiary = self . block . beneficiary ;
122123 if let Ok ( mut account) = self . journal_mut ( ) . load_account_mut ( beneficiary) {
123- account. set_balance ( value) ;
124+ let _ = account. incr_balance ( value) ;
124125 }
125126 }
126127}
@@ -558,4 +559,67 @@ mod tests {
558559 "system transaction should be marked after inspect_one_tx()"
559560 ) ;
560561 }
562+
563+ fn read_beneficiary_balance (
564+ evm : & mut BscEvm < InMemoryDB , NoOpInspector > ,
565+ beneficiary : Address ,
566+ ) -> U256 {
567+ use revm:: context:: { ContextTr , JournalTr } ;
568+ use revm_context_interface:: journaled_state:: account:: JournaledAccountTr ;
569+ * evm. journal_mut ( )
570+ . load_account_mut ( beneficiary)
571+ . expect ( "beneficiary account should load" )
572+ . balance ( )
573+ }
574+
575+ const TRACE_BENEFICIARY_INITIAL_BALANCE : u64 = 1_000_000_000_000u64 ;
576+
577+ #[ test]
578+ fn fund_uses_incr_semantics_not_set ( ) {
579+ let beneficiary = Address :: from ( [ 0x10 ; 20 ] ) ;
580+ let mut evm = make_trace_replay_evm ( beneficiary, VALIDATOR_SYSTEM_CONTRACT ) ;
581+
582+ let initial = U256 :: from ( TRACE_BENEFICIARY_INITIAL_BALANCE ) ;
583+ let top_up = U256 :: from ( 42_000u64 ) ;
584+ evm. fund_beneficiary_for_system_tx_replay ( top_up) ;
585+
586+ assert_eq ! ( read_beneficiary_balance( & mut evm, beneficiary) , initial + top_up) ;
587+ }
588+
589+ #[ test]
590+ fn fund_is_noop_for_zero_value ( ) {
591+ let beneficiary = Address :: from ( [ 0x10 ; 20 ] ) ;
592+ let mut evm = make_trace_replay_evm ( beneficiary, VALIDATOR_SYSTEM_CONTRACT ) ;
593+
594+ let initial = U256 :: from ( TRACE_BENEFICIARY_INITIAL_BALANCE ) ;
595+ evm. fund_beneficiary_for_system_tx_replay ( U256 :: ZERO ) ;
596+
597+ assert_eq ! ( read_beneficiary_balance( & mut evm, beneficiary) , initial) ;
598+ }
599+
600+ #[ test]
601+ fn fund_is_noop_when_trace_disabled ( ) {
602+ let beneficiary = Address :: from ( [ 0x10 ; 20 ] ) ;
603+ let mut cfg_env = CfgEnv :: new_with_spec ( BscHardfork :: Osaka ) . with_chain_id ( 56 ) ;
604+ cfg_env. tx_gas_limit_cap = Some ( 2u64 . pow ( 24 ) ) ;
605+ let block_env = BlockEnv {
606+ beneficiary,
607+ prevrandao : Some ( U256 :: from ( 1 ) . into ( ) ) ,
608+ ..Default :: default ( )
609+ } ;
610+ let mut db = InMemoryDB :: default ( ) ;
611+ db. insert_account_info (
612+ beneficiary,
613+ AccountInfo {
614+ balance : U256 :: from ( TRACE_BENEFICIARY_INITIAL_BALANCE ) ,
615+ ..AccountInfo :: default ( )
616+ } ,
617+ ) ;
618+ let mut evm = BscEvm :: new ( EvmEnv :: new ( cfg_env, block_env) , db, NoOpInspector , false , false ) ;
619+
620+ let initial = U256 :: from ( TRACE_BENEFICIARY_INITIAL_BALANCE ) ;
621+ evm. fund_beneficiary_for_system_tx_replay ( U256 :: from ( 123u64 ) ) ;
622+
623+ assert_eq ! ( read_beneficiary_balance( & mut evm, beneficiary) , initial) ;
624+ }
561625}
0 commit comments