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

This PR implements an (optional) cap of the era inflation that is
allocated to staking rewards. The remaining is minted directly into the
[`RewardRemainder`](https://github.com/paritytech/polkadot-sdk/blob/fb0fd3e62445eb2dee2b2456a0c8574d1ecdcc73/substrate/frame/staking/src/pallet/mod.rs#L160)
account, which is the treasury pot account in Polkadot and Kusama.

The staking pallet now has a percent storage item, `MaxStakersRewards`,
which defines the max percentage of the era inflation that should be
allocated to staking rewards. The remaining era inflation (i.e.
`remaining = max_era_payout - staking_payout.min(staking_payout *
MaxStakersRewards))` is minted directly into the treasury.

The `MaxStakersRewards` can be set by a privileged origin through the
`set_staking_configs` extrinsic.

**To finish**
- [x] run benchmarks for westend-runtime

Replaces https://github.com/paritytech/polkadot-sdk/pull/1483
Closes https://github.com/paritytech/polkadot-sdk/issues/403

---------

Co-authored-by: command-bot <>
This commit is contained in:
Gonçalo Pestana
2024-02-15 20:13:35 +01:00
committed by GitHub
parent 5fc7622cb3
commit fde44474e4
9 changed files with 427 additions and 300 deletions
+84 -4
View File
@@ -55,6 +55,7 @@ fn set_staking_configs_works() {
ConfigOp::Set(10),
ConfigOp::Set(20),
ConfigOp::Set(Percent::from_percent(75)),
ConfigOp::Set(Zero::zero()),
ConfigOp::Set(Zero::zero())
));
assert_eq!(MinNominatorBond::<Test>::get(), 1_500);
@@ -63,6 +64,7 @@ fn set_staking_configs_works() {
assert_eq!(MaxValidatorsCount::<Test>::get(), Some(20));
assert_eq!(ChillThreshold::<Test>::get(), Some(Percent::from_percent(75)));
assert_eq!(MinCommission::<Test>::get(), Perbill::from_percent(0));
assert_eq!(MaxStakedRewards::<Test>::get(), Some(Percent::from_percent(0)));
// noop does nothing
assert_storage_noop!(assert_ok!(Staking::set_staking_configs(
@@ -72,6 +74,7 @@ fn set_staking_configs_works() {
ConfigOp::Noop,
ConfigOp::Noop,
ConfigOp::Noop,
ConfigOp::Noop,
ConfigOp::Noop
)));
@@ -83,6 +86,7 @@ fn set_staking_configs_works() {
ConfigOp::Remove,
ConfigOp::Remove,
ConfigOp::Remove,
ConfigOp::Remove,
ConfigOp::Remove
));
assert_eq!(MinNominatorBond::<Test>::get(), 0);
@@ -91,6 +95,7 @@ fn set_staking_configs_works() {
assert_eq!(MaxValidatorsCount::<Test>::get(), None);
assert_eq!(ChillThreshold::<Test>::get(), None);
assert_eq!(MinCommission::<Test>::get(), Perbill::from_percent(0));
assert_eq!(MaxStakedRewards::<Test>::get(), None);
});
}
@@ -1739,6 +1744,74 @@ fn rebond_emits_right_value_in_event() {
});
}
#[test]
fn max_staked_rewards_default_works() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(<MaxStakedRewards<Test>>::get(), None);
let default_stakers_payout = current_total_payout_for_duration(reward_time_per_era());
assert!(default_stakers_payout > 0);
start_active_era(1);
// the final stakers reward is the same as the reward before applied the cap.
assert_eq!(ErasValidatorReward::<Test>::get(0).unwrap(), default_stakers_payout);
// which is the same behaviour if the `MaxStakedRewards` is set to 100%.
<MaxStakedRewards<Test>>::set(Some(Percent::from_parts(100)));
let default_stakers_payout = current_total_payout_for_duration(reward_time_per_era());
assert_eq!(ErasValidatorReward::<Test>::get(0).unwrap(), default_stakers_payout);
})
}
#[test]
fn max_staked_rewards_works() {
ExtBuilder::default().nominate(true).build_and_execute(|| {
let max_staked_rewards = 10;
// sets new max staked rewards through set_staking_configs.
assert_ok!(Staking::set_staking_configs(
RuntimeOrigin::root(),
ConfigOp::Noop,
ConfigOp::Noop,
ConfigOp::Noop,
ConfigOp::Noop,
ConfigOp::Noop,
ConfigOp::Noop,
ConfigOp::Set(Percent::from_percent(max_staked_rewards)),
));
assert_eq!(<MaxStakedRewards<Test>>::get(), Some(Percent::from_percent(10)));
// check validators account state.
assert_eq!(Session::validators().len(), 2);
assert!(Session::validators().contains(&11) & Session::validators().contains(&21));
// balance of the mock treasury account is 0
assert_eq!(RewardRemainderUnbalanced::get(), 0);
let max_stakers_payout = current_total_payout_for_duration(reward_time_per_era());
start_active_era(1);
let treasury_payout = RewardRemainderUnbalanced::get();
let validators_payout = ErasValidatorReward::<Test>::get(0).unwrap();
let total_payout = treasury_payout + validators_payout;
// max stakers payout (without max staked rewards cap applied) is larger than the final
// validator rewards. The final payment and remainder should be adjusted by redestributing
// the era inflation to apply the cap...
assert!(max_stakers_payout > validators_payout);
// .. which means that the final validator payout is 10% of the total payout..
assert_eq!(validators_payout, Percent::from_percent(max_staked_rewards) * total_payout);
// .. and the remainder 90% goes to the treasury.
assert_eq!(
treasury_payout,
Percent::from_percent(100 - max_staked_rewards) * (treasury_payout + validators_payout)
);
})
}
#[test]
fn reward_to_stake_works() {
ExtBuilder::default()
@@ -5543,7 +5616,8 @@ fn chill_other_works() {
ConfigOp::Remove,
ConfigOp::Remove,
ConfigOp::Remove,
ConfigOp::Remove
ConfigOp::Remove,
ConfigOp::Noop,
));
// Still can't chill these users
@@ -5564,7 +5638,8 @@ fn chill_other_works() {
ConfigOp::Set(10),
ConfigOp::Set(10),
ConfigOp::Noop,
ConfigOp::Noop
ConfigOp::Noop,
ConfigOp::Noop,
));
// Still can't chill these users
@@ -5585,7 +5660,8 @@ fn chill_other_works() {
ConfigOp::Remove,
ConfigOp::Remove,
ConfigOp::Noop,
ConfigOp::Noop
ConfigOp::Noop,
ConfigOp::Noop,
));
// Still can't chill these users
@@ -5606,7 +5682,8 @@ fn chill_other_works() {
ConfigOp::Set(10),
ConfigOp::Set(10),
ConfigOp::Set(Percent::from_percent(75)),
ConfigOp::Noop
ConfigOp::Noop,
ConfigOp::Noop,
));
// 16 people total because tests start with 2 active one
@@ -5652,6 +5729,7 @@ fn capped_stakers_works() {
ConfigOp::Set(max),
ConfigOp::Remove,
ConfigOp::Remove,
ConfigOp::Noop,
));
// can create `max - validator_count` validators
@@ -5722,6 +5800,7 @@ fn capped_stakers_works() {
ConfigOp::Remove,
ConfigOp::Noop,
ConfigOp::Noop,
ConfigOp::Noop,
));
assert_ok!(Staking::nominate(RuntimeOrigin::signed(last_nominator), vec![1]));
assert_ok!(Staking::validate(
@@ -5757,6 +5836,7 @@ fn min_commission_works() {
ConfigOp::Remove,
ConfigOp::Remove,
ConfigOp::Set(Perbill::from_percent(10)),
ConfigOp::Noop,
));
// can't make it less than 10 now