Skip to content

Commit d8faa9e

Browse files
committed
cover sponsorship for a zero-balance signer
1 parent e791f1d commit d8faa9e

1 file changed

Lines changed: 146 additions & 0 deletions

File tree

integration-tests/src/evm_permit.rs

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2959,6 +2959,152 @@ mod sponsored_paymaster {
29592959
});
29602960
}
29612961

2962+
#[test]
2963+
fn signed_dispatch_permit_should_sponsor_entire_cost_when_signer_has_zero_balance() {
2964+
TestNet::reset();
2965+
2966+
Hydra::execute_with(|| {
2967+
init_omnipool_with_oracle_for_block_10();
2968+
2969+
let paymaster = paymaster_account();
2970+
assert_ok!(Balances::mint_into(&paymaster, 100 * UNITS));
2971+
2972+
let user_acc = MockAccount::new(alith_truncated_account());
2973+
for asset in [HDX, WETH, DAI] {
2974+
let held = user_acc.balance(asset);
2975+
if held > 0 {
2976+
assert_ok!(Currencies::update_balance(
2977+
RuntimeOrigin::root(),
2978+
user_acc.address(),
2979+
asset,
2980+
-(held as i128),
2981+
));
2982+
}
2983+
assert_eq!(user_acc.balance(asset), 0, "user must start with nothing");
2984+
}
2985+
2986+
let initial_paymaster_hdx = Currencies::free_balance(HDX, &paymaster);
2987+
2988+
let inner_call = RuntimeCall::System(frame_system::Call::<Runtime>::remark_with_event {
2989+
remark: b"sponsored".to_vec(),
2990+
});
2991+
let (from, data, gas_limit, deadline, v, r, s) =
2992+
build_permit_for_call(&inner_call, 1_000_000, U256::from(1_000_000_000_000u128));
2993+
2994+
let result = MultiTransactionPayment::dispatch_permit(
2995+
RuntimeOrigin::signed(paymaster.clone()),
2996+
from,
2997+
DISPATCH_ADDR,
2998+
U256::from(0),
2999+
data,
3000+
gas_limit,
3001+
deadline,
3002+
v,
3003+
r,
3004+
s,
3005+
);
3006+
assert_ok!(result);
3007+
3008+
let events = frame_system::Pallet::<Runtime>::events();
3009+
3010+
let executed_as_user = events.iter().any(|record| {
3011+
matches!(
3012+
&record.event,
3013+
RuntimeEvent::System(frame_system::Event::Remarked { sender, .. })
3014+
if *sender == user_acc.address()
3015+
)
3016+
});
3017+
assert!(
3018+
executed_as_user,
3019+
"inner call MUST execute under the zero-balance signer's own origin"
3020+
);
3021+
3022+
let alith = alith_evm_address();
3023+
let sponsored = events.iter().any(|record| {
3024+
matches!(
3025+
&record.event,
3026+
RuntimeEvent::MultiTransactionPayment(pallet_transaction_multi_payment::Event::FeeSponsored {
3027+
from,
3028+
fee_payer,
3029+
..
3030+
}) if *from == alith && *fee_payer == paymaster
3031+
)
3032+
});
3033+
assert!(sponsored, "FeeSponsored MUST name the paymaster as fee payer");
3034+
3035+
assert_eq!(user_acc.balance(HDX), 0, "signer MUST NOT be charged in HDX");
3036+
assert_eq!(user_acc.balance(WETH), 0, "signer MUST NOT be charged in WETH");
3037+
assert!(
3038+
Currencies::free_balance(HDX, &paymaster) < initial_paymaster_hdx,
3039+
"paymaster MUST bear the whole cost"
3040+
);
3041+
assert_dispatch_permit_not_paused();
3042+
});
3043+
}
3044+
3045+
#[test]
3046+
fn signed_dispatch_permit_should_fail_without_executing_when_paymaster_cannot_cover_gas() {
3047+
TestNet::reset();
3048+
3049+
Hydra::execute_with(|| {
3050+
init_omnipool_with_oracle_for_block_10();
3051+
3052+
let paymaster: AccountId = [9u8; 32].into();
3053+
assert_eq!(
3054+
Currencies::free_balance(HDX, &paymaster),
3055+
0,
3056+
"control requires a paymaster with nothing"
3057+
);
3058+
3059+
let user_acc = MockAccount::new(alith_truncated_account());
3060+
for asset in [HDX, WETH, DAI] {
3061+
let held = user_acc.balance(asset);
3062+
if held > 0 {
3063+
assert_ok!(Currencies::update_balance(
3064+
RuntimeOrigin::root(),
3065+
user_acc.address(),
3066+
asset,
3067+
-(held as i128),
3068+
));
3069+
}
3070+
}
3071+
3072+
let inner_call = RuntimeCall::System(frame_system::Call::<Runtime>::remark_with_event {
3073+
remark: b"sponsored".to_vec(),
3074+
});
3075+
let (from, data, gas_limit, deadline, v, r, s) =
3076+
build_permit_for_call(&inner_call, 1_000_000, U256::from(1_000_000_000_000u128));
3077+
3078+
let err = MultiTransactionPayment::dispatch_permit(
3079+
RuntimeOrigin::signed(paymaster),
3080+
from,
3081+
DISPATCH_ADDR,
3082+
U256::from(0),
3083+
data,
3084+
gas_limit,
3085+
deadline,
3086+
v,
3087+
r,
3088+
s,
3089+
)
3090+
.expect_err("an unfunded paymaster must not be able to sponsor");
3091+
3092+
assert_eq!(
3093+
err.error,
3094+
pallet_transaction_multi_payment::Error::<Runtime>::EvmPermitRunnerError.into(),
3095+
"insufficient fee-payer balance surfaces as a pre-execution runner error"
3096+
);
3097+
3098+
let executed = frame_system::Pallet::<Runtime>::events().iter().any(|record| {
3099+
matches!(
3100+
&record.event,
3101+
RuntimeEvent::System(frame_system::Event::Remarked { .. })
3102+
)
3103+
});
3104+
assert!(!executed, "inner call MUST NOT execute when sponsorship cannot be paid");
3105+
});
3106+
}
3107+
29623108
#[test]
29633109
fn signed_dispatch_permit_should_emit_fee_sponsored_event_on_success() {
29643110
TestNet::reset();

0 commit comments

Comments
 (0)