diff --git a/modules/fee-market/src/lib.rs b/modules/fee-market/src/lib.rs index b8ae6c4f7..b1c1409bb 100644 --- a/modules/fee-market/src/lib.rs +++ b/modules/fee-market/src/lib.rs @@ -47,6 +47,7 @@ use sp_runtime::{ }; use sp_std::vec::Vec; // --- darwinia-network --- +use s2s::RewardItem; use types::{Order, Relayer, SlashReport}; pub type AccountId = ::AccountId; @@ -107,6 +108,17 @@ pub mod pallet { UpdateAssignedRelayersNumber(u32), /// Slash report FeeMarketSlash(SlashReport>), + /// Create new order. \[lane_id, message_nonce, order_fee, assigned_relayers, + /// out_of_slots_time\] + OrderCreated( + LaneId, + MessageNonce, + BalanceOf, + Vec, + Option, + ), + /// Reward distribute of the order. \[lane_id, message_nonce, rewards\] + OrderReward(LaneId, MessageNonce, RewardItem>), } #[pallet::error] @@ -117,6 +129,8 @@ pub mod pallet { AlreadyEnrolled, /// This relayer doesn't enroll ever. NotEnrolled, + /// Locked collateral is too low to cover one order. + CollateralTooLow, /// Update locked collateral is not allow since some orders are not confirm. StillHasOrdersNotConfirmed, /// The fee is lower than MinimumRelayFee. @@ -206,6 +220,12 @@ pub mod pallet { T::Currency::free_balance(&who) >= lock_collateral, >::InsufficientBalance ); + + ensure!( + Self::collateral_to_order_capacity(lock_collateral) > 0, + >::CollateralTooLow + ); + if let Some(fee) = relay_fee { ensure!(fee >= T::MinimumRelayFee::get(), >::RelayFeeTooLow); } diff --git a/modules/fee-market/src/s2s/callbacks.rs b/modules/fee-market/src/s2s/callbacks.rs index d2895b543..4ba1a470b 100644 --- a/modules/fee-market/src/s2s/callbacks.rs +++ b/modules/fee-market/src/s2s/callbacks.rs @@ -37,15 +37,25 @@ impl, I: 'static> OnMessageAccepted for FeeMarketMessageAcceptedHan *message, now, T::CollateralPerOrder::get(), - assigned_relayers, + assigned_relayers.clone(), T::Slot::get(), ); // Store the create order >::insert((order.lane, order.message), order.clone()); + + let ids: Vec = assigned_relayers.iter().map(|r| r.id.clone()).collect(); + Pallet::::deposit_event(Event::OrderCreated( + order.lane, + order.message, + order.fee(), + ids, + order.range_end(), + )); } - // one read for assigned relayers - // one write for store order + // Storage: FeeMarket AssignedRelayers (r:1 w:0) + // Storage: FeeMarket Orders (r:0 w:1) + // Storage: System Events (r:0 w:1) ::DbWeight::get().reads_writes(1, 1) } } @@ -66,8 +76,7 @@ impl, I: 'static> OnDeliveryConfirmed for FeeMarketMessageConfirmed } } - // one db read for get order - // one db write for update order confirm time + // Storage: FeeMarket Orders (r:1 w:1) ::DbWeight::get().reads_writes(1, 1) } } diff --git a/modules/fee-market/src/s2s/mod.rs b/modules/fee-market/src/s2s/mod.rs index 1aaa10195..5321950d1 100644 --- a/modules/fee-market/src/s2s/mod.rs +++ b/modules/fee-market/src/s2s/mod.rs @@ -17,7 +17,7 @@ // along with Darwinia. If not, see . pub mod payment; -pub use payment::FeeMarketPayment; +pub use payment::{FeeMarketPayment, RewardItem}; pub mod callbacks; pub use callbacks::{FeeMarketMessageAcceptedHandler, FeeMarketMessageConfirmedHandler}; diff --git a/modules/fee-market/src/s2s/payment.rs b/modules/fee-market/src/s2s/payment.rs index 47e2697b7..c0ad6f62c 100644 --- a/modules/fee-market/src/s2s/payment.rs +++ b/modules/fee-market/src/s2s/payment.rs @@ -30,6 +30,7 @@ use sp_std::{ collections::{btree_map::BTreeMap, vec_deque::VecDeque}, ops::RangeInclusive, }; +use scale_info::TypeInfo; // --- darwinia-network --- use crate::{Config, Orders, Pallet, *}; @@ -77,61 +78,54 @@ where received_range: &RangeInclusive, relayer_fund_account: &T::AccountId, ) { - let RewardsBook { - messages_relayers_rewards, - confirmation_relayer_rewards, - assigned_relayers_rewards, - treasury_total_rewards, - } = slash_and_calculate_rewards::( - lane_id, - messages_relayers, - received_range, - relayer_fund_account, - ); + let RewardsBook { deliver_sum, confirm_sum, assigned_relayers_sum, treasury_sum } = + slash_and_calculate_rewards::( + lane_id, + messages_relayers, + confirmation_relayer.clone(), + received_range, + relayer_fund_account, + ); // Pay confirmation relayer rewards - do_reward::(relayer_fund_account, confirmation_relayer, confirmation_relayer_rewards); + do_reward::(relayer_fund_account, confirmation_relayer, confirm_sum); // Pay messages relayers rewards - for (relayer, reward) in messages_relayers_rewards { + for (relayer, reward) in deliver_sum { do_reward::(relayer_fund_account, &relayer, reward); } // Pay assign relayer reward - for (relayer, reward) in assigned_relayers_rewards { + for (relayer, reward) in assigned_relayers_sum { do_reward::(relayer_fund_account, &relayer, reward); } - // Pay treasury reward + // Pay treasury_sum reward do_reward::( relayer_fund_account, &T::TreasuryPalletId::get().into_account(), - treasury_total_rewards, + treasury_sum, ); } } -/// Slash and calculate rewards for messages_relayers, confirmation relayers, treasury, +/// Slash and calculate rewards for messages_relayers, confirmation relayers, treasury_sum, /// assigned_relayers pub fn slash_and_calculate_rewards( lane_id: LaneId, messages_relayers: VecDeque>, + confirm_relayer: T::AccountId, received_range: &RangeInclusive, relayer_fund_account: &T::AccountId, -) -> RewardsBook> +) -> RewardsBook where T: frame_system::Config + Config, I: 'static, { - let mut confirmation_rewards = BalanceOf::::zero(); - let mut messages_rewards = BTreeMap::>::new(); - let mut assigned_relayers_rewards = BTreeMap::>::new(); - let mut treasury_total_rewards = BalanceOf::::zero(); - + let mut rewards_book = RewardsBook::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()); for message_nonce in nonce_begin..nonce_end + 1 { - // The order created when message was accepted, so we can always get the order info - // below. + // The order created when message was accepted, so we can always get the order info. if let Some(order) = >::get(&(lane_id, message_nonce)) { // The confirm_time of the order is set in the `OnDeliveryConfirmed` callback. And // the callback function was called as source chain received message delivery proof, @@ -140,31 +134,29 @@ where order.confirm_time.unwrap_or_else(|| frame_system::Pallet::::block_number()); let message_fee = order.fee(); + let mut reward_item = RewardItem::new(); let message_reward; let confirm_reward; if let Some((who, base_fee)) = order.required_delivery_relayer_for_time(order_confirm_time) { - // message fee - base fee => treasury - let treasury_reward = message_fee.saturating_sub(base_fee); - treasury_total_rewards = treasury_total_rewards.saturating_add(treasury_reward); + // message fee - base fee => treasury_sum + reward_item.to_treasury = Some(message_fee.saturating_sub(base_fee)); - // 60% * base fee => assigned_relayers_rewards - let assigned_relayers_reward = T::AssignedRelayersRewardRatio::get() * base_fee; - assigned_relayers_rewards - .entry(who) - .and_modify(|r| *r = r.saturating_add(assigned_relayers_reward)) - .or_insert(assigned_relayers_reward); + // AssignedRelayersRewardRatio * base fee => slot relayer + let slot_relayer_reward = T::AssignedRelayersRewardRatio::get() * base_fee; + reward_item.to_slot_relayer = Some((who, slot_relayer_reward)); - let bridger_relayers_reward = base_fee.saturating_sub(assigned_relayers_reward); - - // 80% * (1 - 60%) * base_fee => message relayer + let bridger_relayers_reward = base_fee.saturating_sub(slot_relayer_reward); + // MessageRelayersRewardRatio * (1 - AssignedRelayersRewardRatio) * base_fee => + // message relayer message_reward = T::MessageRelayersRewardRatio::get() * bridger_relayers_reward; - // 20% * (1 - 60%) * base_fee => confirm relayer + // ConfirmRelayersRewardRatio * (1 - AssignedRelayersRewardRatio) * base_fee => + // confirm relayer confirm_reward = T::ConfirmRelayersRewardRatio::get() * bridger_relayers_reward; } else { - // The order delivery is delay + // The order delivery is delay, slash occurs. let mut total_slash = message_fee; // calculate slash amount @@ -190,29 +182,26 @@ where } total_slash += assigned_relayers_slash; - // 80% total slash => confirm relayer + // MessageRelayersRewardRatio total slash => message relayer message_reward = T::MessageRelayersRewardRatio::get() * total_slash; - // 20% total slash => confirm relayer + // ConfirmRelayersRewardRatio total slash => confirm relayer confirm_reward = T::ConfirmRelayersRewardRatio::get() * total_slash; } - // Update confirmation relayer total rewards - confirmation_rewards = confirmation_rewards.saturating_add(confirm_reward); - // Update message relayers total rewards - messages_rewards - .entry(entry.relayer.clone()) - .and_modify(|r| *r = r.saturating_add(message_reward)) - .or_insert(message_reward); + reward_item.to_message_relayer = Some((entry.clone().relayer, message_reward)); + reward_item.to_confirm_relayer = Some((confirm_relayer.clone(), confirm_reward)); + + Pallet::::deposit_event(Event::OrderReward( + lane_id, + message_nonce, + reward_item.clone(), + )); + + rewards_book.add_reward_item(reward_item); } } } - - RewardsBook { - messages_relayers_rewards: messages_rewards, - confirmation_relayer_rewards: confirmation_rewards, - assigned_relayers_rewards, - treasury_total_rewards, - } + rewards_book } /// Do slash for absent assigned relayers @@ -278,10 +267,66 @@ pub(crate) fn do_reward, I: 'static>( } } +/// Record the concrete reward distribution of certain order +#[derive(Clone, Debug, Encode, Decode, Eq, PartialEq, TypeInfo)] +pub struct RewardItem { + pub to_slot_relayer: Option<(AccountId, Balance)>, + pub to_treasury: Option, + pub to_message_relayer: Option<(AccountId, Balance)>, + pub to_confirm_relayer: Option<(AccountId, Balance)>, +} + +impl RewardItem { + fn new() -> Self { + Self { + to_slot_relayer: None, + to_treasury: None, + to_message_relayer: None, + to_confirm_relayer: None, + } + } +} + /// Record the calculation rewards result -pub struct RewardsBook { - pub messages_relayers_rewards: BTreeMap, - pub confirmation_relayer_rewards: Balance, - pub assigned_relayers_rewards: BTreeMap, - pub treasury_total_rewards: Balance, +#[derive(Clone, Debug, Eq, PartialEq, TypeInfo)] +pub struct RewardsBook, I: 'static> { + pub deliver_sum: BTreeMap>, + pub confirm_sum: BalanceOf, + pub assigned_relayers_sum: BTreeMap>, + pub treasury_sum: BalanceOf, +} + +impl, I: 'static> RewardsBook { + fn new() -> Self { + Self { + deliver_sum: BTreeMap::new(), + confirm_sum: BalanceOf::::zero(), + assigned_relayers_sum: BTreeMap::new(), + treasury_sum: BalanceOf::::zero(), + } + } + + fn add_reward_item(&mut self, item: RewardItem>) { + if let Some((id, reward)) = item.to_slot_relayer { + self.assigned_relayers_sum + .entry(id) + .and_modify(|r| *r = r.saturating_add(reward)) + .or_insert(reward); + } + + if let Some(reward) = item.to_treasury { + self.treasury_sum = self.treasury_sum.saturating_add(reward); + } + + if let Some((id, reward)) = item.to_message_relayer { + self.deliver_sum + .entry(id) + .and_modify(|r| *r = r.saturating_add(reward)) + .or_insert(reward); + } + + if let Some((_id, reward)) = item.to_confirm_relayer { + self.confirm_sum = self.confirm_sum.saturating_add(reward); + } + } } diff --git a/modules/fee-market/src/tests.rs b/modules/fee-market/src/tests.rs index 705a8f039..8497b512c 100644 --- a/modules/fee-market/src/tests.rs +++ b/modules/fee-market/src/tests.rs @@ -285,34 +285,30 @@ impl MessageDeliveryAndDispatchPayment received_range: &RangeInclusive, relayer_fund_account: &AccountId, ) { - let RewardsBook { - messages_relayers_rewards, - confirmation_relayer_rewards, - assigned_relayers_rewards, - treasury_total_rewards, - } = slash_and_calculate_rewards::( - lane_id, - message_relayers, - received_range, - relayer_fund_account, - ); - let confimation_key = - (b":relayer-reward:", confirmation_relayer, confirmation_relayer_rewards).encode(); + let RewardsBook { deliver_sum, confirm_sum, assigned_relayers_sum, treasury_sum } = + slash_and_calculate_rewards::( + lane_id, + message_relayers, + confirmation_relayer.clone(), + received_range, + relayer_fund_account, + ); + + let confimation_key = (b":relayer-reward:", confirmation_relayer, confirm_sum).encode(); frame_support::storage::unhashed::put(&confimation_key, &true); - for (relayer, reward) in &messages_relayers_rewards { + for (relayer, reward) in &deliver_sum { let key = (b":relayer-reward:", relayer, reward).encode(); frame_support::storage::unhashed::put(&key, &true); } - for (relayer, reward) in &assigned_relayers_rewards { + for (relayer, reward) in &assigned_relayers_sum { let key = (b":relayer-reward:", relayer, reward).encode(); frame_support::storage::unhashed::put(&key, &true); } let treasury_account: AccountId = ::TreasuryPalletId::get().into_account(); - let treasury_key = - (b":relayer-reward:", &treasury_account, treasury_total_rewards).encode(); + let treasury_key = (b":relayer-reward:", &treasury_account, treasury_sum).encode(); frame_support::storage::unhashed::put(&treasury_key, &true); } } @@ -517,6 +513,10 @@ fn test_call_relayer_enroll_works() { FeeMarket::enroll_and_lock_collateral(Origin::signed(1), 200, None), >::InsufficientBalance ); + assert_err!( + FeeMarket::enroll_and_lock_collateral(Origin::signed(1), 99, None), + >::CollateralTooLow + ); assert_ok!(FeeMarket::enroll_and_lock_collateral(Origin::signed(1), 100, None)); assert!(FeeMarket::is_enrolled(&1)); @@ -529,10 +529,6 @@ fn test_call_relayer_enroll_works() { FeeMarket::enroll_and_lock_collateral(Origin::signed(1), 100, None), >::AlreadyEnrolled ); - - assert_ok!(FeeMarket::enroll_and_lock_collateral(Origin::signed(3), 250, None)); - - assert_ok!(FeeMarket::enroll_and_lock_collateral(Origin::signed(4), 0, None),); }); } @@ -547,14 +543,11 @@ fn test_call_relayer_increase_lock_collateral_works() { let _ = FeeMarket::enroll_and_lock_collateral(Origin::signed(12), 200, None); assert_eq!(FeeMarket::relayer(&12).collateral, 200); - // Increase locked balance from 200 to 500 + // Increase locked collateral from 200 to 500 assert_ok!(FeeMarket::update_locked_collateral(Origin::signed(12), 500)); assert_eq!(FeeMarket::relayer(&12).collateral, 500); - // Increase locked balance from 20 to 200 - let _ = FeeMarket::enroll_and_lock_collateral(Origin::signed(13), 20, None); - assert_ok!(FeeMarket::update_locked_collateral(Origin::signed(13), 200)); - + let _ = FeeMarket::enroll_and_lock_collateral(Origin::signed(13), 200, None); let _ = FeeMarket::enroll_and_lock_collateral(Origin::signed(14), 300, None); let market_fee = FeeMarket::market_fee().unwrap(); let _ = send_regular_message(market_fee); @@ -749,6 +742,14 @@ fn test_callback_order_creation() { assert_eq!(relayers[1].id, assigned_relayers.get(1).unwrap().id); assert_eq!(relayers[2].id, assigned_relayers.get(2).unwrap().id); assert_eq!(order.sent_time, 2); + + System::assert_has_event(Event::FeeMarket(crate::Event::OrderCreated( + lane, + message_nonce, + order.fee(), + vec![relayers[0].id, relayers[1].id, relayers[2].id], + order.range_end(), + ))); }); } @@ -801,7 +802,7 @@ fn test_payment_cal_reward_normally_single_message() { let _ = FeeMarket::enroll_and_lock_collateral(Origin::signed(2), 110, Some(50)); let _ = FeeMarket::enroll_and_lock_collateral(Origin::signed(3), 120, Some(100)); let market_fee = FeeMarket::market_fee().unwrap(); - let (_, _) = send_regular_message(market_fee); + let (lane, message_nonce) = send_regular_message(market_fee); // Receive delivery message proof System::set_block_number(4); @@ -821,16 +822,27 @@ fn test_payment_cal_reward_normally_single_message() { }, )); - // Analysis: - // 1. assigned_relayers [(1, 30, 2-52),(2, 50, 52-102),(3, 100, 102-152)] -> id: 1, reward = - // 60% * 30 = 18 2. message relayer -> id: 100, reward = 40% * 30 * 80% = 9.6 ~ 10 - // 3. confirmation relayer -> id: 5, reward = 40% * 30 * 20% = 2.4 ~ 2 - // 4. treasury reward -> reward: 100 - 30 = 70 + // Reward Analysis: assigned_relayers [(1, 30, 2-52),(2, 50, 52-102),(3, 100, 102-152)] + // 1. slot relayer -> id: 1, reward = 60% * 30 = 18 + // 2. message relayer -> id: 100, reward = 40% * 30 * 80% = 9.6 ~ 10 + // 3. confirm relayer -> id: 5, reward = 40% * 30 * 20% = 2.4 ~ 2 + // 4. treasury reward -> reward = 100 - 30 = 70 let t: AccountId = ::TreasuryPalletId::get().into_account(); assert!(TestMessageDeliveryAndDispatchPayment::is_reward_paid(t, 70)); assert!(TestMessageDeliveryAndDispatchPayment::is_reward_paid(1, 18)); assert!(TestMessageDeliveryAndDispatchPayment::is_reward_paid(5, 2)); assert!(TestMessageDeliveryAndDispatchPayment::is_reward_paid(TEST_RELAYER_A, 10)); + + System::assert_has_event(Event::FeeMarket(crate::Event::OrderReward( + lane, + message_nonce, + RewardItem { + to_slot_relayer: Some((1, 18)), + to_treasury: Some(70), + to_message_relayer: Some((100, 10)), + to_confirm_relayer: Some((5, 2)), + }, + ))); }); }