Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements a % cap on staking rewards from era inflation #1660

Merged
merged 28 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e88d2cd
Adds minimum treasury inflation per era
gpestana Sep 21, 2023
8a64bef
Adds set_min_treasury_fraction extrinsic; Improves tests
gpestana Sep 22, 2023
810e9d8
Updates docs
gpestana Sep 22, 2023
02d5512
refactors so that the max_stakers_payout is used rather than min_rema…
gpestana Sep 23, 2023
9ad6e10
Merge branch 'master' of https://github.com/paritytech/polkadot-sdk i…
Sep 23, 2023
046119d
".git/.scripts/commands/bench/bench.sh" --subcommand=pallet --runtime…
Sep 23, 2023
6cc5785
Addresses PR comments
gpestana Sep 23, 2023
59fb828
fix westend weights
gpestana Sep 23, 2023
cf89d26
Defines backwards compatible default for MaxStakedRewards
gpestana Sep 25, 2023
910c785
Merge branch 'master' into gpestana/403-treasury_mint_remainder
gpestana Oct 23, 2023
50f12ad
bubbles up cap of staking rewards to staking pallet
gpestana Oct 23, 2023
d98148f
Backtracks changes to era_payout in runtime-common
gpestana Oct 23, 2023
019c04a
improves tests
gpestana Oct 23, 2023
261450c
More test improvements
gpestana Oct 23, 2023
c317d43
Merge branch 'master' into gpestana/403-treasury_mint_remainder
gpestana Jan 8, 2024
13a0941
Fixes benchmarks
gpestana Jan 8, 2024
b1d3780
".git/.scripts/commands/bench/bench.sh" --subcommand=pallet --runtime…
Jan 8, 2024
078bf86
".git/.scripts/commands/bench/bench.sh" --subcommand=runtime --runtim…
Jan 8, 2024
3d9a364
Merge branch 'master' into gpestana/403-treasury_mint_remainder
gpestana Jan 21, 2024
1fdf817
Add prdoc
gpestana Jan 21, 2024
9a18ae1
Fixes prdoc
gpestana Jan 22, 2024
06c9eb8
sets max staked rewards through the set_staking_config callable
gpestana Jan 28, 2024
48eab32
fixes nom pools e2e tests set_staking_configs
gpestana Jan 28, 2024
b3cb0d9
removes set_max_staked_rewards from westend weights
gpestana Jan 28, 2024
f9f4e94
fixes benchmarks
gpestana Jan 28, 2024
4f589b3
Merge branch 'master' of https://github.com/paritytech/polkadot-sdk i…
Jan 28, 2024
73adb6c
".git/.scripts/commands/bench/bench.sh" --subcommand=pallet --runtime…
Jan 28, 2024
72d5f3c
Merge branch 'master' into gpestana/403-treasury_mint_remainder
gpestana Feb 15, 2024
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
60 changes: 55 additions & 5 deletions polkadot/runtime/common/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
use crate::NegativeImbalance;
use frame_support::traits::{Currency, Imbalance, OnUnbalanced};
use primitives::Balance;
use sp_runtime::Perquintill;
use sp_runtime::{Percent, Perquintill};

/// Logic for the author to get a portion of fees.
pub struct ToAuthor<R>(sp_std::marker::PhantomData<R>);
Expand Down Expand Up @@ -64,6 +64,7 @@ pub fn era_payout(
total_stakable: Balance,
max_annual_inflation: Perquintill,
period_fraction: Perquintill,
min_fraction_remainder: Percent,
auctioned_slots: u64,
) -> (Balance, Balance) {
use pallet_staking_reward_fn::compute_inflation;
Expand All @@ -86,8 +87,12 @@ pub fn era_payout(
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 min_remainder = min_fraction_remainder * max_payout;
let staking_payout =
((period_fraction * staking_inflation) * total_stakable).min(max_payout - min_remainder);
let other_remainder = max_payout - (min_remainder + staking_payout);

let rest = min_remainder + other_remainder;

let other_issuance = total_stakable.saturating_sub(total_staked);
if total_staked > other_issuance {
Expand Down Expand Up @@ -282,11 +287,56 @@ mod tests {
#[test]
fn era_payout_should_give_sensible_results() {
assert_eq!(
era_payout(75, 100, Perquintill::from_percent(10), Perquintill::one(), 0,),
era_payout(
75,
100,
Perquintill::from_percent(10),
Perquintill::one(),
Percent::zero(),
0
),
(10, 0)
);
assert_eq!(
era_payout(80, 100, Perquintill::from_percent(10), Perquintill::one(), 0,),
era_payout(
80,
100,
Perquintill::from_percent(10),
Perquintill::one(),
Percent::zero(),
0
),
(6, 4)
);
}

#[test]
fn era_payout_min_remainder_works() {
// minimum remainder is set to 50% of total payout for the era, so the validator payout
// takes a cut.
assert_eq!(
era_payout(
75,
100,
Perquintill::from_percent(10),
Perquintill::one(),
Percent::from_parts(50),
0
),
(5, 5)
);
// minimum remainder is 10%. the validator payout does not take a cut because the payout
// remainder (`payout_remainder = max_payout - validator_payout`) is larger than the set
// minimum remainder.
assert_eq!(
era_payout(
80,
100,
Perquintill::from_percent(10),
Perquintill::one(),
Percent::from_parts(10),
0
),
(6, 4)
);
}
Expand Down
2 changes: 2 additions & 0 deletions polkadot/runtime/kusama/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,7 @@ impl pallet_staking::EraPayout<Balance> for EraPayout {
fn era_payout(
total_staked: Balance,
_total_issuance: Balance,
min_fraction_remainder: Percent,
era_duration_millis: u64,
) -> (Balance, Balance) {
// all para-ids that are currently active.
Expand All @@ -661,6 +662,7 @@ impl pallet_staking::EraPayout<Balance> for EraPayout {
Nis::issuance().other,
MAX_ANNUAL_INFLATION,
Perquintill::from_rational(era_duration_millis, MILLISECONDS_PER_YEAR),
min_fraction_remainder,
auctioned_slots,
)
}
Expand Down
5 changes: 5 additions & 0 deletions polkadot/runtime/kusama/src/weights/pallet_staking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -793,4 +793,9 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}

// TODO(gpestana): run bench bot
fn set_min_treasury_fraction() -> Weight {
Weight::default()
}
}
2 changes: 2 additions & 0 deletions polkadot/runtime/polkadot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,7 @@ impl pallet_staking::EraPayout<Balance> for EraPayout {
fn era_payout(
total_staked: Balance,
total_issuance: Balance,
min_fraction_remainder: Percent,
era_duration_millis: u64,
) -> (Balance, Balance) {
// all para-ids that are not active.
Expand All @@ -579,6 +580,7 @@ impl pallet_staking::EraPayout<Balance> for EraPayout {
total_issuance,
MAX_ANNUAL_INFLATION,
Perquintill::from_rational(era_duration_millis, MILLISECONDS_PER_YEAR),
min_fraction_remainder,
auctioned_slots,
)
}
Expand Down
5 changes: 5 additions & 0 deletions polkadot/runtime/polkadot/src/weights/pallet_staking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -793,4 +793,9 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}

// TODO(gpestana): run bench bot
fn set_min_treasury_fraction() -> Weight {
Weight::default()
}
}
5 changes: 5 additions & 0 deletions polkadot/runtime/westend/src/weights/pallet_staking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -793,4 +793,9 @@ impl<T: frame_system::Config> pallet_staking::WeightInfo for WeightInfo<T> {
.saturating_add(Weight::from_parts(0, 0))
.saturating_add(T::DbWeight::get().writes(1))
}

// TODO(gpestana): run bench bot
fn set_min_treasury_fraction() -> Weight {
Weight::default()
}
}
7 changes: 7 additions & 0 deletions substrate/frame/staking/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,13 @@ benchmarks! {
assert_eq!(MinCommission::<T>::get(), Perbill::from_percent(100));
}

set_min_treasury_fraction {
let treasury_fraction = Percent::max_value();
}: _(RawOrigin::Root, treasury_fraction)
verify {
assert_eq!(MinRemainderPayout::<T>::get(), Percent::from_parts(100));
}

impl_benchmark_test_suite!(
Staking,
crate::mock::ExtBuilder::default().has_stakers(true),
Expand Down
23 changes: 19 additions & 4 deletions substrate/frame/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,13 @@
//! ```nocompile
//! remaining_payout = max_yearly_inflation * total_tokens / era_per_year - staker_payout
//! ```
//!
//! Note, however, that it is possible to set a minimum `remaining_payout` through the
//! `MinRemainderPayout` storage type. The `era_payout` implementor must ensure that the
//! `max_payout >= min_remainder_payout + staker_payout`. The excess should be taken from the
//! `staker_payout` to ensure that the `max_payout` per era is not exceeded and the
//! `min_remainder_payout` is respected.
//!
//! The remaining reward is send to the configurable end-point
//! [`Config::RewardRemainder`].
//!
Expand Down Expand Up @@ -310,7 +317,7 @@ use scale_info::TypeInfo;
use sp_runtime::{
curve::PiecewiseLinear,
traits::{AtLeast32BitUnsigned, Convert, Saturating, StaticLookup, Zero},
Perbill, Perquintill, Rounding, RuntimeDebug,
Perbill, Percent, Perquintill, Rounding, RuntimeDebug,
};
pub use sp_staking::StakerStatus;
use sp_staking::{
Expand Down Expand Up @@ -845,6 +852,7 @@ pub trait EraPayout<Balance> {
fn era_payout(
total_staked: Balance,
total_issuance: Balance,
min_fraction_remainder: Percent,
era_duration_millis: u64,
) -> (Balance, Balance);
}
Expand All @@ -853,6 +861,7 @@ impl<Balance: Default> EraPayout<Balance> for () {
fn era_payout(
_total_staked: Balance,
_total_issuance: Balance,
_min_fraction_remainder: Percent,
_era_duration_millis: u64,
) -> (Balance, Balance) {
(Default::default(), Default::default())
Expand All @@ -862,12 +871,15 @@ impl<Balance: Default> EraPayout<Balance> for () {
/// Adaptor to turn a `PiecewiseLinear` curve definition into an `EraPayout` impl, used for
/// backwards compatibility.
pub struct ConvertCurve<T>(sp_std::marker::PhantomData<T>);
impl<Balance: AtLeast32BitUnsigned + Clone, T: Get<&'static PiecewiseLinear<'static>>>
EraPayout<Balance> for ConvertCurve<T>
impl<Balance, T> EraPayout<Balance> for ConvertCurve<T>
where
Balance: AtLeast32BitUnsigned + Clone + Copy,
T: Get<&'static PiecewiseLinear<'static>>,
{
fn era_payout(
total_staked: Balance,
total_issuance: Balance,
min_fraction_remainder: Percent,
era_duration_millis: u64,
) -> (Balance, Balance) {
let (validator_payout, max_payout) = inflation::compute_total_payout(
Expand All @@ -877,7 +889,10 @@ impl<Balance: AtLeast32BitUnsigned + Clone, T: Get<&'static PiecewiseLinear<'sta
// Duration of era; more than u64::MAX is rewarded as u64::MAX.
era_duration_millis,
);
let rest = max_payout.saturating_sub(validator_payout.clone());
let min_remainder = min_fraction_remainder * max_payout;
let validator_payout = validator_payout.min(max_payout - min_remainder);
let other_remainder = max_payout - (min_remainder + validator_payout);
let rest = min_remainder + other_remainder;
(validator_payout, rest)
}
}
Expand Down
10 changes: 10 additions & 0 deletions substrate/frame/staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ parameter_types! {

parameter_types! {
pub static RewardRemainderUnbalanced: u128 = 0;
pub static MinRewardRemainder: Percent = Percent::default();
}

pub struct RewardRemainderMock;
Expand Down Expand Up @@ -345,6 +346,7 @@ pub struct ExtBuilder {
initialize_first_session: bool,
pub min_nominator_bond: Balance,
min_validator_bond: Balance,
min_remainder: Percent,
balance_factor: Balance,
status: BTreeMap<AccountId, StakerStatus<AccountId>>,
stakes: BTreeMap<AccountId, Balance>,
Expand All @@ -363,6 +365,7 @@ impl Default for ExtBuilder {
initialize_first_session: true,
min_nominator_bond: ExistentialDeposit::get(),
min_validator_bond: ExistentialDeposit::get(),
min_remainder: Percent::default(),
status: Default::default(),
stakes: Default::default(),
stakers: Default::default(),
Expand Down Expand Up @@ -445,6 +448,10 @@ impl ExtBuilder {
self.balance_factor = factor;
self
}
pub fn min_remainder(mut self, fraction: u8) -> Self {
self.min_remainder = Percent::from_parts(fraction);
self
}
fn build(self) -> sp_io::TestExternalities {
sp_tracing::try_init_simple();
let mut storage = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
Expand Down Expand Up @@ -532,6 +539,7 @@ impl ExtBuilder {
slash_reward_fraction: Perbill::from_percent(10),
min_nominator_bond: self.min_nominator_bond,
min_validator_bond: self.min_validator_bond,
min_remainder: self.min_remainder,
..Default::default()
}
.assimilate_storage(&mut storage);
Expand Down Expand Up @@ -661,6 +669,7 @@ pub(crate) fn current_total_payout_for_duration(duration: u64) -> Balance {
let (payout, _rest) = <Test as Config>::EraPayout::era_payout(
Staking::eras_total_stake(active_era()),
Balances::total_issuance(),
MinRewardRemainder::get(),
duration,
);
assert!(payout > 0);
Expand All @@ -671,6 +680,7 @@ pub(crate) fn maximum_payout_for_duration(duration: u64) -> Balance {
let (payout, rest) = <Test as Config>::EraPayout::era_payout(
Staking::eras_total_stake(active_era()),
Balances::total_issuance(),
MinRewardRemainder::get(),
duration,
);
payout + rest
Expand Down
9 changes: 7 additions & 2 deletions substrate/frame/staking/src/pallet/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,8 +457,13 @@ impl<T: Config> Pallet<T> {
let era_duration = (now_as_millis_u64 - active_era_start).saturated_into::<u64>();
let staked = Self::eras_total_stake(&active_era.index);
let issuance = T::Currency::total_issuance();
let (validator_payout, remainder) =
T::EraPayout::era_payout(staked, issuance, era_duration);

let (validator_payout, remainder) = T::EraPayout::era_payout(
staked,
issuance,
MinRemainderPayout::<T>::get(),
era_duration,
);

Self::deposit_event(Event::<T>::EraPaid {
era_index: active_era.index,
Expand Down
18 changes: 18 additions & 0 deletions substrate/frame/staking/src/pallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,12 @@ pub mod pallet {
#[pallet::getter(fn force_era)]
pub type ForceEra<T> = StorageValue<_, Forcing, ValueQuery>;

/// Minimum of the reward remainder per era, i.e. the percentage of the era inflation that
/// is not used for rewards.
/// See [Era payout](./index.html#era-payout).
#[pallet::storage]
pub type MinRemainderPayout<T> = StorageValue<_, Percent, ValueQuery>;

/// The percentage of the slash that is distributed to reporters.
///
/// The rest of the slashed value is handled by the `Slash`.
Expand Down Expand Up @@ -591,6 +597,7 @@ pub mod pallet {
pub canceled_payout: BalanceOf<T>,
pub stakers:
Vec<(T::AccountId, T::AccountId, BalanceOf<T>, crate::StakerStatus<T::AccountId>)>,
pub min_remainder: Percent,
pub min_nominator_bond: BalanceOf<T>,
pub min_validator_bond: BalanceOf<T>,
pub max_validator_count: Option<u32>,
Expand All @@ -606,6 +613,7 @@ pub mod pallet {
ForceEra::<T>::put(self.force_era);
CanceledSlashPayout::<T>::put(self.canceled_payout);
SlashRewardFraction::<T>::put(self.slash_reward_fraction);
MinRemainderPayout::<T>::put(self.min_remainder);
MinNominatorBond::<T>::put(self.min_nominator_bond);
MinValidatorBond::<T>::put(self.min_validator_bond);
if let Some(x) = self.max_validator_count {
Expand Down Expand Up @@ -1780,6 +1788,16 @@ pub mod pallet {
MinCommission::<T>::put(new);
Ok(())
}

/// Sets the minimum fraction of the era's inflation that should be minted directly into
/// the treasury.
#[pallet::call_index(26)]
#[pallet::weight(T::WeightInfo::set_min_treasury_fraction())]
pub fn set_min_treasury_fraction(origin: OriginFor<T>, new: Percent) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
MinRemainderPayout::<T>::put(new);
Ok(())
}
}
}

Expand Down
Loading