Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions pallets/rewards/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,24 @@ pub use pallet::*;
pub mod pallet {
// Import various useful types required by all FRAME pallets.
use polkadot_sdk::frame_support::pallet_prelude::*;
use polkadot_sdk::frame_support::traits::{Currency, Imbalance, OnUnbalanced};
use polkadot_sdk::frame_support::weights::Weight;
use polkadot_sdk::frame_system::pallet_prelude::{BlockNumberFor, OriginFor};
use polkadot_sdk::pallet_staking::WeightInfo;
use polkadot_sdk::sp_runtime::Saturating;
use polkadot_sdk::{frame_system, pallet_staking, sp_staking};

use super::*;

/// The balance type of [`Config::Currency`].
pub type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;

/// The negative imbalance type of [`Config::Currency`].
type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
<T as frame_system::Config>::AccountId,
>>::NegativeImbalance;

/// Rewards pallet, providing automated reward payouts for validator block rewards
#[pallet::pallet]
pub struct Pallet<T>(_);
Expand All @@ -44,6 +55,8 @@ pub mod pallet {
+ IsType<<Self as polkadot_sdk::frame_system::Config>::RuntimeEvent>;
/// How many payout calls may be made per block (prevents overweight).
type MaxPayoutsPerBlock: Get<u32>;
/// The currency transaction fees are collected in.
type Currency: Currency<Self::AccountId>;
}

/// The next era that we expect to pay out.
Expand All @@ -56,6 +69,19 @@ pub mod pallet {
#[pallet::getter(fn payer_account)]
pub type PayerAccount<T: Config> = StorageValue<_, T::AccountId, OptionQuery>;

/// Transaction fees collected for inclusion in the next era's validator payout.
#[pallet::storage]
pub type CollectedFees<T: Config> = StorageValue<_, BalanceOf<T>, ValueQuery>;

/// Adds transaction fees to [`CollectedFees`].
pub struct DealWithFees<T>(PhantomData<T>);

impl<T: Config> OnUnbalanced<NegativeImbalanceOf<T>> for DealWithFees<T> {
fn on_nonzero_unbalanced(credit: NegativeImbalanceOf<T>) {
CollectedFees::<T>::mutate(|total| *total = total.saturating_add(credit.peek()));
}
}

/// Errors that could occur while processing validator rewards for payout
#[pallet::error]
pub enum Error<T> {
Expand Down
1 change: 1 addition & 0 deletions pallets/rewards/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ pub const MAX_AUTHORITIES: u32 = 100_000u32;
impl pallet_rewards::Config for Test {
type RuntimeEvent = RuntimeEvent;
type MaxPayoutsPerBlock = ConstU32<3>;
type Currency = Balances;
}

parameter_types! {
Expand Down
28 changes: 27 additions & 1 deletion pallets/rewards/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,32 @@
use crate::mock::new_test_ext;
use polkadot_sdk::frame_support::traits::{Currency, OnUnbalanced};

use crate::mock::{new_test_ext, Balances, Test};
use crate::{CollectedFees, DealWithFees};

#[test]
fn staking_rewards_pay_out() {
new_test_ext().execute_with(|| {});
}

#[test]
fn unbalanced_fees_accumulate_in_collected_fees() {
new_test_ext().execute_with(|| {
assert_eq!(CollectedFees::<Test>::get(), 0);

DealWithFees::<Test>::on_unbalanced(Balances::issue(100));
DealWithFees::<Test>::on_unbalanceds([Balances::issue(20), Balances::issue(3)].into_iter());

assert_eq!(CollectedFees::<Test>::get(), 123);
});
}

#[test]
fn collected_fees_saturate() {
new_test_ext().execute_with(|| {
CollectedFees::<Test>::put(u128::MAX - 1);

DealWithFees::<Test>::on_unbalanced(Balances::issue(5));

assert_eq!(CollectedFees::<Test>::get(), u128::MAX);
});
}
1 change: 1 addition & 0 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ polkadot-sdk = { workspace = true, features = [
"pallet-authorship",
"pallet-im-online",
"pallet-offences",
"pallet-parameters",
"pallet-authority-discovery",
"pallet-election-provider-multi-phase",
"pallet-babe",
Expand Down
94 changes: 75 additions & 19 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,18 @@ use polkadot_sdk::frame_election_provider_support::{
SequentialPhragmen,
};
use polkadot_sdk::frame_support::dispatch::DispatchClass;
use polkadot_sdk::frame_support::dynamic_params::{dynamic_pallet_params, dynamic_params};
use polkadot_sdk::frame_support::genesis_builder_helper::{build_state, get_preset};
use polkadot_sdk::frame_support::traits::VariantCountOf;
pub use polkadot_sdk::frame_support::traits::{
AsEnsureOriginWithArg,
ConstBool,
ConstU128,
ConstU32,
ConstU64,
ConstU8,
Currency,
Get,
KeyOwnerProofSystem,
Randomness,
StorageInfo,
Expand All @@ -55,9 +58,8 @@ use polkadot_sdk::pallet_grandpa::AuthorityId as GrandpaId;
pub use polkadot_sdk::pallet_im_online::sr25519::AuthorityId as ImOnlineId;
pub use polkadot_sdk::pallet_timestamp::Call as TimestampCall;
#[allow(deprecated)]
use polkadot_sdk::pallet_transaction_payment::{CurrencyAdapter, Multiplier};
use polkadot_sdk::pallet_transaction_payment::{ConstFeeMultiplier, CurrencyAdapter, Multiplier};
use polkadot_sdk::sp_api::impl_runtime_apis;
use polkadot_sdk::sp_arithmetic::traits::UniqueSaturatedInto;
use polkadot_sdk::sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
use polkadot_sdk::sp_consensus_babe::AuthorityId as BabeId;
use polkadot_sdk::sp_core::crypto::KeyTypeId;
Expand Down Expand Up @@ -109,6 +111,7 @@ use polkadot_sdk::{
pallet_migrations,
pallet_multisig,
pallet_offences,
pallet_parameters,
pallet_session,
pallet_staking,
pallet_staking_runtime_api,
Expand Down Expand Up @@ -244,6 +247,7 @@ macro_rules! prod_or_dev {
/// <https://research.web3.foundation/Polkadot/protocols/block-production/Babe#6-practical-results>
pub const MILLISECS_PER_BLOCK: u64 = 3000;
pub const SECS_PER_BLOCK: u64 = MILLISECS_PER_BLOCK / 1000;
pub const MILLISECONDS_PER_DAY: u64 = 1000 * 3600 * 24;

// NOTE: Currently it is not possible to change the slot duration after the chain has started.
// Attempting to do so will brick block production.
Expand Down Expand Up @@ -419,8 +423,6 @@ pub const WEIGHT_FEE: u128 =
AVERAGE_INSERT_TARGET_COST_PER_ROW.saturating_div(INSERT_FEE_TARGET_CALL_WEIGHT);

parameter_types! {
pub const TransactionByteFee: Balance = TARGET_BYTE_FEE;
pub const WeightFeePerRefTime: Balance = WEIGHT_FEE;
pub const OperationalFeeMultiplier: u8 = 5;
pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(80);
pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(1, 100_000);
Expand All @@ -431,10 +433,13 @@ parameter_types! {
impl pallet_transaction_payment::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
#[allow(deprecated)]
type OnChargeTransaction = CurrencyAdapter<Balances, ()>;
type WeightToFee = ConstantMultiplier<Balance, WeightFeePerRefTime>;
type LengthToFee = ConstantMultiplier<Balance, TransactionByteFee>;
type FeeMultiplierUpdate = ();
type OnChargeTransaction = CurrencyAdapter<Balances, pallet_rewards::DealWithFees<Runtime>>;
type WeightToFee =
ConstantMultiplier<Balance, dynamic_params::transaction_payment::WeightFeePerRefTime>;
type LengthToFee =
ConstantMultiplier<Balance, dynamic_params::transaction_payment::TransactionByteFee>;
type FeeMultiplierUpdate =
ConstFeeMultiplier<dynamic_params::transaction_payment::FeeMultiplier>;
type OperationalFeeMultiplier = OperationalFeeMultiplier;
}

Expand Down Expand Up @@ -538,17 +543,14 @@ impl pallet_staking::EraPayout<Balance> for EraPayout {
_total_issuance: Balance,
era_duration_millis: u64,
) -> (Balance, Balance) {
const MILLISECONDS_PER_YEAR: u64 = (1000 * 3600 * 24 * 36525) / 100;
// A normal-sized era will have 1 / 365.25 here:
let relative_era_len =
FixedU128::from_rational(era_duration_millis.into(), MILLISECONDS_PER_YEAR.into());

let base_rate = FixedU128::from_rational(97, 1000);
let yearly_emission = base_rate.saturating_mul_int(total_staked);

let era_emission = relative_era_len.saturating_mul_int(yearly_emission);

(era_emission.unique_saturated_into(), Balance::zero())
(
FixedU128::from_rational(era_duration_millis.into(), MILLISECONDS_PER_DAY.into())
.saturating_mul_int(
dynamic_params::rewards::PerDiemRate::get().mul_floor(total_staked),
)
.saturating_add(pallet_rewards::CollectedFees::<Runtime>::take()),
Balance::zero(),
)
}
}

Expand Down Expand Up @@ -885,10 +887,60 @@ impl pallet_smartcontracts::Config<native_api::Api> for Runtime {
type WeightInfo = pallet_smartcontracts::weights::SubstrateWeight<Runtime>;
}

/// Root-settable runtime parameters.
#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::<Runtime>)]
pub mod dynamic_params {
use super::*;

/// Validator reward parameters.
#[dynamic_pallet_params]
#[codec(index = 0)]
pub mod rewards {
/// The daily validator reward rate, applied to total stake; defaults to 9.7% per year.
#[codec(index = 0)]
pub static PerDiemRate: Perbill = Perbill::from_rational(97u64, 365_250u64);
}

/// Transaction fee parameters.
#[dynamic_pallet_params]
#[codec(index = 1)]
pub mod transaction_payment {
/// Fee per unit of `ref_time`, applied to the base fee and the multiplied weight fee.
#[codec(index = 0)]
pub static WeightFeePerRefTime: Balance = WEIGHT_FEE;

/// Fee per byte of extrinsic length.
#[codec(index = 1)]
pub static TransactionByteFee: Balance = TARGET_BYTE_FEE;

/// Multiplier applied to the weight fee, excluding the base fee.
#[codec(index = 2)]
pub static FeeMultiplier: Multiplier = Multiplier::from_u32(0);
}
}

#[cfg(feature = "runtime-benchmarks")]
impl Default for RuntimeParameters {
fn default() -> Self {
RuntimeParameters::Rewards(dynamic_params::rewards::Parameters::PerDiemRate(
dynamic_params::rewards::PerDiemRate,
Some(Perbill::from_percent(1)),
))
}
}

impl pallet_parameters::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type RuntimeParameters = RuntimeParameters;
type AdminOrigin = AsEnsureOriginWithArg<EnsureRoot<AccountId>>;
type WeightInfo = ();
}

impl pallet_rewards::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
// Payout up to 3 pages per block
type MaxPayoutsPerBlock = ConstU32<3>;
type Currency = Balances;
}

#[cfg(feature = "runtime-benchmarks")]
Expand Down Expand Up @@ -976,6 +1028,9 @@ mod runtime {
#[runtime::pallet_index(72)]
pub type MultiBlockMigrations = pallet_migrations::Pallet<Runtime>;

#[runtime::pallet_index(73)]
pub type Parameters = pallet_parameters::Pallet<Runtime>;

// Custom pallets start at index 100 to ensure room for future consensus work
#[runtime::pallet_index(100)]
pub type Permissions = pallet_permissions::Pallet<Runtime>;
Expand Down Expand Up @@ -1063,6 +1118,7 @@ mod benches {
[pallet_staking, Staking]
[pallet_sudo, Sudo]
[pallet_multisig, Multisig]
[pallet_parameters, Parameters]
[pallet_migrations, MultiBlockMigrations]
[frame_system, SystemBench::<Runtime>]
[pallet_timestamp, Timestamp]
Expand Down
Loading
Loading