// Copyright 2019-2020 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot 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. // Polkadot 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 Polkadot. If not, see . //! Auxiliary `struct`/`enum`s for polkadot runtime. use crate::NegativeImbalance; use frame_support::traits::{Currency, Imbalance, OnUnbalanced}; use primitives::Balance; use sp_runtime::Perquintill; /// Logic for the author to get a portion of fees. pub struct ToAuthor(sp_std::marker::PhantomData); impl OnUnbalanced> for ToAuthor where R: pallet_balances::Config + pallet_authorship::Config, ::AccountId: From, ::AccountId: Into, { fn on_nonzero_unbalanced(amount: NegativeImbalance) { if let Some(author) = >::author() { >::resolve_creating(&author, amount); } } } pub struct DealWithFees(sp_std::marker::PhantomData); impl OnUnbalanced> for DealWithFees where R: pallet_balances::Config + pallet_treasury::Config + pallet_authorship::Config, pallet_treasury::Pallet: OnUnbalanced>, ::AccountId: From, ::AccountId: Into, { fn on_unbalanceds(mut fees_then_tips: impl Iterator>) { if let Some(fees) = fees_then_tips.next() { // for fees, 80% to treasury, 20% to author let mut split = fees.ration(80, 20); if let Some(tips) = fees_then_tips.next() { // for tips, if any, 100% to author tips.merge_into(&mut split.1); } use pallet_treasury::Pallet as Treasury; as OnUnbalanced<_>>::on_unbalanced(split.0); as OnUnbalanced<_>>::on_unbalanced(split.1); } } } pub fn era_payout( total_staked: Balance, total_stakable: Balance, max_annual_inflation: Perquintill, period_fraction: Perquintill, auctioned_slots: u64, ) -> (Balance, Balance) { use pallet_staking_reward_fn::compute_inflation; use sp_runtime::traits::Saturating; let min_annual_inflation = Perquintill::from_rational(25u64, 1000u64); let delta_annual_inflation = max_annual_inflation.saturating_sub(min_annual_inflation); // 30% reserved for up to 60 slots. let auction_proportion = Perquintill::from_rational(auctioned_slots.min(60), 200u64); // Therefore the ideal amount at stake (as a percentage of total issuance) is 75% less the // amount that we expect to be taken up with auctions. let ideal_stake = Perquintill::from_percent(75).saturating_sub(auction_proportion); let stake = Perquintill::from_rational(total_staked, total_stakable); let falloff = Perquintill::from_percent(5); let adjustment = compute_inflation(stake, ideal_stake, falloff); let staking_inflation = min_annual_inflation.saturating_add(delta_annual_inflation * adjustment); let max_payout = period_fraction * max_annual_inflation * total_stakable; let staking_payout = (period_fraction * staking_inflation) * total_stakable; let rest = max_payout.saturating_sub(staking_payout); let other_issuance = total_stakable.saturating_sub(total_staked); if total_staked > other_issuance { let _cap_rest = Perquintill::from_rational(other_issuance, total_staked) * staking_payout; // We don't do anything with this, but if we wanted to, we could introduce a cap on the // treasury amount with: `rest = rest.min(cap_rest);` } (staking_payout, rest) } #[cfg(test)] mod tests { use super::*; use frame_support::{ dispatch::DispatchClass, parameter_types, traits::FindAuthor, weights::Weight, PalletId, }; use frame_system::limits; use primitives::AccountId; use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, Perbill, }; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; const TEST_ACCOUNT: AccountId = AccountId::new([1; 32]); frame_support::construct_runtime!( pub enum Test where Block = Block, NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, Authorship: pallet_authorship::{Pallet, Storage}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event}, } ); parameter_types! { pub const BlockHashCount: u64 = 250; pub BlockWeights: limits::BlockWeights = limits::BlockWeights::builder() .base_block(Weight::from_ref_time(10)) .for_class(DispatchClass::all(), |weight| { weight.base_extrinsic = Weight::from_ref_time(100); }) .for_class(DispatchClass::non_mandatory(), |weight| { weight.max_total = Some(Weight::from_parts(1024, u64::MAX)); }) .build_or_panic(); pub BlockLength: limits::BlockLength = limits::BlockLength::max(2 * 1024); pub const AvailableBlockRatio: Perbill = Perbill::one(); } impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; type Index = u64; type BlockNumber = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Header = Header; type RuntimeEvent = RuntimeEvent; type BlockHashCount = BlockHashCount; type BlockLength = BlockLength; type BlockWeights = BlockWeights; type DbWeight = (); type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_balances::Config for Test { type Balance = u64; type RuntimeEvent = RuntimeEvent; type DustRemoval = (); type ExistentialDeposit = (); type AccountStore = System; type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type WeightInfo = (); } parameter_types! { pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); pub const MaxApprovals: u32 = 100; } impl pallet_treasury::Config for Test { type Currency = pallet_balances::Pallet; type ApproveOrigin = frame_system::EnsureRoot; type RejectOrigin = frame_system::EnsureRoot; type RuntimeEvent = RuntimeEvent; type OnSlash = (); type ProposalBond = (); type ProposalBondMinimum = (); type ProposalBondMaximum = (); type SpendPeriod = (); type Burn = (); type BurnDestination = (); type PalletId = TreasuryPalletId; type SpendFunds = (); type MaxApprovals = MaxApprovals; type WeightInfo = (); type SpendOrigin = frame_support::traits::NeverEnsureOrigin; } pub struct OneAuthor; impl FindAuthor for OneAuthor { fn find_author<'a, I>(_: I) -> Option where I: 'a, { Some(TEST_ACCOUNT) } } impl pallet_authorship::Config for Test { type FindAuthor = OneAuthor; type EventHandler = (); } pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); // We use default for brevity, but you can configure as desired if needed. pallet_balances::GenesisConfig::::default() .assimilate_storage(&mut t) .unwrap(); t.into() } #[test] fn test_fees_and_tip_split() { new_test_ext().execute_with(|| { let fee = Balances::issue(10); let tip = Balances::issue(20); assert_eq!(Balances::free_balance(Treasury::account_id()), 0); assert_eq!(Balances::free_balance(TEST_ACCOUNT), 0); DealWithFees::on_unbalanceds(vec![fee, tip].into_iter()); // Author gets 100% of tip and 20% of fee = 22 assert_eq!(Balances::free_balance(TEST_ACCOUNT), 22); // Treasury gets 80% of fee assert_eq!(Balances::free_balance(Treasury::account_id()), 8); }); } #[test] fn compute_inflation_should_give_sensible_results() { assert_eq!( pallet_staking_reward_fn::compute_inflation( Perquintill::from_percent(75), Perquintill::from_percent(75), Perquintill::from_percent(5), ), Perquintill::one() ); assert_eq!( pallet_staking_reward_fn::compute_inflation( Perquintill::from_percent(50), Perquintill::from_percent(75), Perquintill::from_percent(5), ), Perquintill::from_rational(2u64, 3u64) ); assert_eq!( pallet_staking_reward_fn::compute_inflation( Perquintill::from_percent(80), Perquintill::from_percent(75), Perquintill::from_percent(5), ), Perquintill::from_rational(1u64, 2u64) ); } #[test] fn era_payout_should_give_sensible_results() { assert_eq!( era_payout(75, 100, Perquintill::from_percent(10), Perquintill::one(), 0,), (10, 0) ); assert_eq!( era_payout(80, 100, Perquintill::from_percent(10), Perquintill::one(), 0,), (6, 4) ); } }