@@ -2875,6 +2875,147 @@ mod sponsored_paymaster {
28752875 )
28762876 }
28772877
2878+ fn build_permit_at_current_nonce (
2879+ inner_call : & RuntimeCall ,
2880+ gas_limit : u64 ,
2881+ ) -> ( H160 , Vec < u8 > , u64 , U256 , u8 , H256 , H256 ) {
2882+ let from = alith_evm_address ( ) ;
2883+ let secret_key = SecretKey :: parse ( & alith_secret_key ( ) ) . unwrap ( ) ;
2884+ let data = inner_call. encode ( ) ;
2885+ let deadline = U256 :: from ( 1_000_000_000_000u128 ) ;
2886+ let nonce = pallet_evm_precompile_call_permit:: NoncesStorage :: get ( from) ;
2887+
2888+ let permit = pallet_evm_precompile_call_permit:: CallPermitPrecompile :: < Runtime > :: generate_permit (
2889+ CALLPERMIT ,
2890+ from,
2891+ DISPATCH_ADDR ,
2892+ U256 :: from ( 0 ) ,
2893+ data. clone ( ) ,
2894+ gas_limit,
2895+ nonce,
2896+ deadline,
2897+ ) ;
2898+ let message = Message :: parse ( & permit) ;
2899+ let ( rs, v) = sign ( & message, & secret_key) ;
2900+ (
2901+ from,
2902+ data,
2903+ gas_limit,
2904+ deadline,
2905+ v. serialize ( ) ,
2906+ H256 :: from ( rs. r . b32 ( ) ) ,
2907+ H256 :: from ( rs. s . b32 ( ) ) ,
2908+ )
2909+ }
2910+
2911+ fn permit_batch_of_evm_calls (
2912+ paymaster : & AccountId ,
2913+ target : H160 ,
2914+ legs : usize ,
2915+ outer_gas_limit : u64 ,
2916+ leg_gas_limit : u64 ,
2917+ ) -> frame_support:: dispatch:: DispatchResultWithPostInfo {
2918+ let leg = RuntimeCall :: Dispatcher ( pallet_dispatcher:: Call :: dispatch_evm_call {
2919+ call : Box :: new ( RuntimeCall :: EVM ( pallet_evm:: Call :: call {
2920+ source : alith_evm_address ( ) ,
2921+ target,
2922+ input : vec ! [ 0x06 , 0xfd , 0xde , 0x03 ] ,
2923+ value : U256 :: zero ( ) ,
2924+ gas_limit : leg_gas_limit,
2925+ max_fee_per_gas : crate :: evm:: gas_price ( ) ,
2926+ max_priority_fee_per_gas : None ,
2927+ nonce : None ,
2928+ access_list : vec ! [ ] ,
2929+ authorization_list : vec ! [ ] ,
2930+ } ) ) ,
2931+ } ) ;
2932+
2933+ let batch = RuntimeCall :: Utility ( pallet_utility:: Call :: batch_all { calls : vec ! [ leg; legs] } ) ;
2934+
2935+ let ( from, data, gas_limit, deadline, v, r, s) = build_permit_at_current_nonce ( & batch, outer_gas_limit) ;
2936+
2937+ MultiTransactionPayment :: dispatch_permit (
2938+ RuntimeOrigin :: signed ( paymaster. clone ( ) ) ,
2939+ from,
2940+ DISPATCH_ADDR ,
2941+ U256 :: from ( 0 ) ,
2942+ data,
2943+ gas_limit,
2944+ deadline,
2945+ v,
2946+ r,
2947+ s,
2948+ )
2949+ }
2950+
2951+ #[ test]
2952+ fn dispatch_permit_should_execute_every_leg_when_batching_evm_calls_under_one_permit ( ) {
2953+ TestNet :: reset ( ) ;
2954+
2955+ Hydra :: execute_with ( || {
2956+ init_omnipool_with_oracle_for_block_10 ( ) ;
2957+
2958+ let contract = crate :: utils:: contracts:: deploy_contract ( "HydraToken" , crate :: contracts:: deployer ( ) ) ;
2959+ let paymaster = paymaster_account ( ) ;
2960+ assert_ok ! ( Balances :: mint_into( & paymaster, 100_000 * UNITS ) ) ;
2961+
2962+ let user_acc = MockAccount :: new ( alith_truncated_account ( ) ) ;
2963+ let initial_user_weth = user_acc. balance ( WETH ) ;
2964+ let initial_paymaster_hdx = Currencies :: free_balance ( HDX , & paymaster) ;
2965+
2966+ assert_ok ! ( permit_batch_of_evm_calls( & paymaster, contract, 14 , 10_000_000 , 100_000 ) ) ;
2967+
2968+ let batch_completed = frame_system:: Pallet :: < Runtime > :: events ( ) . iter ( ) . any ( |record| {
2969+ matches ! (
2970+ & record. event,
2971+ RuntimeEvent :: Utility ( pallet_utility:: Event :: BatchCompleted )
2972+ )
2973+ } ) ;
2974+ assert ! (
2975+ batch_completed,
2976+ "batch_all emits BatchCompleted only when EVERY leg succeeded"
2977+ ) ;
2978+
2979+ assert_eq ! (
2980+ user_acc. balance( WETH ) ,
2981+ initial_user_weth,
2982+ "signer MUST NOT pay for any leg"
2983+ ) ;
2984+ assert ! (
2985+ Currencies :: free_balance( HDX , & paymaster) < initial_paymaster_hdx,
2986+ "paymaster MUST bear the cost of the whole batch"
2987+ ) ;
2988+ } ) ;
2989+ }
2990+
2991+ #[ test]
2992+ fn dispatch_permit_should_fail_when_gas_limit_exceeds_what_the_paymaster_can_cover ( ) {
2993+ TestNet :: reset ( ) ;
2994+
2995+ Hydra :: execute_with ( || {
2996+ init_omnipool_with_oracle_for_block_10 ( ) ;
2997+
2998+ let contract = crate :: utils:: contracts:: deploy_contract ( "HydraToken" , crate :: contracts:: deployer ( ) ) ;
2999+ let paymaster = paymaster_account ( ) ;
3000+
3001+ let affordable = permit_batch_of_evm_calls ( & paymaster, contract, 14 , 10_000_000 , 100_000 ) ;
3002+ assert_ok ! ( affordable) ;
3003+
3004+ let too_expensive = permit_batch_of_evm_calls ( & paymaster, contract, 14 , 40_000_000 , 100_000 )
3005+ . expect_err ( "gas budget above the paymaster's balance must be rejected" ) ;
3006+
3007+ assert_eq ! (
3008+ too_expensive. error,
3009+ pallet_transaction_multi_payment:: Error :: <Runtime >:: EvmPermitRunnerError . into( ) ,
3010+ "the binding constraint is gas_limit x gas_price vs fee-payer balance, not the block gas limit"
3011+ ) ;
3012+ assert ! (
3013+ <Runtime as pallet_evm:: Config >:: BlockGasLimit :: get( ) > U256 :: from( 40_000_000u64 ) ,
3014+ "40M is well under the 60M block ceiling — this failure is affordability, not the block limit"
3015+ ) ;
3016+ } ) ;
3017+ }
3018+
28783019 fn submit_signed_dispatch_permit_omni_sell (
28793020 paymaster : AccountId ,
28803021 sell_amount : Balance ,
0 commit comments