@@ -398,13 +398,22 @@ impl<Client> EvTransactionValidator<Client> {
398398 self . validate_evnode_calls ( tx) ?;
399399
400400 if let Some ( signature) = tx. fee_payer_signature . as_ref ( ) {
401+ // Sponsored transaction: validate sponsor balance
401402 let executor = pooled. transaction ( ) . signer ( ) ;
402403 let sponsor = tx. recover_sponsor ( executor, signature) . map_err ( |_| {
403404 InvalidPoolTransactionError :: other ( EvTxPoolError :: InvalidSponsorSignature )
404405 } ) ?;
405406
406407 let gas_cost = U256 :: from ( tx. max_fee_per_gas ) . saturating_mul ( U256 :: from ( tx. gas_limit ) ) ;
407408 self . validate_sponsor_balance ( state, sponsor, gas_cost) ?;
409+ } else {
410+ // Non-sponsored EvNode transaction: executor pays gas, validate their balance
411+ if sender_balance < * pooled. cost ( ) {
412+ return Err ( InvalidPoolTransactionError :: Overdraft {
413+ cost : * pooled. cost ( ) ,
414+ balance : sender_balance,
415+ } ) ;
416+ }
408417 }
409418
410419 Ok ( ( ) )
@@ -502,6 +511,10 @@ where
502511 . set_tx_fee_cap ( ctx. config ( ) . rpc . rpc_tx_fee_cap )
503512 . with_max_tx_gas_limit ( ctx. config ( ) . txpool . max_tx_gas_limit )
504513 . with_minimum_priority_fee ( ctx. config ( ) . txpool . minimum_priority_fee )
514+ // Disable the standard caller balance check - we handle balance validation
515+ // in EvTransactionValidator::validate_evnode which checks:
516+ // - Sponsor balance for sponsored EvNode transactions
517+ // - Sender balance for non-sponsored EvNode and standard Ethereum transactions
505518 . disable_balance_check ( )
506519 . with_additional_tasks ( ctx. config ( ) . txpool . additional_validation_tasks )
507520 . build_with_tasks :: < EvPooledTransaction , _ , _ > (
@@ -528,3 +541,124 @@ where
528541 Ok ( transaction_pool)
529542 }
530543}
544+
545+ #[ cfg( test) ]
546+ mod tests {
547+ use super :: * ;
548+ use alloy_consensus:: Signed ;
549+ use alloy_eips:: eip2930:: AccessList ;
550+ use alloy_primitives:: { Bytes , Signature , TxKind } ;
551+ use ev_primitives:: { Call , EvNodeSignedTx , EvNodeTransaction } ;
552+ use reth_provider:: test_utils:: MockEthProvider ;
553+
554+ fn sample_signature ( ) -> Signature {
555+ let mut bytes = [ 0u8 ; 65 ] ;
556+ bytes[ 64 ] = 27 ;
557+ Signature :: from_raw_array ( & bytes) . expect ( "valid test signature" )
558+ }
559+
560+ /// Creates a non-sponsored EvNode transaction (fee_payer_signature = None)
561+ fn create_non_sponsored_evnode_tx ( gas_limit : u64 , max_fee_per_gas : u128 ) -> EvNodeSignedTx {
562+ let tx = EvNodeTransaction {
563+ chain_id : 1 ,
564+ nonce : 0 ,
565+ max_priority_fee_per_gas : 1 ,
566+ max_fee_per_gas,
567+ gas_limit,
568+ calls : vec ! [ Call {
569+ to: TxKind :: Call ( Address :: ZERO ) ,
570+ value: U256 :: ZERO ,
571+ input: Bytes :: new( ) ,
572+ } ] ,
573+ access_list : AccessList :: default ( ) ,
574+ fee_payer_signature : None , // Non-sponsored
575+ } ;
576+ Signed :: new_unhashed ( tx, sample_signature ( ) )
577+ }
578+
579+ fn create_pooled_tx ( signed_tx : EvNodeSignedTx , signer : Address ) -> EvPooledTransaction {
580+ let envelope = EvTxEnvelope :: EvNode ( signed_tx) ;
581+ let recovered = alloy_consensus:: transaction:: Recovered :: new_unchecked ( envelope, signer) ;
582+ let encoded_length = 200 ; // Approximate length for test
583+ EvPooledTransaction :: new ( recovered, encoded_length)
584+ }
585+
586+ fn create_test_validator ( ) -> EvTransactionValidator < MockEthProvider > {
587+ use reth_transaction_pool:: {
588+ blobstore:: InMemoryBlobStore , validate:: EthTransactionValidatorBuilder ,
589+ } ;
590+
591+ let provider = MockEthProvider :: default ( ) ;
592+ let blob_store = InMemoryBlobStore :: default ( ) ;
593+ let inner = EthTransactionValidatorBuilder :: new ( provider)
594+ . no_shanghai ( )
595+ . no_cancun ( )
596+ . build ( blob_store) ;
597+ EvTransactionValidator :: new ( inner)
598+ }
599+
600+ /// Tests that non-sponsored EvNode transactions with insufficient sender balance
601+ /// are rejected with an Overdraft error.
602+ ///
603+ /// BUG: Currently this test FAILS because validate_evnode does not check
604+ /// sender balance for non-sponsored EvNode transactions.
605+ #[ test]
606+ fn non_sponsored_evnode_rejects_insufficient_balance ( ) {
607+ let validator = create_test_validator ( ) ;
608+
609+ // Create a non-sponsored EvNode transaction
610+ let gas_limit = 21_000u64 ;
611+ let max_fee_per_gas = 1_000_000_000u128 ; // 1 gwei
612+ let signed_tx = create_non_sponsored_evnode_tx ( gas_limit, max_fee_per_gas) ;
613+
614+ let signer = Address :: random ( ) ;
615+ let pooled = create_pooled_tx ( signed_tx, signer) ;
616+
617+ // Sender has ZERO balance - clearly insufficient
618+ let sender_balance = U256 :: ZERO ;
619+ let mut state: Option < Box < dyn AccountInfoReader > > = None ;
620+
621+ // Call validate_evnode - should return Overdraft error
622+ let result = validator. validate_evnode ( & pooled, sender_balance, & mut state) ;
623+
624+ assert ! (
625+ result. is_err( ) ,
626+ "Non-sponsored EvNode with zero balance should be rejected, but got Ok(())"
627+ ) ;
628+
629+ if let Err ( err) = result {
630+ assert ! (
631+ matches!( err, InvalidPoolTransactionError :: Overdraft { .. } ) ,
632+ "Expected Overdraft error, got: {:?}" ,
633+ err
634+ ) ;
635+ }
636+ }
637+
638+ /// Tests that non-sponsored EvNode transactions with sufficient balance are accepted.
639+ #[ test]
640+ fn non_sponsored_evnode_accepts_sufficient_balance ( ) {
641+ let validator = create_test_validator ( ) ;
642+
643+ let gas_limit = 21_000u64 ;
644+ let max_fee_per_gas = 1_000_000_000u128 ;
645+ let signed_tx = create_non_sponsored_evnode_tx ( gas_limit, max_fee_per_gas) ;
646+
647+ let signer = Address :: random ( ) ;
648+ let pooled = create_pooled_tx ( signed_tx, signer) ;
649+
650+ let tx_cost = * pooled. cost ( ) ;
651+
652+ // Sender has MORE than enough balance
653+ let sender_balance = tx_cost + U256 :: from ( 1 ) ;
654+ let mut state: Option < Box < dyn AccountInfoReader > > = None ;
655+
656+ let result = validator. validate_evnode ( & pooled, sender_balance, & mut state) ;
657+
658+ assert ! (
659+ result. is_ok( ) ,
660+ "Non-sponsored EvNode with sufficient balance should be accepted, got: {:?}" ,
661+ result
662+ ) ;
663+ }
664+ }
0 commit comments