// 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_relayers::PaymentProcedure; use sp_arithmetic::traits::AtLeast32BitUnsigned; use sp_std::marker::PhantomData; use weights::WeightInfo; pub use pallet::*; pub use payment_adapter::MessageDeliveryAndDispatchPaymentAdapter; mod benchmarking; mod mock; mod payment_adapter; pub mod weights; #[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 Event: From> + IsType<::Event>; /// 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) -> DispatchResult { let relayer = ensure_signed(origin)?; RelayerRewards::::try_mutate_exists(&relayer, |maybe_reward| -> DispatchResult { let reward = maybe_reward.take().ok_or(Error::::NoRewardForRelayer)?; T::PaymentProcedure::pay_reward(&relayer, reward).map_err(|e| { log::trace!( target: "runtime::bridge-relayers", "Failed to pay rewards to {:?}: {:?}", relayer, e, ); Error::::FailedToPayReward })?; Self::deposit_event(Event::::RewardPaid { relayer: relayer.clone(), reward }); Ok(()) }) } } #[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, /// 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 = StorageMap<_, Blake2_128Concat, T::AccountId, T::Reward, OptionQuery>; } #[cfg(test)] mod tests { use super::*; use mock::*; use frame_support::{assert_noop, assert_ok, traits::fungible::Inspect}; use sp_runtime::DispatchError; #[test] fn root_cant_claim_anything() { run_test(|| { assert_noop!( Pallet::::claim_rewards(Origin::root()), DispatchError::BadOrigin, ); }); } #[test] fn relayer_cant_claim_if_no_reward_exists() { run_test(|| { assert_noop!( Pallet::::claim_rewards(Origin::signed(REGULAR_RELAYER)), Error::::NoRewardForRelayer, ); }); } #[test] fn relayer_cant_claim_if_payment_procedure_fails() { run_test(|| { RelayerRewards::::insert(FAILING_RELAYER, 100); assert_noop!( Pallet::::claim_rewards(Origin::signed(FAILING_RELAYER)), Error::::FailedToPayReward, ); }); } #[test] fn relayer_can_claim_reward() { run_test(|| { RelayerRewards::::insert(REGULAR_RELAYER, 100); assert_ok!(Pallet::::claim_rewards(Origin::signed(REGULAR_RELAYER))); assert_eq!(RelayerRewards::::get(REGULAR_RELAYER), None); }); } #[test] fn mint_reward_payment_procedure_actually_mints_tokens() { type Balances = pallet_balances::Pallet; run_test(|| { assert_eq!(Balances::balance(&1), 0); assert_eq!(Balances::total_issuance(), 0); bp_relayers::MintReward::::pay_reward(&1, 100).unwrap(); assert_eq!(Balances::balance(&1), 100); assert_eq!(Balances::total_issuance(), 100); }); } }