Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Remove pre-simple-payout code from staking #6253

Merged
3 commits merged into from
Jun 5, 2020
Merged
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
252 changes: 32 additions & 220 deletions frame/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -928,19 +928,20 @@ impl Default for Forcing {
fn default() -> Self { Forcing::NotForcing }
}

// A value placed in storage that represents the current version of the Staking storage.
// This value is used by the `on_runtime_upgrade` logic to determine whether we run
// storage migration logic. This should match directly with the semantic versions of the Rust crate.
// A value placed in storage that represents the current version of the Staking storage. This value
// is used by the `on_runtime_upgrade` logic to determine whether we run storage migration logic.
// This should match directly with the semantic versions of the Rust crate.
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug)]
enum Releases {
V1_0_0Ancient,
V2_0_0,
V3_0_0,
V4_0_0,
}

impl Default for Releases {
fn default() -> Self {
Releases::V3_0_0
Releases::V4_0_0
}
}

Expand Down Expand Up @@ -1126,10 +1127,7 @@ decl_storage! {
/// Storage version of the pallet.
///
/// This is set to v3.0.0 for new networks.
StorageVersion build(|_: &GenesisConfig<T>| Releases::V3_0_0): Releases;

/// The era where we migrated from Lazy Payouts to Simple Payouts
MigrateEra: Option<EraIndex>;
StorageVersion build(|_: &GenesisConfig<T>| Releases::V4_0_0): Releases;
}
add_extra_genesis {
config(stakers):
Expand Down Expand Up @@ -1277,6 +1275,28 @@ decl_module! {

fn deposit_event() = default;

fn on_runtime_upgrade() -> Weight {
#[allow(dead_code)]
mod inner {
pub struct Module<T>(sp_std::marker::PhantomData<T>);
frame_support::decl_storage! {
trait Store for Module<T: super::Trait> as Staking {
pub MigrateEra: Option<super::EraIndex>;
}
}
}

if let Releases::V3_0_0 = StorageVersion::get() {
StorageVersion::put(Releases::V4_0_0);
inner::MigrateEra::kill();

T::DbWeight::get().reads_writes(1, 1)
} else {
T::DbWeight::get().reads(1)
}
}


/// sets `ElectionStatus` to `Open(now)` where `now` is the block number at which the
/// election window has opened, if we are at the last session and less blocks than
/// `T::ElectionLookahead` is remaining until the next new session schedule. The offchain
Expand Down Expand Up @@ -1901,69 +1921,6 @@ decl_module! {
<Self as Store>::UnappliedSlashes::insert(&era, &unapplied);
}

/// **This extrinsic will be removed after `MigrationEra + HistoryDepth` has passed, giving
/// opportunity for users to claim all rewards before moving to Simple Payouts. After this
/// time, you should use `payout_stakers` instead.**
///
/// Make one nominator's payout for one era.
///
/// - `who` is the controller account of the nominator to pay out.
/// - `era` may not be lower than one following the most recently paid era. If it is higher,
/// then it indicates an instruction to skip the payout of all previous eras.
/// - `validators` is the list of all validators that `who` had exposure to during `era`,
/// alongside the index of `who` in the clipped exposure of the validator.
/// I.e. each element is a tuple of
/// `(validator, index of `who` in clipped exposure of validator)`.
/// If it is incomplete, then less than the full reward will be paid out.
/// It must not exceed `MAX_NOMINATIONS`.
///
/// WARNING: once an era is payed for a validator such validator can't claim the payout of
/// previous era.
///
/// WARNING: Incorrect arguments here can result in loss of payout. Be very careful.
///
/// # <weight>
/// - Number of storage read of `O(validators)`; `validators` is the argument of the call,
/// and is bounded by `MAX_NOMINATIONS`.
/// - Each storage read is `O(N)` size and decode complexity; `N` is the maximum
/// nominations that can be given to a single validator.
/// - Computation complexity: `O(MAX_NOMINATIONS * logN)`; `MAX_NOMINATIONS` is the
/// maximum number of validators that may be nominated by a single nominator, it is
/// bounded only economically (all nominators are required to place a minimum stake).
/// # </weight>
#[weight = 500_000_000]
fn payout_nominator(origin, era: EraIndex, validators: Vec<(T::AccountId, u32)>)
-> DispatchResult
{
let ctrl = ensure_signed(origin)?;
Self::do_payout_nominator(ctrl, era, validators)
}

/// **This extrinsic will be removed after `MigrationEra + HistoryDepth` has passed, giving
/// opportunity for users to claim all rewards before moving to Simple Payouts. After this
/// time, you should use `payout_stakers` instead.**
///
/// Make one validator's payout for one era.
///
/// - `who` is the controller account of the validator to pay out.
/// - `era` may not be lower than one following the most recently paid era. If it is higher,
/// then it indicates an instruction to skip the payout of all previous eras.
///
/// WARNING: once an era is payed for a validator such validator can't claim the payout of
/// previous era.
///
/// WARNING: Incorrect arguments here can result in loss of payout. Be very careful.
///
/// # <weight>
/// - Time complexity: O(1).
/// - Contains a limited number of reads and writes.
/// # </weight>
#[weight = 500_000_000]
fn payout_validator(origin, era: EraIndex) -> DispatchResult {
let ctrl = ensure_signed(origin)?;
Self::do_payout_validator(ctrl, era)
}

/// Pay out all the stakers behind a single validator for a single era.
///
/// - `validator_stash` is the stash account of the validator. Their nominators, up to
Expand All @@ -1982,16 +1939,15 @@ decl_module! {
/// N is the Number of payouts for the validator (including the validator)
/// Base Weight: 110 + 54.2 * N µs (Median Slopes)
/// DB Weight:
/// - Read: EraElectionStatus, CurrentEra, HistoryDepth, MigrateEra, ErasValidatorReward,
/// - Read: EraElectionStatus, CurrentEra, HistoryDepth, ErasValidatorReward,
/// ErasStakersClipped, ErasRewardPoints, ErasValidatorPrefs (8 items)
/// - Read Each: Bonded, Ledger, Payee, Locks, System Account (5 items)
/// - Write Each: System Account, Locks, Ledger (3 items)
// TODO: Remove read on Migrate Era
/// # </weight>
#[weight =
110 * WEIGHT_PER_MICROS
+ 54 * WEIGHT_PER_MICROS * Weight::from(T::MaxNominatorRewardedPerValidator::get())
+ T::DbWeight::get().reads(8)
+ T::DbWeight::get().reads(7)
+ T::DbWeight::get().reads(5) * Weight::from(T::MaxNominatorRewardedPerValidator::get() + 1)
+ T::DbWeight::get().writes(3) * Weight::from(T::MaxNominatorRewardedPerValidator::get() + 1)
]
Expand Down Expand Up @@ -2273,143 +2229,6 @@ impl<T: Trait> Module<T> {
<SnapshotNominators<T>>::kill();
}

fn do_payout_nominator(ctrl: T::AccountId, era: EraIndex, validators: Vec<(T::AccountId, u32)>)
-> DispatchResult
{
// validators len must not exceed `MAX_NOMINATIONS` to avoid querying more validator
// exposure than necessary.
if validators.len() > MAX_NOMINATIONS {
return Err(Error::<T>::InvalidNumberOfNominations.into());
}
// If migrate_era is not populated, then you should use `payout_stakers`
let migrate_era = MigrateEra::get().ok_or(Error::<T>::InvalidEraToReward)?;
// This payout mechanism will only work for eras before the migration.
// Subsequent payouts should use `payout_stakers`.
ensure!(era < migrate_era, Error::<T>::InvalidEraToReward);
let current_era = CurrentEra::get().ok_or(Error::<T>::InvalidEraToReward)?;
ensure!(era <= current_era, Error::<T>::InvalidEraToReward);
let history_depth = Self::history_depth();
ensure!(era >= current_era.saturating_sub(history_depth), Error::<T>::InvalidEraToReward);

// Note: if era has no reward to be claimed, era may be future. better not to update
// `nominator_ledger.last_reward` in this case.
let era_payout = <ErasValidatorReward<T>>::get(&era)
.ok_or_else(|| Error::<T>::InvalidEraToReward)?;

let mut nominator_ledger = <Ledger<T>>::get(&ctrl).ok_or_else(|| Error::<T>::NotController)?;

ensure!(
Self::era_election_status().is_closed() || Self::payee(&nominator_ledger.stash) != RewardDestination::Staked,
Error::<T>::CallNotAllowed,
);

nominator_ledger.claimed_rewards.retain(|&x| x >= current_era.saturating_sub(history_depth));
match nominator_ledger.claimed_rewards.binary_search(&era) {
Ok(_) => Err(Error::<T>::AlreadyClaimed)?,
Err(pos) => nominator_ledger.claimed_rewards.insert(pos, era),
}

<Ledger<T>>::insert(&ctrl, &nominator_ledger);

let mut reward = Perbill::zero();
let era_reward_points = <ErasRewardPoints<T>>::get(&era);

for (validator, nominator_index) in validators.into_iter() {
let commission = Self::eras_validator_prefs(&era, &validator).commission;
let validator_exposure = <ErasStakersClipped<T>>::get(&era, &validator);

if let Some(nominator_exposure) = validator_exposure.others
.get(nominator_index as usize)
{
if nominator_exposure.who != nominator_ledger.stash {
continue;
}

let nominator_exposure_part = Perbill::from_rational_approximation(
nominator_exposure.value,
validator_exposure.total,
);
let validator_point = era_reward_points.individual.get(&validator)
.map(|points| *points)
.unwrap_or_else(|| Zero::zero());
let validator_point_part = Perbill::from_rational_approximation(
validator_point,
era_reward_points.total,
);
reward = reward.saturating_add(
validator_point_part
.saturating_mul(Perbill::one().saturating_sub(commission))
.saturating_mul(nominator_exposure_part)
);
}
}

if let Some(imbalance) = Self::make_payout(&nominator_ledger.stash, reward * era_payout) {
Self::deposit_event(RawEvent::Reward(ctrl, imbalance.peek()));
}

Ok(())
}

fn do_payout_validator(ctrl: T::AccountId, era: EraIndex) -> DispatchResult {
// If migrate_era is not populated, then you should use `payout_stakers`
let migrate_era = MigrateEra::get().ok_or(Error::<T>::InvalidEraToReward)?;
// This payout mechanism will only work for eras before the migration.
// Subsequent payouts should use `payout_stakers`.
ensure!(era < migrate_era, Error::<T>::InvalidEraToReward);
let current_era = CurrentEra::get().ok_or(Error::<T>::InvalidEraToReward)?;
ensure!(era <= current_era, Error::<T>::InvalidEraToReward);
let history_depth = Self::history_depth();
ensure!(era >= current_era.saturating_sub(history_depth), Error::<T>::InvalidEraToReward);

// Note: if era has no reward to be claimed, era may be future. better not to update
// `ledger.last_reward` in this case.
let era_payout = <ErasValidatorReward<T>>::get(&era)
.ok_or_else(|| Error::<T>::InvalidEraToReward)?;

let mut ledger = <Ledger<T>>::get(&ctrl).ok_or_else(|| Error::<T>::NotController)?;

ensure!(
Self::era_election_status().is_closed() || Self::payee(&ledger.stash) != RewardDestination::Staked,
Error::<T>::CallNotAllowed,
);

ledger.claimed_rewards.retain(|&x| x >= current_era.saturating_sub(history_depth));
match ledger.claimed_rewards.binary_search(&era) {
Ok(_) => Err(Error::<T>::AlreadyClaimed)?,
Err(pos) => ledger.claimed_rewards.insert(pos, era),
}

<Ledger<T>>::insert(&ctrl, &ledger);

let era_reward_points = <ErasRewardPoints<T>>::get(&era);
let commission = Self::eras_validator_prefs(&era, &ledger.stash).commission;
let exposure = <ErasStakersClipped<T>>::get(&era, &ledger.stash);

let exposure_part = Perbill::from_rational_approximation(
exposure.own,
exposure.total,
);
let validator_point = era_reward_points.individual.get(&ledger.stash)
.map(|points| *points)
.unwrap_or_else(|| Zero::zero());
let validator_point_part = Perbill::from_rational_approximation(
validator_point,
era_reward_points.total,
);
let reward = validator_point_part.saturating_mul(
commission.saturating_add(
Perbill::one().saturating_sub(commission).saturating_mul(exposure_part)
)
);

if let Some(imbalance) = Self::make_payout(&ledger.stash, reward * era_payout) {
Self::deposit_event(RawEvent::Reward(ctrl, imbalance.peek()));
}

Ok(())
}

fn do_payout_stakers(
validator_stash: T::AccountId,
era: EraIndex,
Expand All @@ -2420,13 +2239,6 @@ impl<T: Trait> Module<T> {
let history_depth = Self::history_depth();
ensure!(era >= current_era.saturating_sub(history_depth), Error::<T>::InvalidEraToReward);

// If there was no migration, then this function is always valid.
if let Some(migrate_era) = MigrateEra::get() {
// This payout mechanism will only work for eras on and after the migration.
// Payouts before then should use `payout_nominator`/`payout_validator`.
ensure!(migrate_era <= era, Error::<T>::InvalidEraToReward);
}

// Note: if era has no reward to be claimed, era may be future. better not to update
// `ledger.claimed_rewards` in this case.
let era_payout = <ErasValidatorReward<T>>::get(&era)
Expand Down Expand Up @@ -3148,11 +2960,11 @@ impl<T: Trait> Module<T> {
/// - after a `withdraw_unbond()` call that frees all of a stash's bonded balance.
/// - through `reap_stash()` if the balance has fallen to zero (through slashing).
fn kill_stash(stash: &T::AccountId, num_slashing_spans: u32) -> DispatchResult {
let controller = Bonded::<T>::get(stash).ok_or(Error::<T>::NotStash)?;
let controller = <Bonded<T>>::get(stash).ok_or(Error::<T>::NotStash)?;

slashing::clear_stash_metadata::<T>(stash, num_slashing_spans)?;

Bonded::<T>::remove(stash);
<Bonded<T>>::remove(stash);
<Ledger<T>>::remove(&controller);

<Payee<T>>::remove(stash);
Expand Down
34 changes: 1 addition & 33 deletions frame/staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

//! Test utilities

use std::{collections::{HashSet, HashMap}, cell::RefCell};
use std::{collections::HashSet, cell::RefCell};
use sp_runtime::Perbill;
use sp_runtime::curve::PiecewiseLinear;
use sp_runtime::traits::{IdentityLookup, Convert, SaturatedConversion, Zero};
Expand Down Expand Up @@ -980,38 +980,6 @@ pub(crate) fn prepare_submission_with(
(compact, winners, score)
}

/// Make all validator and nominator request their payment
pub(crate) fn make_all_reward_payment_before_migration(era: EraIndex) {
let validators_with_reward = ErasRewardPoints::<Test>::get(era).individual.keys()
.cloned()
.collect::<Vec<_>>();

// reward nominators
let mut nominator_controllers = HashMap::new();
for validator in Staking::eras_reward_points(era).individual.keys() {
let validator_exposure = Staking::eras_stakers_clipped(era, validator);
for (nom_index, nom) in validator_exposure.others.iter().enumerate() {
if let Some(nom_ctrl) = Staking::bonded(nom.who) {
nominator_controllers.entry(nom_ctrl)
.or_insert(vec![])
.push((validator.clone(), nom_index as u32));
}
}
}
for (nominator_controller, validators_with_nom_index) in nominator_controllers {
assert_ok!(Staking::payout_nominator(
Origin::signed(nominator_controller),
era,
validators_with_nom_index,
));
}

// reward validators
for validator_controller in validators_with_reward.iter().filter_map(Staking::bonded) {
assert_ok!(Staking::payout_validator(Origin::signed(validator_controller), era));
}
}

/// Make all validator and nominator request their payment
pub(crate) fn make_all_reward_payment(era: EraIndex) {
let validators_with_reward = ErasRewardPoints::<Test>::get(era).individual.keys()
Expand Down
Loading