|
| 1 | +//! # Fee Handler Pallet |
| 2 | +//! |
| 3 | +//! This pallet is responsible for handling fee distribution in a Substrate-based blockchain. |
| 4 | +//! It provides functionality to configure fee distribution proportions and transfer fees to |
| 5 | +//! designated accounts (e.g., treasury and fee pot accounts). |
| 6 | +//! |
| 7 | +//! ## Overview |
| 8 | +//! |
| 9 | +//! - Allows governance to configure fee distribution proportions. |
| 10 | +//! - Handles fee transfers to the treasury and fee pot accounts based on the configured |
| 11 | +//! proportions. |
| 12 | +//! - Emits events for key actions such as configuration updates and fee transfers. |
| 13 | +//! |
| 14 | +//! ## Dispatchable Functions |
| 15 | +//! |
| 16 | +//! - `manual_topup`: Allows a user to manually top up the fee pot account. |
| 17 | +//! - `fee_distribution_config`: Allows governance to set the fee distribution proportions. |
| 18 | +
|
| 19 | +#![cfg_attr(not(feature = "std"), no_std)] |
| 20 | + |
| 21 | +// todo! Add Unit tests and Benchmarking |
| 22 | + |
| 23 | +use codec::{Decode, Encode, MaxEncodedLen}; |
| 24 | +use frame_support::{__private::RuntimeDebug, pallet_prelude::TypeInfo, traits::Currency}; |
| 25 | +pub use pallet::*; |
| 26 | +use sp_runtime::{Permill, Saturating}; |
| 27 | + |
| 28 | +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] |
| 29 | +pub struct FeeDistributionProportion { |
| 30 | + treasury_proportion: Permill, |
| 31 | + fee_pot_proportion: Permill, |
| 32 | +} |
| 33 | + |
| 34 | +pub type BalanceOf<T> = |
| 35 | + <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance; |
| 36 | + |
| 37 | +impl FeeDistributionProportion { |
| 38 | + /// Creates a new `FeeDistributionProportion` if the total proportions equal 100%. |
| 39 | + pub fn new(treasury_proportion: u32, fee_pot_proportion: u32) -> Option<Self> { |
| 40 | + let treasury_proportion = Permill::from_percent(treasury_proportion); |
| 41 | + let fee_pot_proportion = Permill::from_percent(fee_pot_proportion); |
| 42 | + let total = treasury_proportion.saturating_add(fee_pot_proportion); |
| 43 | + if total == Permill::one() { |
| 44 | + Some(Self { treasury_proportion, fee_pot_proportion }) |
| 45 | + } else { |
| 46 | + None |
| 47 | + } |
| 48 | + } |
| 49 | +} |
| 50 | + |
| 51 | +pub trait FeeHandler<T: Config> { |
| 52 | + /// Handles the distribution of fees to the treasury and fee pot accounts. |
| 53 | + fn handle_fee(source: T::AccountId, fee_amount: BalanceOf<T>) -> sp_runtime::DispatchResult; |
| 54 | +} |
| 55 | + |
| 56 | +// todo! Fixed clippy warnings |
| 57 | +#[allow(deprecated)] |
| 58 | +#[allow(clippy::let_unit_value)] |
| 59 | +#[allow(clippy::manual_inspect)] |
| 60 | +#[frame_support::pallet] |
| 61 | +pub mod pallet { |
| 62 | + use frame_support::{ |
| 63 | + pallet_prelude::*, |
| 64 | + traits::{ExistenceRequirement, LockableCurrency}, |
| 65 | + PalletId, |
| 66 | + }; |
| 67 | + use frame_system::pallet_prelude::*; |
| 68 | + use sp_runtime::traits::AccountIdConversion; |
| 69 | + |
| 70 | + use super::*; |
| 71 | + |
| 72 | + #[pallet::pallet] |
| 73 | + pub struct Pallet<T>(_); |
| 74 | + |
| 75 | + #[pallet::config] |
| 76 | + pub trait Config: frame_system::Config { |
| 77 | + /// The overarching runtime event type. |
| 78 | + type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; |
| 79 | + /// Native Currency Support. |
| 80 | + type Currency: LockableCurrency<Self::AccountId, Moment = BlockNumberFor<Self>>; |
| 81 | + /// Governance origin for privileged calls. |
| 82 | + type GovernanceOrigin: EnsureOrigin<Self::RuntimeOrigin>; |
| 83 | + /// Pallet ID for the fee pot account. |
| 84 | + #[pallet::constant] |
| 85 | + type PalletId: Get<PalletId>; |
| 86 | + /// Pallet ID for the treasury account. |
| 87 | + #[pallet::constant] |
| 88 | + type TreasuryPalletId: Get<PalletId>; |
| 89 | + } |
| 90 | + |
| 91 | + #[pallet::storage] |
| 92 | + pub type FeeDistributionProportionConfig<T> = StorageValue<_, FeeDistributionProportion>; |
| 93 | + |
| 94 | + #[pallet::event] |
| 95 | + #[pallet::generate_deposit(pub(super) fn deposit_event)] |
| 96 | + pub enum Event<T: Config> { |
| 97 | + /// Manual top-up of the fee pot account. |
| 98 | + ManualFeeAccountTopUp { source: T::AccountId, amount: BalanceOf<T> }, |
| 99 | + /// Fee distribution configuration updated. |
| 100 | + FeeDistributionProportionConfigSet { config: FeeDistributionProportion }, |
| 101 | + } |
| 102 | + |
| 103 | + #[pallet::error] |
| 104 | + pub enum Error<T> { |
| 105 | + /// Fee distribution configuration is not set. |
| 106 | + FeeDistributionConfigNotSet, |
| 107 | + /// Arithmetic overflow occurred. |
| 108 | + ArithmeticOverflow, |
| 109 | + } |
| 110 | + |
| 111 | + #[pallet::call] |
| 112 | + impl<T: Config> Pallet<T> { |
| 113 | + /// Allows a user to manually top up the fee pot account. |
| 114 | + #[pallet::call_index(0)] |
| 115 | + // todo! Add actual weights |
| 116 | + #[pallet::weight(10_000)] |
| 117 | + pub fn manual_topup(origin: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult { |
| 118 | + let who = ensure_signed(origin)?; |
| 119 | + T::Currency::transfer( |
| 120 | + &who, |
| 121 | + &Self::fee_pot_account_id(), |
| 122 | + amount, |
| 123 | + ExistenceRequirement::AllowDeath, |
| 124 | + )?; |
| 125 | + Self::deposit_event(Event::ManualFeeAccountTopUp { source: who, amount }); |
| 126 | + Ok(()) |
| 127 | + } |
| 128 | + |
| 129 | + /// Allows governance to set the fee distribution proportions. |
| 130 | + #[pallet::call_index(1)] |
| 131 | + // todo! Add actual weights |
| 132 | + #[pallet::weight(10_000)] |
| 133 | + pub fn fee_distribution_config( |
| 134 | + origin: OriginFor<T>, |
| 135 | + treasury_fee_proportion: u32, |
| 136 | + fee_pot_proportion: u32, |
| 137 | + ) -> DispatchResult { |
| 138 | + T::GovernanceOrigin::ensure_origin(origin)?; |
| 139 | + let fee_distribution_config = |
| 140 | + FeeDistributionProportion::new(treasury_fee_proportion, fee_pot_proportion) |
| 141 | + .ok_or(Error::<T>::ArithmeticOverflow)?; |
| 142 | + <FeeDistributionProportionConfig<T>>::put(fee_distribution_config.clone()); |
| 143 | + Self::deposit_event(Event::FeeDistributionProportionConfigSet { |
| 144 | + config: fee_distribution_config, |
| 145 | + }); |
| 146 | + Ok(()) |
| 147 | + } |
| 148 | + } |
| 149 | + |
| 150 | + impl<T: Config> Pallet<T> { |
| 151 | + /// Returns the account ID of the fee pot. |
| 152 | + pub fn fee_pot_account_id() -> T::AccountId { |
| 153 | + T::PalletId::get().into_account_truncating() |
| 154 | + } |
| 155 | + |
| 156 | + /// Returns the account ID of the treasury. |
| 157 | + pub fn treasury_account_id() -> T::AccountId { |
| 158 | + T::TreasuryPalletId::get().into_account_truncating() |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + impl<T: Config> FeeHandler<T> for Pallet<T> { |
| 163 | + fn handle_fee(source: T::AccountId, fee_amount: BalanceOf<T>) -> DispatchResult { |
| 164 | + let fee_config: FeeDistributionProportion = <FeeDistributionProportionConfig<T>>::get() |
| 165 | + .ok_or(Error::<T>::FeeDistributionConfigNotSet)?; |
| 166 | + let fee_pot_amount = fee_config.fee_pot_proportion.mul_floor(fee_amount); |
| 167 | + let treasury_amount = fee_amount.saturating_sub(fee_pot_amount); |
| 168 | + |
| 169 | + let fee_pot_account = Pallet::<T>::fee_pot_account_id(); |
| 170 | + let treasury_account = Pallet::<T>::treasury_account_id(); |
| 171 | + |
| 172 | + T::Currency::transfer( |
| 173 | + &source, |
| 174 | + &fee_pot_account, |
| 175 | + fee_pot_amount, |
| 176 | + ExistenceRequirement::AllowDeath, |
| 177 | + )?; |
| 178 | + T::Currency::transfer( |
| 179 | + &source, |
| 180 | + &treasury_account, |
| 181 | + treasury_amount, |
| 182 | + ExistenceRequirement::AllowDeath, |
| 183 | + )?; |
| 184 | + Ok(()) |
| 185 | + } |
| 186 | + } |
| 187 | +} |
0 commit comments