Skip to content
This repository has been archived by the owner on Feb 29, 2024. It is now read-only.

Update fee-market terminology #192

Merged
merged 3 commits into from
Sep 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions modules/fee-market/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Requirements

1. Users use native tokens of the source chain as the only method of cross-chain payment;
1. Users use native tokens of the source chain as the only method of cross-chain payment.
2. The cross-chain fees paid by users are generated by the fee market system and the final price quoted by the fee market is influenced by all relayers involved in the delivery of messages throughout the market.
3. The cost on the target chain is at the expense of the relayer who claims the handling fee on the source chain with the proof of delivery after delivery is completed successfully. To incentivize relayers, the fee market system should ensure the relayers gain is greater than the cost in long term. If an automatic pricing mechanism is infeasible, relayers should be able to give their offers manually and shoulder the cost of offering. The relayers should be punished if they fail to relay the message as expected.
4. Relayers and Users constitute a secondary supply-and-demand market, where quotes rise when relayers number is low and fall when relayers number is abundant. There are no access restrictions for relayers, and anyone can enter. Relayer should evaluate and quote at their own discretion as an economically rational person. An incomplete list of risks that relayers should take into account is as follows:
Expand All @@ -26,7 +26,7 @@ In any time, the message delivery and confirmation relayer can be anyone, do not

When calculating rewards and penalties, the following parameters are crucial.

- GuardRelayersRewardRatio: `20% by default`
- DutyRelayersRewardRatio: `20% by default`
- MessageRelayersRewardRatio: `80% by default`
- ConfirmRelayersRewardRatio: `20% by default`
- AssignedRelayerSlashRatio: `20% by default`
Expand All @@ -40,12 +40,31 @@ After a user sends a cross-chain transaction, the fee market system calculates t

As long as the order is confirmed before the last block of the nth slot, we consider it to be delivered on time, and the calculation of rewards and penalties varies depending on when the message is confirmed.

Suppose the cross-chain message has n slots, the message is confirmed at the m-th slot, and `Pm` denotes the quote price of m-th slot. At this point, the assigned relayers from 0 to m slots will be penalized, and the penalty will be calculated as `AssignedRelayerSlashRatio * collateral`, while the assigned relayers from m to n slots will receive a reward for ensuring the message is completed on time `(GuardRelayersRewardRatio * (fee - Pm)) / (n - m)`. `Pm` plus the penalties for the other assigned relayers mentioned above will be distributed as new rewards to the message delivery relayer and the message confirm relayer, where the message delivery relayer receive the `MessageRelayersRewardRatio` of this reward, the message confirm relayer gets the `ConfirmRelayersRewardRatio` of this reward. The rest of the fee will be given to treasury.
Suppose the cross-chain message has n slots, the message is confirmed at the m-th slot, and `Pm` denotes the quote price of m-th slot. At this point, the assigned relayers from 0 to m slots will be penalized, and the penalty will be calculated as `AssignedRelayerSlashRatio * collateral`, while the assigned relayers from m to n slots will receive a reward for ensuring the message is completed on time `(DutyRelayersRewardRatio * (fee - Pm)) / (n - m)`. `Pm` plus the penalties for the other assigned relayers mentioned above will be distributed as new rewards to the message delivery relayer and the message confirm relayer, where the message delivery relayer receive the `MessageRelayersRewardRatio` of this reward, the message confirm relayer gets the `ConfirmRelayersRewardRatio` of this reward. The rest of the fee will be given to treasury.

- The cross-chain transaction is confirmed after the last block of the n slot.

We believe that this cross-chain transaction is severely delayed, in which case all assigned relayers will be heavily penalized by deducting the `AssignedRelayerSlashRatio` of the collateral and calculating an additional penalty amount based on the delay time. These penalty amounts, plus the cross-chain fees paid by users, will be distributed as new rewards to the message delivery relayer and the message confirm relayer, where the message delivery relayer receive the `MessageRelayersRewardRatio` of this reward, the message confirm relayer gets the `ConfirmRelayersRewardRatio` of this reward.


The following diagram shows how rewards are distributed:

```sh

assigned offensive relayers collateral
| |---> messaage delivery relayer
| |
|- slot price + slot offensive slash(may have) = message reward --> |
| |
message fee - |---> message confirm relayer
| ---> treasury
| |
|- message surplus -|
|
---> slot duty rewards -> assigned duty relayers

```

### An example

1. Assume that relayers `R1`, `R2`, `R3`, `R4` and `R5` have registered with the fee market and are all running relayer clients capable of delivering messages with quote prices of `P1=10`, `P2=20`, `P3=30`, `P4=4`0 and `P5=50` and are registered with a locked asset of `100`.
Expand All @@ -66,7 +85,7 @@ The slash, reward analysis is divided into two cases.

Reward Summary:

* To assigned relayers (R1, R2, R3): `(GuardRelayersRewardRatio * (30 - 10)) / 3 = 1`
* To assigned relayers (R1, R2, R3): `(DutyRelayersRewardRatio * (30 - 10)) / 3 = 1`
* To treasury: `30 - 10 - 1 * 3 = 17`
* To message delivery relayer: `10 * MessageRelayersRewardRatio = 8`
* To message confirm relayer: `10 * ConfirmRelayersRewardRatio = 2`
Expand All @@ -81,7 +100,7 @@ The slash, reward analysis is divided into two cases.

Reward Summary:

* To assigned relayers (R2, R3) = `(GuardRelayersRewardRatio * (30 - 20)) / 2 = 1`
* To assigned relayers (R2, R3) = `(DutyRelayersRewardRatio * (30 - 20)) / 2 = 1`
* To treasury: `30 - 20 - 1 * 2 = 8`
* To message delivery relayer: `(20 + 20) * MessageRelayersRewardRatio = 32`
* To message confirm relayer: `(20 + 20) * MessageRelayersRewardRatio = 8`
Expand All @@ -98,7 +117,7 @@ The slash, reward analysis is divided into two cases.

Reward Summary:

* To assigned relayers (R3) = `(GuardRelayersRewardRatio * (30 - 30)) / 1 = 0`
* To assigned relayers (R3) = `(DutyRelayersRewardRatio * (30 - 30)) / 1 = 0`
* To treasury: `30 - 30 - 0 = 0`
* To message delivery relayer: `(10 + 20 * 2) * MessageRelayersRewardRatio = 56`
* To message confirm relayer: `(10 + 20 * 2) * MessageRelayersRewardRatio = 14`
Expand Down
2 changes: 1 addition & 1 deletion modules/fee-market/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ pub mod pallet {

/// Reward parameters
#[pallet::constant]
type GuardRelayersRewardRatio: Get<Permill>;
type DutyRelayersRewardRatio: Get<Permill>;
#[pallet::constant]
type MessageRelayersRewardRatio: Get<Permill>;
#[pallet::constant]
Expand Down
86 changes: 42 additions & 44 deletions modules/fee-market/src/s2s/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use frame_support::{
use scale_info::TypeInfo;
use sp_runtime::traits::{AccountIdConversion, CheckedDiv, Saturating, UniqueSaturatedInto, Zero};
use sp_std::{
cmp::{max, min},
collections::{btree_map::BTreeMap, vec_deque::VecDeque},
ops::RangeInclusive,
};
Expand Down Expand Up @@ -160,89 +161,86 @@ where
{
let mut rewards_items = Vec::new();
for entry in messages_relayers {
let nonce_begin = sp_std::cmp::max(entry.messages.begin, *received_range.start());
let nonce_end = sp_std::cmp::min(entry.messages.end, *received_range.end());
let nonce_begin = max(entry.messages.begin, *received_range.start());
let nonce_end = min(entry.messages.end, *received_range.end());

for message_nonce in nonce_begin..nonce_end + 1 {
// The order created when message was accepted, so we can always get the order info.
if let Some(order) = <Orders<T, I>>::get(&(lane_id, message_nonce)) {
let mut reward_item = RewardItem::new();

let (delivery_and_confirm_reward, treasury_reward) = match order.confirmed_info() {
let (message_reward, treasury_reward) = match order.confirmed_info() {
// When the order is confirmed at the first slot, no assigned relayers will be
// not slashed in this case. The total reward to the message deliver relayer and
// message confirm relayer is the confirmed slot price(first slot price), the
// guarding relayers would be rewarded with the 20% remaining order_fee, and all
// the guarding relayers share the guard_rewards equally. Finally, the
// remaining the order_fee goes to the treasury.
// duty relayers would be rewarded with the 20% of the message fee, and all
// the duty relayers share the duty_rewards equally. Finally, the
// surplus of the message fee goes to the treasury.
Some((slot_index, slot_price)) if slot_index == 0 => {
let mut order_remain_fee = order.fee().saturating_sub(slot_price);
let guard_rewards = T::GuardRelayersRewardRatio::get() * order_remain_fee;
let mut message_surplus = order.fee().saturating_sub(slot_price);
let slot_duty_rewards = T::DutyRelayersRewardRatio::get() * message_surplus;

// All assigned relayers successfully guarded in this case, no slash
// happens, just calculate the guarding relayers rewards.
let guard_relayers_list: Vec<_> =
// All assigned relayers successfully are on duty in this case, no slash
// happens, just calculate the duty relayers rewards.
let duty_relayers: Vec<_> =
order.assigned_relayers_slice().iter().map(|r| r.id.clone()).collect();
let average_reward = guard_rewards
.checked_div(&(guard_relayers_list.len()).unique_saturated_into())
let average_reward = slot_duty_rewards
.checked_div(&(duty_relayers.len()).unique_saturated_into())
.unwrap_or_default();
for id in guard_relayers_list {
for id in duty_relayers {
reward_item.to_assigned_relayers.insert(id.clone(), average_reward);
order_remain_fee = order_remain_fee.saturating_sub(average_reward);
message_surplus = message_surplus.saturating_sub(average_reward);
}

(slot_price, Some(order_remain_fee))
(slot_price, Some(message_surplus))
},
// When the order is confirmed not at the first slot but within the deadline,
// some other assigned relayers will be slashed in this case. The total reward
// to the message deliver relayer and message confirm relayer is the confirmed
// slot price(first slot price) + other_assigned_relayers_slash part, the
// guarding relayers would be rewarded with the 20% remaining order_fee, and all
// the guarding relayers share the guard_rewards equally. Finally, the
// remaining the order_fee goes to the treasury.
// slot price(first slot price) + slot_offensive_slash part, the
// duty relayers would be rewarded with the 20% of the message surplus, and all
// the duty relayers share the duty_rewards equally. Finally, the
// surplus of the message fee goes to the treasury.
Some((slot_index, slot_price)) if slot_index >= 1 => {
let mut order_remain_fee = order.fee().saturating_sub(slot_price);
let guard_rewards = T::GuardRelayersRewardRatio::get() * order_remain_fee;
let mut message_surplus = order.fee().saturating_sub(slot_price);
let slot_duty_rewards = T::DutyRelayersRewardRatio::get() * message_surplus;

// Since part of the assigned relayers successfully guarded, calculate the
// guarding relayers slash part first.
let mut slashed_relayers_list: Vec<_> =
// Since part of the assigned relayers are on duty, calculate the duty
// relayers slash part first.
let mut offensive_relayers: Vec<_> =
order.assigned_relayers_slice().iter().map(|r| r.id.clone()).collect();
let guard_relayers_list = slashed_relayers_list.split_off(slot_index);
let duty_relayers = offensive_relayers.split_off(slot_index);

// Calculate the assigned relayers slash part
let mut other_assigned_relayers_slash = BalanceOf::<T, I>::zero();
for r in slashed_relayers_list {
let mut slot_offensive_slash = BalanceOf::<T, I>::zero();
for r in offensive_relayers {
let amount = slash_assigned_relayer::<T, I>(
&order,
&r,
relayer_fund_account,
T::AssignedRelayerSlashRatio::get()
* Pallet::<T, I>::relayer_locked_collateral(&r),
);
other_assigned_relayers_slash += amount;
slot_offensive_slash += amount;
}

// Calculate the guarding relayers rewards
let average_reward = guard_rewards
.checked_div(&(guard_relayers_list.len()).unique_saturated_into())
// Calculate the duty relayers rewards
let average_reward = slot_duty_rewards
.checked_div(&(duty_relayers.len()).unique_saturated_into())
.unwrap_or_default();
for id in guard_relayers_list {
for id in duty_relayers {
reward_item.to_assigned_relayers.insert(id.clone(), average_reward);
order_remain_fee = order_remain_fee.saturating_sub(average_reward);
message_surplus = message_surplus.saturating_sub(average_reward);
}

(
slot_price.saturating_add(other_assigned_relayers_slash),
Some(order_remain_fee),
)
(slot_price.saturating_add(slot_offensive_slash), Some(message_surplus))
},
// When the order is confirmed delayer, all assigned relayers will be slashed in
// this case. So, no confirmed slot price here. All reward will distribute to
// the message deliver relayer and message confirm relayer. No guarding rewards
// the message deliver relayer and message confirm relayer. No duty rewards
// and treasury reward.
_ => {
let mut other_assigned_relayers_slash = BalanceOf::<T, I>::zero();
let mut slot_offensive_slash = BalanceOf::<T, I>::zero();
for r in order.assigned_relayers_slice() {
// 1. For the fixed part
let mut slash_amount = T::AssignedRelayerSlashRatio::get()
Expand Down Expand Up @@ -272,19 +270,19 @@ where
relayer_fund_account,
slash_amount,
);
other_assigned_relayers_slash += amount;
slot_offensive_slash += amount;
}

(order.fee().saturating_add(other_assigned_relayers_slash), None)
(order.fee().saturating_add(slot_offensive_slash), None)
},
};

if let Some(treasury_reward) = treasury_reward {
reward_item.to_treasury = Some(treasury_reward);
}

let deliver_rd = T::MessageRelayersRewardRatio::get() * delivery_and_confirm_reward;
let confirm_rd = T::ConfirmRelayersRewardRatio::get() * delivery_and_confirm_reward;
let deliver_rd = T::MessageRelayersRewardRatio::get() * message_reward;
let confirm_rd = T::ConfirmRelayersRewardRatio::get() * message_reward;
reward_item.to_message_relayer = Some((entry.relayer.clone(), deliver_rd));
reward_item.to_confirm_relayer = Some((confirm_relayer.clone(), confirm_rd));

Expand Down
4 changes: 2 additions & 2 deletions modules/fee-market/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ frame_support::parameter_types! {
pub const CollateralPerOrder: Balance = 100;
pub const Slot: u64 = 50;

pub const GuardRelayersRewardRatio: Permill = Permill::from_percent(20);
pub const DutyRelayersRewardRatio: Permill = Permill::from_percent(20);
pub const MessageRelayersRewardRatio: Permill = Permill::from_percent(80);
pub const ConfirmRelayersRewardRatio: Permill = Permill::from_percent(20);
pub const AssignedRelayerSlashRatio: Permill = Permill::from_percent(20);
Expand All @@ -476,8 +476,8 @@ impl Config for Test {
type CollateralPerOrder = CollateralPerOrder;
type ConfirmRelayersRewardRatio = ConfirmRelayersRewardRatio;
type Currency = Balances;
type DutyRelayersRewardRatio = DutyRelayersRewardRatio;
type Event = Event;
type GuardRelayersRewardRatio = GuardRelayersRewardRatio;
type LockId = FeeMarketLockId;
type MessageRelayersRewardRatio = MessageRelayersRewardRatio;
type MinimumRelayFee = MinimumRelayFee;
Expand Down