Skip to content
Merged
10 changes: 4 additions & 6 deletions bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<T>::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 });
Expand Down
140 changes: 139 additions & 1 deletion bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -389,3 +389,141 @@ fn test_add_tip_amount_zero() {
assert_eq!(Tips::<Test>::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::<Test>::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::<Test>::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::<Test>::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::<Test>::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::<Test>::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::<Test>::get(nonce), None);
});
}
4 changes: 3 additions & 1 deletion bridges/snowbridge/test-utils/src/mock_rewards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ impl From<BridgeReward> for RewardsAccountParams<u64> {

parameter_types! {
pub static RegisteredRewardsCount: u128 = 0;
pub static RegisteredRewardAmount: u128 = 0;
}

pub struct MockRewardLedger;
Expand All @@ -58,8 +59,9 @@ impl RewardLedger<sp_runtime::AccountId32, BridgeReward, u128> 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);
}
}
13 changes: 13 additions & 0 deletions prdoc/pr_9746.prdoc
Original file line number Diff line number Diff line change
@@ -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
Loading