diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index fb79ec00f244e..b82139bf5629f 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -258,12 +258,10 @@ pub mod pallet { })?; // Pay relayer reward - if !relayer_fee.is_zero() { - T::RewardPayment::register_reward( - &relayer, - T::DefaultRewardKind::get(), - relayer_fee, - ); + let tip = Tips::::take(nonce).unwrap_or_default(); + let total_tip = relayer_fee.saturating_add(tip); + if total_tip > 0 { + T::RewardPayment::register_reward(&relayer, T::DefaultRewardKind::get(), total_tip); } Self::deposit_event(Event::MessageReceived { nonce, message_id }); diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index 0e66dd4d33d5f..65b084ea11c28 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -7,7 +7,7 @@ use codec::Encode; use frame_support::{assert_noop, assert_ok}; use snowbridge_inbound_queue_primitives::{v2::XcmPayload, EventProof, Proof}; use snowbridge_test_utils::{ - mock_rewards::RegisteredRewardsCount, + mock_rewards::{RegisteredRewardAmount, RegisteredRewardsCount}, mock_xcm::{set_charge_fees_override, set_sender_override}, }; use sp_keyring::sr25519::Keyring; @@ -389,3 +389,141 @@ fn test_add_tip_amount_zero() { assert_eq!(Tips::::get(nonce), None); }); } + +#[test] +fn inbound_tip_is_paid_out_to_relayer() { + new_tester().execute_with(|| { + let nonce: u64 = 77; + let tip: u128 = 12_345; + let relayer_fee: u128 = 2_000; + + // Add tip for nonce before message is processed + assert_ok!(InboundQueue::add_tip(nonce, tip)); + assert_eq!(Tips::::get(nonce), Some(tip)); + + // Process inbound message with relayer_fee + let relayer: AccountId = Keyring::Bob.into(); + assert_ok!(InboundQueue::process_message( + relayer, + Message { + nonce, + assets: vec![], + xcm: XcmPayload::Raw(vec![]), + claimer: None, + execution_fee: 1_000_000_000, + relayer_fee, + gateway: mock::GatewayAddress::get(), + origin: H160::random(), + value: 3_000_000_000, + }, + )); + + // Reward should be registered from relayer_fee + tip + assert_eq!( + RegisteredRewardsCount::get(), + 1, + "Reward should be registered from relayer_fee + tip" + ); + + // Check the actual reward amount paid out (should be relayer_fee + tip) + assert_eq!( + RegisteredRewardAmount::get(), + relayer_fee + tip, + "Reward amount should equal relayer_fee + tip" + ); + + // Tip should be consumed from storage + assert_eq!(Tips::::get(nonce), None); + }); +} + +#[test] +fn relayer_fee_paid_out_when_no_tip_exists() { + new_tester().execute_with(|| { + let nonce: u64 = 88; + let relayer_fee: u128 = 5_000; + + // Ensure no tip exists for this nonce + assert_eq!(Tips::::get(nonce), None); + + // Process inbound message with relayer_fee but no tip + let relayer: AccountId = Keyring::Bob.into(); + assert_ok!(InboundQueue::process_message( + relayer, + Message { + nonce, + assets: vec![], + xcm: XcmPayload::Raw(vec![]), + claimer: None, + execution_fee: 1_000_000_000, + relayer_fee, + gateway: mock::GatewayAddress::get(), + origin: H160::random(), + value: 3_000_000_000, + }, + )); + + // Relayer fee should be paid out even without tip + assert_eq!( + RegisteredRewardsCount::get(), + 1, + "Relayer fee should be paid out even when no tip exists" + ); + + // Check the actual reward amount paid out + assert_eq!( + RegisteredRewardAmount::get(), + relayer_fee, + "Reward amount should equal relayer_fee when no tip exists" + ); + + // Confirm no tip storage was affected + assert_eq!(Tips::::get(nonce), None); + }); +} + +#[test] +fn tip_paid_out_when_no_relayer_fee() { + new_tester().execute_with(|| { + let nonce: u64 = 99; + let tip: u128 = 8_500; + + // Add tip for nonce before message is processed + assert_ok!(InboundQueue::add_tip(nonce, tip)); + assert_eq!(Tips::::get(nonce), Some(tip)); + + // Process inbound message with zero relayer_fee but with tip + let relayer: AccountId = Keyring::Bob.into(); + assert_ok!(InboundQueue::process_message( + relayer, + Message { + nonce, + assets: vec![], + xcm: XcmPayload::Raw(vec![]), + claimer: None, + execution_fee: 1_000_000_000, + relayer_fee: 0, + gateway: mock::GatewayAddress::get(), + origin: H160::random(), + value: 3_000_000_000, + }, + )); + + // Tip should be paid out even without relayer fee + assert_eq!( + RegisteredRewardsCount::get(), + 1, + "Tip should be paid out even when relayer_fee is 0" + ); + + // Check the actual reward amount paid out (should be just the tip) + assert_eq!( + RegisteredRewardAmount::get(), + tip, + "Reward amount should equal tip when relayer_fee is 0" + ); + + // Tip should be consumed from storage + assert_eq!(Tips::::get(nonce), None); + }); +} diff --git a/bridges/snowbridge/test-utils/src/mock_rewards.rs b/bridges/snowbridge/test-utils/src/mock_rewards.rs index d0fa81cd60bb5..e90801312fd0f 100644 --- a/bridges/snowbridge/test-utils/src/mock_rewards.rs +++ b/bridges/snowbridge/test-utils/src/mock_rewards.rs @@ -50,6 +50,7 @@ impl From for RewardsAccountParams { parameter_types! { pub static RegisteredRewardsCount: u128 = 0; + pub static RegisteredRewardAmount: u128 = 0; } pub struct MockRewardLedger; @@ -58,8 +59,9 @@ impl RewardLedger for MockRewardLed fn register_reward( _relayer: &sp_runtime::AccountId32, _reward: BridgeReward, - _reward_balance: u128, + reward_balance: u128, ) { RegisteredRewardsCount::set(RegisteredRewardsCount::get().saturating_add(1)); + RegisteredRewardAmount::set(reward_balance); } } diff --git a/prdoc/pr_9746.prdoc b/prdoc/pr_9746.prdoc new file mode 100644 index 0000000000000..5e27888723f9f --- /dev/null +++ b/prdoc/pr_9746.prdoc @@ -0,0 +1,13 @@ +title: Snowbridge Inbound Queue V2 relayer tip payout fix + +doc: +- audience: Runtime Dev + description: | + Fixes a bug where relayer tips were not properly paid out, causing the tips to be lost since it had already been + burnt. + +crates: +- name: snowbridge-pallet-inbound-queue-v2 + bump: patch +- name: snowbridge-test-utils + bump: minor