// Copyright 2019-2021 Parity Technologies (UK) Ltd. // This file is part of Parity Bridges Common. // Parity Bridges Common is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Parity Bridges Common is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . //! Runtime module that is used to store relayer rewards and (in the future) to //! coordinate relations between relayers. #![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs)] use bp_messages::LaneId; use bp_relayers::PaymentProcedure; use frame_support::sp_runtime::Saturating; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; use sp_std::marker::PhantomData; use weights::WeightInfo; pub use pallet::*; pub use payment_adapter::DeliveryConfirmationPaymentsAdapter; pub mod benchmarking; mod mock; mod payment_adapter; pub mod weights; /// The target that will be used when publishing logs related to this pallet. pub const LOG_TARGET: &str = "runtime::bridge-relayers"; #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Type of relayer reward. type Reward: AtLeast32BitUnsigned + Copy + Parameter + MaxEncodedLen; /// Pay rewards adapter. type PaymentProcedure: PaymentProcedure; /// Pallet call weights. type WeightInfo: WeightInfo; } #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(PhantomData); #[pallet::call] impl Pallet { /// Claim accumulated rewards. #[pallet::weight(T::WeightInfo::claim_rewards())] pub fn claim_rewards(origin: OriginFor, lane_id: LaneId) -> DispatchResult { let relayer = ensure_signed(origin)?; RelayerRewards::::try_mutate_exists( &relayer, lane_id, |maybe_reward| -> DispatchResult { let reward = maybe_reward.take().ok_or(Error::::NoRewardForRelayer)?; T::PaymentProcedure::pay_reward(&relayer, lane_id, reward).map_err(|e| { log::trace!( target: LOG_TARGET, "Failed to pay {:?} rewards to {:?}: {:?}", lane_id, relayer, e, ); Error::::FailedToPayReward })?; Self::deposit_event(Event::::RewardPaid { relayer: relayer.clone(), lane_id, reward, }); Ok(()) }, ) } } impl Pallet { /// Register reward for given relayer. pub fn register_relayer_reward(lane_id: LaneId, relayer: &T::AccountId, reward: T::Reward) { if reward.is_zero() { return } RelayerRewards::::mutate(relayer, lane_id, |old_reward: &mut Option| { let new_reward = old_reward.unwrap_or_else(Zero::zero).saturating_add(reward); *old_reward = Some(new_reward); log::trace!( target: crate::LOG_TARGET, "Relayer {:?} can now claim reward: {:?}", relayer, new_reward, ); }); } } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Reward has been paid to the relayer. RewardPaid { /// Relayer account that has been rewarded. relayer: T::AccountId, /// Relayer has received reward for serving this lane. lane_id: LaneId, /// Reward amount. reward: T::Reward, }, } #[pallet::error] pub enum Error { /// No reward can be claimed by given relayer. NoRewardForRelayer, /// Reward payment procedure has failed. FailedToPayReward, } /// Map of the relayer => accumulated reward. #[pallet::storage] pub type RelayerRewards = StorageDoubleMap< _, Blake2_128Concat, T::AccountId, Identity, LaneId, T::Reward, OptionQuery, >; } #[cfg(test)] mod tests { use super::*; use mock::{RuntimeEvent as TestEvent, *}; use crate::Event::RewardPaid; use frame_support::{ assert_noop, assert_ok, traits::fungible::{Inspect, Mutate}, }; use frame_system::{EventRecord, Pallet as System, Phase}; use sp_runtime::DispatchError; fn get_ready_for_events() { System::::set_block_number(1); System::::reset_events(); } #[test] fn root_cant_claim_anything() { run_test(|| { assert_noop!( Pallet::::claim_rewards(RuntimeOrigin::root(), TEST_LANE_ID), DispatchError::BadOrigin, ); }); } #[test] fn relayer_cant_claim_if_no_reward_exists() { run_test(|| { assert_noop!( Pallet::::claim_rewards( RuntimeOrigin::signed(REGULAR_RELAYER), TEST_LANE_ID ), Error::::NoRewardForRelayer, ); }); } #[test] fn relayer_cant_claim_if_payment_procedure_fails() { run_test(|| { RelayerRewards::::insert(FAILING_RELAYER, TEST_LANE_ID, 100); assert_noop!( Pallet::::claim_rewards( RuntimeOrigin::signed(FAILING_RELAYER), TEST_LANE_ID ), Error::::FailedToPayReward, ); }); } #[test] fn relayer_can_claim_reward() { run_test(|| { get_ready_for_events(); RelayerRewards::::insert(REGULAR_RELAYER, TEST_LANE_ID, 100); assert_ok!(Pallet::::claim_rewards( RuntimeOrigin::signed(REGULAR_RELAYER), TEST_LANE_ID )); assert_eq!(RelayerRewards::::get(REGULAR_RELAYER, TEST_LANE_ID), None); //Check if the `RewardPaid` event was emitted. assert_eq!( System::::events(), vec![EventRecord { phase: Phase::Initialization, event: TestEvent::Relayers(RewardPaid { relayer: REGULAR_RELAYER, lane_id: TEST_LANE_ID, reward: 100 }), topics: vec![], }], ); }); } #[test] fn pay_lane_reward_from_account_actually_pays_reward() { type Balances = pallet_balances::Pallet; type PayLaneRewardFromAccount = bp_relayers::PayLaneRewardFromAccount; run_test(|| { let lane0_rewards_account = PayLaneRewardFromAccount::lane_rewards_account([0, 0, 0, 0]); let lane1_rewards_account = PayLaneRewardFromAccount::lane_rewards_account([0, 0, 0, 1]); Balances::mint_into(&lane0_rewards_account, 100).unwrap(); Balances::mint_into(&lane1_rewards_account, 100).unwrap(); assert_eq!(Balances::balance(&lane0_rewards_account), 100); assert_eq!(Balances::balance(&lane1_rewards_account), 100); assert_eq!(Balances::balance(&1), 0); PayLaneRewardFromAccount::pay_reward(&1, [0, 0, 0, 0], 100).unwrap(); assert_eq!(Balances::balance(&lane0_rewards_account), 0); assert_eq!(Balances::balance(&lane1_rewards_account), 100); assert_eq!(Balances::balance(&1), 100); PayLaneRewardFromAccount::pay_reward(&1, [0, 0, 0, 1], 100).unwrap(); assert_eq!(Balances::balance(&lane0_rewards_account), 0); assert_eq!(Balances::balance(&lane1_rewards_account), 0); assert_eq!(Balances::balance(&1), 200); }); } }