1- use alloy_consensus:: { TxEnvelope , TxReceipt } ;
2- use alloy_eips:: { eip2718:: Encodable2718 , BlockNumberOrTag } ;
1+ use alloy_consensus:: { SignableTransaction , TxEnvelope , TxReceipt } ;
2+ use alloy_eips:: { eip2718:: Encodable2718 , eip2930 :: AccessList , BlockNumberOrTag } ;
33use alloy_network:: eip2718:: Decodable2718 ;
44use alloy_primitives:: { address, Address , Bytes , TxKind , B256 , U256 } ;
55use alloy_rpc_types:: {
@@ -10,6 +10,7 @@ use alloy_rpc_types::{
1010 BlockId ,
1111} ;
1212use alloy_rpc_types_engine:: { ForkchoiceState , PayloadAttributes , PayloadStatusEnum } ;
13+ use alloy_signer:: SignerSync ;
1314use alloy_sol_types:: { sol, SolCall } ;
1415use eyre:: Result ;
1516use futures:: future;
@@ -28,6 +29,8 @@ use crate::common::{
2829 create_test_chain_spec, create_test_chain_spec_with_base_fee_sink,
2930 create_test_chain_spec_with_mint_admin, TEST_CHAIN_ID ,
3031} ;
32+ use ev_node:: rpc:: { EvRpcReceipt , EvRpcTransaction , EvTransactionRequest } ;
33+ use ev_primitives:: { Call , EvNodeTransaction , EvTxEnvelope } ;
3134use ev_precompiles:: mint:: MINT_PRECOMPILE_ADDR ;
3235
3336sol ! {
@@ -374,6 +377,197 @@ async fn test_e2e_base_fee_sink_receives_base_fee() -> Result<()> {
374377 Ok ( ( ) )
375378}
376379
380+ /// Tests that a sponsored EvNode transaction charges gas to the sponsor, not the executor.
381+ ///
382+ /// # Test Flow
383+ /// 1. Creates an executor and sponsor account from genesis-funded wallets
384+ /// 2. Builds a sponsored EvNode transfer transaction (type 0x76)
385+ /// 3. Includes the transaction in a block via the Engine API
386+ /// 4. Verifies `feePayer` appears in the RPC tx and receipt responses
387+ /// 5. Asserts balances: executor pays value, sponsor pays gas
388+ ///
389+ /// # Success Criteria
390+ /// - Receipt and transaction expose the sponsor address
391+ /// - Recipient receives the transfer value
392+ /// - Executor balance decreases by exactly `value`
393+ /// - Sponsor balance decreases by exactly `gas_used * effective_gas_price`
394+ #[ tokio:: test( flavor = "multi_thread" ) ]
395+ async fn test_e2e_sponsored_evnode_transaction ( ) -> Result < ( ) > {
396+ reth_tracing:: init_test_tracing ( ) ;
397+
398+ let chain_spec = create_test_chain_spec ( ) ;
399+ let chain_id = chain_spec. chain ( ) . id ( ) ;
400+
401+ let mut setup = Setup :: < EvolveEngineTypes > :: default ( )
402+ . with_chain_spec ( chain_spec)
403+ . with_network ( NetworkSetup :: single_node ( ) )
404+ . with_dev_mode ( true ) ;
405+
406+ let mut env = Environment :: < EvolveEngineTypes > :: default ( ) ;
407+ setup. apply :: < EvolveNode > ( & mut env) . await ?;
408+
409+ let parent_block = env. node_clients [ 0 ]
410+ . get_block_by_number ( BlockNumberOrTag :: Latest )
411+ . await ?
412+ . expect ( "parent block should exist" ) ;
413+ let mut parent_hash = parent_block. header . hash ;
414+ let mut parent_timestamp = parent_block. header . inner . timestamp ;
415+ let mut parent_number = parent_block. header . inner . number ;
416+ let gas_limit = parent_block. header . inner . gas_limit ;
417+
418+ let mut wallets = Wallet :: new ( 3 ) . with_chain_id ( chain_id) . wallet_gen ( ) ;
419+ let executor = wallets. remove ( 0 ) ;
420+ let sponsor = wallets. remove ( 0 ) ;
421+ let recipient = Address :: random ( ) ;
422+ let executor_address = executor. address ( ) ;
423+ let sponsor_address = sponsor. address ( ) ;
424+
425+ let executor_balance_before =
426+ EthApiClient :: < TransactionRequest , Transaction , Block , Receipt , Header > :: balance (
427+ & env. node_clients [ 0 ] . rpc ,
428+ executor_address,
429+ Some ( BlockId :: latest ( ) ) ,
430+ )
431+ . await ?;
432+ let sponsor_balance_before =
433+ EthApiClient :: < TransactionRequest , Transaction , Block , Receipt , Header > :: balance (
434+ & env. node_clients [ 0 ] . rpc ,
435+ sponsor_address,
436+ Some ( BlockId :: latest ( ) ) ,
437+ )
438+ . await ?;
439+ let recipient_balance_before =
440+ EthApiClient :: < TransactionRequest , Transaction , Block , Receipt , Header > :: balance (
441+ & env. node_clients [ 0 ] . rpc ,
442+ recipient,
443+ Some ( BlockId :: latest ( ) ) ,
444+ )
445+ . await ?;
446+
447+ let executor_nonce = EthApiClient :: < TransactionRequest , Transaction , Block , Receipt , Header > :: transaction_count (
448+ & env. node_clients [ 0 ] . rpc ,
449+ executor_address,
450+ Some ( BlockId :: latest ( ) ) ,
451+ )
452+ . await ?;
453+ let executor_nonce = u64:: try_from ( executor_nonce) . expect ( "nonce fits into u64" ) ;
454+
455+ let transfer_value = U256 :: from ( 1_000_000_000_000_000u64 ) ; // 0.001 ETH
456+ let call = Call {
457+ to : TxKind :: Call ( recipient) ,
458+ value : transfer_value,
459+ input : Bytes :: default ( ) ,
460+ } ;
461+
462+ let ev_tx = EvNodeTransaction {
463+ chain_id,
464+ nonce : executor_nonce,
465+ max_priority_fee_per_gas : 1_000_000_000 ,
466+ max_fee_per_gas : 2_000_000_000 ,
467+ gas_limit : 100_000 ,
468+ calls : vec ! [ call] ,
469+ access_list : AccessList :: default ( ) ,
470+ fee_payer_signature : None ,
471+ } ;
472+
473+ let executor_sig = executor
474+ . sign_hash_sync ( & ev_tx. signature_hash ( ) )
475+ . expect ( "executor signature" ) ;
476+ let mut signed = ev_tx. into_signed ( executor_sig) ;
477+ let sponsor_hash = signed. tx ( ) . sponsor_signing_hash ( executor_address) ;
478+ let sponsor_sig = sponsor
479+ . sign_hash_sync ( & sponsor_hash)
480+ . expect ( "sponsor signature" ) ;
481+ signed. tx_mut ( ) . fee_payer_signature = Some ( sponsor_sig) ;
482+
483+ let envelope = EvTxEnvelope :: EvNode ( signed) ;
484+ let raw_tx: Bytes = envelope. encoded_2718 ( ) . into ( ) ;
485+ let tx_hash = * envelope. tx_hash ( ) ;
486+
487+ build_block_with_transactions (
488+ & mut env,
489+ & mut parent_hash,
490+ & mut parent_number,
491+ & mut parent_timestamp,
492+ Some ( gas_limit) ,
493+ vec ! [ raw_tx] ,
494+ Address :: ZERO ,
495+ )
496+ . await ?;
497+
498+ type EvRpcBlock = Block < EvRpcTransaction , Header > ;
499+ let receipt = EthApiClient :: < EvTransactionRequest , EvRpcTransaction , EvRpcBlock , EvRpcReceipt , Header > :: transaction_receipt (
500+ & env. node_clients [ 0 ] . rpc ,
501+ tx_hash,
502+ )
503+ . await ?
504+ . expect ( "sponsored transaction receipt available" ) ;
505+ let receipt_inner = receipt. inner ( ) ;
506+ assert ! ( receipt_inner. status( ) , "sponsored transaction should succeed" ) ;
507+ assert_eq ! (
508+ receipt. fee_payer( ) ,
509+ Some ( sponsor_address) ,
510+ "receipt should expose sponsor fee payer"
511+ ) ;
512+
513+ let tx = EthApiClient :: < EvTransactionRequest , EvRpcTransaction , EvRpcBlock , EvRpcReceipt , Header > :: transaction_by_hash (
514+ & env. node_clients [ 0 ] . rpc ,
515+ tx_hash,
516+ )
517+ . await ?
518+ . expect ( "sponsored transaction available" ) ;
519+ assert_eq ! (
520+ tx. fee_payer( ) ,
521+ Some ( sponsor_address) ,
522+ "transaction should expose sponsor fee payer"
523+ ) ;
524+
525+ let executor_balance_after =
526+ EthApiClient :: < TransactionRequest , Transaction , Block , Receipt , Header > :: balance (
527+ & env. node_clients [ 0 ] . rpc ,
528+ executor_address,
529+ Some ( BlockId :: latest ( ) ) ,
530+ )
531+ . await ?;
532+ let sponsor_balance_after =
533+ EthApiClient :: < TransactionRequest , Transaction , Block , Receipt , Header > :: balance (
534+ & env. node_clients [ 0 ] . rpc ,
535+ sponsor_address,
536+ Some ( BlockId :: latest ( ) ) ,
537+ )
538+ . await ?;
539+ let recipient_balance_after =
540+ EthApiClient :: < TransactionRequest , Transaction , Block , Receipt , Header > :: balance (
541+ & env. node_clients [ 0 ] . rpc ,
542+ recipient,
543+ Some ( BlockId :: latest ( ) ) ,
544+ )
545+ . await ?;
546+
547+ let executor_spent = executor_balance_before. saturating_sub ( executor_balance_after) ;
548+ let sponsor_spent = sponsor_balance_before. saturating_sub ( sponsor_balance_after) ;
549+ let recipient_gain = recipient_balance_after. saturating_sub ( recipient_balance_before) ;
550+ assert_eq ! (
551+ recipient_gain, transfer_value,
552+ "recipient should receive transfer value"
553+ ) ;
554+ assert_eq ! (
555+ executor_spent, transfer_value,
556+ "executor should only pay value when sponsored"
557+ ) ;
558+
559+ let expected_gas_cost = U256 :: from ( receipt_inner. gas_used )
560+ . saturating_mul ( U256 :: from ( receipt_inner. effective_gas_price ) ) ;
561+ assert_eq ! (
562+ sponsor_spent, expected_gas_cost,
563+ "sponsor should pay gas cost"
564+ ) ;
565+
566+ drop ( setup) ;
567+
568+ Ok ( ( ) )
569+ }
570+
377571/// Tests minting and burning tokens to/from a dynamically generated wallet not in genesis.
378572///
379573/// # Test Flow
0 commit comments