mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 23:21:02 +00:00
Simple Staking Payouts (#5406)
* Simple Payouts * explicit test for out of order claiming * Add `payout_all` benchmark * Fix merge * add docs * change event to controller * Fix timestamp test warnings * Revert "change event to controller" This reverts commit 5d4a97832d47fe1273602d5410774d5421940c4e. * Update Reward event doc * Add "non-production" test * add unlock chunk to test * fix merge * End payout early if no reward points * payout_validator -> payout_stakers * bring back payout nominator/validator, but limit their use to before migration era * Add test for before migration * New payout works for the era that we migrate * Fix logic, check that migration era works * Migrate Era tests (copypasta) * Move comment * Add mock back to external functions * Fixes based on review from gui * Update Cargo.lock * Update Cargo.lock * small docs update Co-authored-by: joepetrowski <joe@parity.io>
This commit is contained in:
Generated
+1
@@ -4437,6 +4437,7 @@ dependencies = [
|
|||||||
"sp-runtime",
|
"sp-runtime",
|
||||||
"sp-staking",
|
"sp-staking",
|
||||||
"sp-std",
|
"sp-std",
|
||||||
|
"sp-storage",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
"substrate-test-utils",
|
"substrate-test-utils",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ rand_chacha = { version = "0.2", default-features = false, optional = true }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" }
|
sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" }
|
||||||
|
sp-storage = { version = "2.0.0-alpha.5", path = "../../primitives/storage" }
|
||||||
pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" }
|
pallet-balances = { version = "2.0.0-alpha.5", path = "../balances" }
|
||||||
pallet-timestamp = { version = "2.0.0-alpha.5", path = "../timestamp" }
|
pallet-timestamp = { version = "2.0.0-alpha.5", path = "../timestamp" }
|
||||||
pallet-staking-reward-curve = { version = "2.0.0-alpha.5", path = "../staking/reward-curve" }
|
pallet-staking-reward-curve = { version = "2.0.0-alpha.5", path = "../staking/reward-curve" }
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use super::*;
|
|||||||
|
|
||||||
use rand_chacha::{rand_core::{RngCore, SeedableRng}, ChaChaRng};
|
use rand_chacha::{rand_core::{RngCore, SeedableRng}, ChaChaRng};
|
||||||
|
|
||||||
use sp_runtime::traits::One;
|
use sp_runtime::traits::{Dispatchable, One};
|
||||||
use sp_io::hashing::blake2_256;
|
use sp_io::hashing::blake2_256;
|
||||||
|
|
||||||
use frame_system::RawOrigin;
|
use frame_system::RawOrigin;
|
||||||
@@ -147,61 +147,6 @@ pub fn create_validator_with_nominators<T: Trait>(n: u32, upper_bound: u32) -> R
|
|||||||
Ok(v_controller)
|
Ok(v_controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function generates one nominator nominating v validators.
|
|
||||||
// It starts an era and creates pending payouts.
|
|
||||||
pub fn create_nominator_with_validators<T: Trait>(v: u32) -> Result<(T::AccountId, Vec<T::AccountId>), &'static str> {
|
|
||||||
let mut validators = Vec::new();
|
|
||||||
let mut points_total = 0;
|
|
||||||
let mut points_individual = Vec::new();
|
|
||||||
|
|
||||||
MinimumValidatorCount::put(0);
|
|
||||||
|
|
||||||
// Create v validators
|
|
||||||
let mut validator_lookups = Vec::new();
|
|
||||||
for i in 0 .. v {
|
|
||||||
let (v_stash, v_controller) = create_stash_controller::<T>(i)?;
|
|
||||||
let validator_prefs = ValidatorPrefs {
|
|
||||||
commission: Perbill::from_percent(50),
|
|
||||||
};
|
|
||||||
Staking::<T>::validate(RawOrigin::Signed(v_controller.clone()).into(), validator_prefs)?;
|
|
||||||
let stash_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(v_stash.clone());
|
|
||||||
|
|
||||||
points_total += 10;
|
|
||||||
points_individual.push((v_stash.clone(), 10));
|
|
||||||
validator_lookups.push(stash_lookup);
|
|
||||||
// Add to the list if it is less than the number we want the nominator to have
|
|
||||||
if validators.len() < v as usize {
|
|
||||||
validators.push(v_stash.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a nominator
|
|
||||||
let (_n_stash, n_controller) = create_stash_controller::<T>(u32::max_value())?;
|
|
||||||
Staking::<T>::nominate(RawOrigin::Signed(n_controller.clone()).into(), validator_lookups)?;
|
|
||||||
|
|
||||||
ValidatorCount::put(v);
|
|
||||||
|
|
||||||
// Start a new Era
|
|
||||||
let new_validators = Staking::<T>::new_era(SessionIndex::one()).unwrap();
|
|
||||||
|
|
||||||
assert!(new_validators.len() == v as usize);
|
|
||||||
|
|
||||||
// Give Era Points
|
|
||||||
let reward = EraRewardPoints::<T::AccountId> {
|
|
||||||
total: points_total,
|
|
||||||
individual: points_individual.into_iter().collect(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let current_era = CurrentEra::get().unwrap();
|
|
||||||
ErasRewardPoints::<T>::insert(current_era, reward);
|
|
||||||
|
|
||||||
// Create reward pool
|
|
||||||
let total_payout = T::Currency::minimum_balance() * 1000.into();
|
|
||||||
<ErasValidatorReward<T>>::insert(current_era, total_payout);
|
|
||||||
|
|
||||||
Ok((n_controller, validators))
|
|
||||||
}
|
|
||||||
|
|
||||||
benchmarks! {
|
benchmarks! {
|
||||||
_{
|
_{
|
||||||
// User account seed
|
// User account seed
|
||||||
@@ -306,18 +251,12 @@ benchmarks! {
|
|||||||
let slash_indices: Vec<u32> = (0 .. s).collect();
|
let slash_indices: Vec<u32> = (0 .. s).collect();
|
||||||
}: _(RawOrigin::Root, era, slash_indices)
|
}: _(RawOrigin::Root, era, slash_indices)
|
||||||
|
|
||||||
payout_validator {
|
payout_stakers {
|
||||||
let n in 1 .. MAX_NOMINATIONS as u32;
|
let n in 1 .. MAX_NOMINATIONS as u32;
|
||||||
let validator = create_validator_with_nominators::<T>(n, MAX_NOMINATIONS as u32)?;
|
let validator = create_validator_with_nominators::<T>(n, MAX_NOMINATIONS as u32)?;
|
||||||
let current_era = CurrentEra::get().unwrap();
|
let current_era = CurrentEra::get().unwrap();
|
||||||
}: _(RawOrigin::Signed(validator), current_era)
|
let caller = account("caller", n, SEED);
|
||||||
|
}: _(RawOrigin::Signed(caller), validator, current_era)
|
||||||
payout_nominator {
|
|
||||||
let v in 1 .. MAX_NOMINATIONS as u32;
|
|
||||||
let (nominator, validators) = create_nominator_with_validators::<T>(v)?;
|
|
||||||
let current_era = CurrentEra::get().unwrap();
|
|
||||||
let find_nominator = validators.into_iter().map(|x| (x, 0)).collect();
|
|
||||||
}: _(RawOrigin::Signed(nominator), current_era, find_nominator)
|
|
||||||
|
|
||||||
rebond {
|
rebond {
|
||||||
let l in 1 .. 1000;
|
let l in 1 .. 1000;
|
||||||
@@ -386,6 +325,45 @@ benchmarks! {
|
|||||||
&mut NegativeImbalanceOf::<T>::zero()
|
&mut NegativeImbalanceOf::<T>::zero()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
payout_all {
|
||||||
|
let v in 1 .. 10;
|
||||||
|
let n in 1 .. 100;
|
||||||
|
MinimumValidatorCount::put(0);
|
||||||
|
create_validators_with_nominators_for_era::<T>(v, n)?;
|
||||||
|
// Start a new Era
|
||||||
|
let new_validators = Staking::<T>::new_era(SessionIndex::one()).unwrap();
|
||||||
|
assert!(new_validators.len() == v as usize);
|
||||||
|
|
||||||
|
let current_era = CurrentEra::get().unwrap();
|
||||||
|
let mut points_total = 0;
|
||||||
|
let mut points_individual = Vec::new();
|
||||||
|
let mut payout_calls = Vec::new();
|
||||||
|
|
||||||
|
for validator in new_validators.iter() {
|
||||||
|
points_total += 10;
|
||||||
|
points_individual.push((validator.clone(), 10));
|
||||||
|
payout_calls.push(Call::<T>::payout_stakers(validator.clone(), current_era))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give Era Points
|
||||||
|
let reward = EraRewardPoints::<T::AccountId> {
|
||||||
|
total: points_total,
|
||||||
|
individual: points_individual.into_iter().collect(),
|
||||||
|
};
|
||||||
|
|
||||||
|
ErasRewardPoints::<T>::insert(current_era, reward);
|
||||||
|
|
||||||
|
// Create reward pool
|
||||||
|
let total_payout = T::Currency::minimum_balance() * 1000.into();
|
||||||
|
<ErasValidatorReward<T>>::insert(current_era, total_payout);
|
||||||
|
|
||||||
|
let caller: T::AccountId = account("caller", 0, SEED);
|
||||||
|
}: {
|
||||||
|
for call in payout_calls {
|
||||||
|
call.dispatch(RawOrigin::Signed(caller.clone()).into())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -397,7 +375,7 @@ mod tests {
|
|||||||
use crate::benchmarking::{
|
use crate::benchmarking::{
|
||||||
create_validators_with_nominators_for_era,
|
create_validators_with_nominators_for_era,
|
||||||
create_validator_with_nominators,
|
create_validator_with_nominators,
|
||||||
create_nominator_with_validators,
|
SelectedBenchmark,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -429,36 +407,31 @@ mod tests {
|
|||||||
let current_era = CurrentEra::get().unwrap();
|
let current_era = CurrentEra::get().unwrap();
|
||||||
let controller = validator;
|
let controller = validator;
|
||||||
let ledger = Staking::ledger(&controller).unwrap();
|
let ledger = Staking::ledger(&controller).unwrap();
|
||||||
let stash = &ledger.stash;
|
let stash = ledger.stash;
|
||||||
|
|
||||||
let original_free_balance = Balances::free_balance(stash);
|
let original_free_balance = Balances::free_balance(&stash);
|
||||||
assert_ok!(Staking::payout_validator(Origin::signed(controller), current_era));
|
assert_ok!(Staking::payout_stakers(Origin::signed(1337), stash, current_era));
|
||||||
let new_free_balance = Balances::free_balance(stash);
|
let new_free_balance = Balances::free_balance(&stash);
|
||||||
|
|
||||||
assert!(original_free_balance < new_free_balance);
|
assert!(original_free_balance < new_free_balance);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_nominator_with_validators_works() {
|
fn test_payout_all() {
|
||||||
ExtBuilder::default().has_stakers(false).build().execute_with(|| {
|
ExtBuilder::default().has_stakers(false).build().execute_with(|| {
|
||||||
let v = 5;
|
let v = 10;
|
||||||
|
let n = 100;
|
||||||
|
|
||||||
let (nominator, validators) = create_nominator_with_validators::<Test>(v).unwrap();
|
let selected_benchmark = SelectedBenchmark::payout_all;
|
||||||
|
let c = vec![(frame_benchmarking::BenchmarkParameter::v, v), (frame_benchmarking::BenchmarkParameter::n, n)];
|
||||||
|
let closure_to_benchmark =
|
||||||
|
<SelectedBenchmark as frame_benchmarking::BenchmarkingSetup<Test>>::instance(
|
||||||
|
&selected_benchmark,
|
||||||
|
&c
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
let current_era = CurrentEra::get().unwrap();
|
assert_ok!(closure_to_benchmark());
|
||||||
let controller = nominator;
|
|
||||||
let ledger = Staking::ledger(&controller).unwrap();
|
|
||||||
let stash = &ledger.stash;
|
|
||||||
|
|
||||||
let find_nominator = validators.into_iter().map(|x| (x, 0)).collect();
|
|
||||||
|
|
||||||
let original_free_balance = Balances::free_balance(stash);
|
|
||||||
assert_ok!(Staking::payout_nominator(Origin::signed(controller), current_era, find_nominator));
|
|
||||||
let new_free_balance = Balances::free_balance(stash);
|
|
||||||
|
|
||||||
assert!(original_free_balance < new_free_balance);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,10 +104,11 @@
|
|||||||
//! The **reward and slashing** procedure is the core of the Staking module, attempting to _embrace
|
//! The **reward and slashing** procedure is the core of the Staking module, attempting to _embrace
|
||||||
//! valid behavior_ while _punishing any misbehavior or lack of availability_.
|
//! valid behavior_ while _punishing any misbehavior or lack of availability_.
|
||||||
//!
|
//!
|
||||||
//! Reward must be claimed by stakers for each era before it gets too old by $HISTORY_DEPTH using
|
//! Rewards must be claimed for each era before it gets too old by `$HISTORY_DEPTH` using the
|
||||||
//! `payout_nominator` and `payout_validator` calls.
|
//! `payout_stakers` call. Any account can call `payout_stakers`, which pays the reward to
|
||||||
|
//! the validator as well as its nominators.
|
||||||
//! Only the [`T::MaxNominatorRewardedPerValidator`] biggest stakers can claim their reward. This
|
//! Only the [`T::MaxNominatorRewardedPerValidator`] biggest stakers can claim their reward. This
|
||||||
//! limit the i/o cost to compute nominators payout.
|
//! is to limit the i/o cost to mutate storage for each nominator's account.
|
||||||
//!
|
//!
|
||||||
//! Slashing can occur at any point in time, once misbehavior is reported. Once slashing is
|
//! Slashing can occur at any point in time, once misbehavior is reported. Once slashing is
|
||||||
//! determined, a value is deducted from the balance of the validator and all the nominators who
|
//! determined, a value is deducted from the balance of the validator and all the nominators who
|
||||||
@@ -452,8 +453,9 @@ pub struct StakingLedger<AccountId, Balance: HasCompact> {
|
|||||||
/// Any balance that is becoming free, which may eventually be transferred out
|
/// Any balance that is becoming free, which may eventually be transferred out
|
||||||
/// of the stash (assuming it doesn't get slashed first).
|
/// of the stash (assuming it doesn't get slashed first).
|
||||||
pub unlocking: Vec<UnlockChunk<Balance>>,
|
pub unlocking: Vec<UnlockChunk<Balance>>,
|
||||||
/// The latest and highest era which the staker has claimed reward for.
|
/// List of eras for which the stakers behind a validator have claimed rewards. Only updated
|
||||||
pub last_reward: Option<EraIndex>,
|
/// for validators.
|
||||||
|
pub claimed_rewards: Vec<EraIndex>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<
|
impl<
|
||||||
@@ -478,7 +480,7 @@ impl<
|
|||||||
total,
|
total,
|
||||||
active: self.active,
|
active: self.active,
|
||||||
unlocking,
|
unlocking,
|
||||||
last_reward: self.last_reward
|
claimed_rewards: self.claimed_rewards
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -775,7 +777,7 @@ pub trait Trait: frame_system::Trait {
|
|||||||
/// A transaction submitter.
|
/// A transaction submitter.
|
||||||
type SubmitTransaction: SubmitUnsignedTransaction<Self, <Self as Trait>::Call>;
|
type SubmitTransaction: SubmitUnsignedTransaction<Self, <Self as Trait>::Call>;
|
||||||
|
|
||||||
/// The maximum number of nominator rewarded for each validator.
|
/// The maximum number of nominators rewarded for each validator.
|
||||||
///
|
///
|
||||||
/// For each validator only the `$MaxNominatorRewardedPerValidator` biggest stakers can claim
|
/// For each validator only the `$MaxNominatorRewardedPerValidator` biggest stakers can claim
|
||||||
/// their reward. This used to limit the i/o cost for the nominator payout.
|
/// their reward. This used to limit the i/o cost for the nominator payout.
|
||||||
@@ -818,13 +820,13 @@ impl Default for Releases {
|
|||||||
|
|
||||||
decl_storage! {
|
decl_storage! {
|
||||||
trait Store for Module<T: Trait> as Staking {
|
trait Store for Module<T: Trait> as Staking {
|
||||||
/// Number of era to keep in history.
|
/// Number of eras to keep in history.
|
||||||
///
|
///
|
||||||
/// Information is kept for eras in `[current_era - history_depth; current_era]
|
/// Information is kept for eras in `[current_era - history_depth; current_era]`.
|
||||||
///
|
///
|
||||||
/// Must be more than the number of era delayed by session otherwise.
|
/// Must be more than the number of eras delayed by session otherwise.
|
||||||
/// i.e. active era must always be in history.
|
/// I.e. active era must always be in history.
|
||||||
/// i.e. `active_era > current_era - history_depth` must be guaranteed.
|
/// I.e. `active_era > current_era - history_depth` must be guaranteed.
|
||||||
HistoryDepth get(fn history_depth) config(): u32 = 84;
|
HistoryDepth get(fn history_depth) config(): u32 = 84;
|
||||||
|
|
||||||
/// The ideal number of staking participants.
|
/// The ideal number of staking participants.
|
||||||
@@ -860,7 +862,7 @@ decl_storage! {
|
|||||||
|
|
||||||
/// The current era index.
|
/// The current era index.
|
||||||
///
|
///
|
||||||
/// This is the latest planned era, depending on how session module queues the validator
|
/// This is the latest planned era, depending on how the Session pallet queues the validator
|
||||||
/// set, it might be active or not.
|
/// set, it might be active or not.
|
||||||
pub CurrentEra get(fn current_era): Option<EraIndex>;
|
pub CurrentEra get(fn current_era): Option<EraIndex>;
|
||||||
|
|
||||||
@@ -870,7 +872,7 @@ decl_storage! {
|
|||||||
/// Validator set of this era must be equal to `SessionInterface::validators`.
|
/// Validator set of this era must be equal to `SessionInterface::validators`.
|
||||||
pub ActiveEra get(fn active_era): Option<ActiveEraInfo>;
|
pub ActiveEra get(fn active_era): Option<ActiveEraInfo>;
|
||||||
|
|
||||||
/// The session index at which the era start for the last `HISTORY_DEPTH` eras
|
/// The session index at which the era start for the last `HISTORY_DEPTH` eras.
|
||||||
pub ErasStartSessionIndex get(fn eras_start_session_index):
|
pub ErasStartSessionIndex get(fn eras_start_session_index):
|
||||||
map hasher(twox_64_concat) EraIndex => Option<SessionIndex>;
|
map hasher(twox_64_concat) EraIndex => Option<SessionIndex>;
|
||||||
|
|
||||||
@@ -886,7 +888,7 @@ decl_storage! {
|
|||||||
|
|
||||||
/// Clipped Exposure of validator at era.
|
/// Clipped Exposure of validator at era.
|
||||||
///
|
///
|
||||||
/// This is similar to [`ErasStakers`] but number of nominators exposed is reduce to the
|
/// This is similar to [`ErasStakers`] but number of nominators exposed is reduced to the
|
||||||
/// `T::MaxNominatorRewardedPerValidator` biggest stakers.
|
/// `T::MaxNominatorRewardedPerValidator` biggest stakers.
|
||||||
/// (Note: the field `total` and `own` of the exposure remains unchanged).
|
/// (Note: the field `total` and `own` of the exposure remains unchanged).
|
||||||
/// This is used to limit the i/o cost for the nominator payout.
|
/// This is used to limit the i/o cost for the nominator payout.
|
||||||
@@ -899,7 +901,7 @@ decl_storage! {
|
|||||||
double_map hasher(twox_64_concat) EraIndex, hasher(twox_64_concat) T::AccountId
|
double_map hasher(twox_64_concat) EraIndex, hasher(twox_64_concat) T::AccountId
|
||||||
=> Exposure<T::AccountId, BalanceOf<T>>;
|
=> Exposure<T::AccountId, BalanceOf<T>>;
|
||||||
|
|
||||||
/// Similarly to `ErasStakers` this holds the preferences of validators.
|
/// Similar to `ErasStakers`, this holds the preferences of validators.
|
||||||
///
|
///
|
||||||
/// This is keyed first by the era index to allow bulk deletion and then the stash account.
|
/// This is keyed first by the era index to allow bulk deletion and then the stash account.
|
||||||
///
|
///
|
||||||
@@ -998,6 +1000,9 @@ decl_storage! {
|
|||||||
///
|
///
|
||||||
/// This is set to v3.0.0 for new networks.
|
/// This is set to v3.0.0 for new networks.
|
||||||
StorageVersion build(|_: &GenesisConfig<T>| Releases::V3_0_0): Releases;
|
StorageVersion build(|_: &GenesisConfig<T>| Releases::V3_0_0): Releases;
|
||||||
|
|
||||||
|
/// The era where we migrated from Lazy Payouts to Simple Payouts
|
||||||
|
MigrateEra: Option<EraIndex>;
|
||||||
}
|
}
|
||||||
add_extra_genesis {
|
add_extra_genesis {
|
||||||
config(stakers):
|
config(stakers):
|
||||||
@@ -1035,7 +1040,7 @@ decl_storage! {
|
|||||||
|
|
||||||
decl_event!(
|
decl_event!(
|
||||||
pub enum Event<T> where Balance = BalanceOf<T>, <T as frame_system::Trait>::AccountId {
|
pub enum Event<T> where Balance = BalanceOf<T>, <T as frame_system::Trait>::AccountId {
|
||||||
/// The staker has been rewarded by this amount. AccountId is controller account.
|
/// The staker has been rewarded by this amount. `AccountId` is the stash account.
|
||||||
Reward(AccountId, Balance),
|
Reward(AccountId, Balance),
|
||||||
/// One validator (and its nominators) has been slashed by the given amount.
|
/// One validator (and its nominators) has been slashed by the given amount.
|
||||||
Slash(AccountId, Balance),
|
Slash(AccountId, Balance),
|
||||||
@@ -1088,6 +1093,8 @@ decl_error! {
|
|||||||
InvalidNumberOfNominations,
|
InvalidNumberOfNominations,
|
||||||
/// Items are not sorted and unique.
|
/// Items are not sorted and unique.
|
||||||
NotSortedAndUnique,
|
NotSortedAndUnique,
|
||||||
|
/// Rewards for this era have already been claimed for this validator.
|
||||||
|
AlreadyClaimed,
|
||||||
/// The submitted result is received out of the open window.
|
/// The submitted result is received out of the open window.
|
||||||
PhragmenEarlySubmission,
|
PhragmenEarlySubmission,
|
||||||
/// The submitted result is not as good as the one stored on chain.
|
/// The submitted result is not as good as the one stored on chain.
|
||||||
@@ -1223,6 +1230,7 @@ decl_module! {
|
|||||||
// For Kusama the type hasn't actually changed as Moment was u64 and was the number of
|
// For Kusama the type hasn't actually changed as Moment was u64 and was the number of
|
||||||
// millisecond since unix epoch.
|
// millisecond since unix epoch.
|
||||||
StorageVersion::put(Releases::V3_0_0);
|
StorageVersion::put(Releases::V3_0_0);
|
||||||
|
Self::migrate_last_reward_to_claimed_rewards();
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1273,6 +1281,10 @@ decl_module! {
|
|||||||
|
|
||||||
system::Module::<T>::inc_ref(&stash);
|
system::Module::<T>::inc_ref(&stash);
|
||||||
|
|
||||||
|
let current_era = CurrentEra::get().unwrap_or(0);
|
||||||
|
let history_depth = Self::history_depth();
|
||||||
|
let last_reward_era = current_era.saturating_sub(history_depth);
|
||||||
|
|
||||||
let stash_balance = T::Currency::free_balance(&stash);
|
let stash_balance = T::Currency::free_balance(&stash);
|
||||||
let value = value.min(stash_balance);
|
let value = value.min(stash_balance);
|
||||||
Self::deposit_event(RawEvent::Bonded(stash.clone(), value));
|
Self::deposit_event(RawEvent::Bonded(stash.clone(), value));
|
||||||
@@ -1281,7 +1293,7 @@ decl_module! {
|
|||||||
total: value,
|
total: value,
|
||||||
active: value,
|
active: value,
|
||||||
unlocking: vec![],
|
unlocking: vec![],
|
||||||
last_reward: Self::current_era(),
|
claimed_rewards: (last_reward_era..current_era).collect(),
|
||||||
};
|
};
|
||||||
Self::update_ledger(&controller, &item);
|
Self::update_ledger(&controller, &item);
|
||||||
}
|
}
|
||||||
@@ -1629,6 +1641,10 @@ decl_module! {
|
|||||||
<Self as Store>::UnappliedSlashes::insert(&era, &unapplied);
|
<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.
|
/// Make one nominator's payout for one era.
|
||||||
///
|
///
|
||||||
/// - `who` is the controller account of the nominator to pay out.
|
/// - `who` is the controller account of the nominator to pay out.
|
||||||
@@ -1663,6 +1679,10 @@ decl_module! {
|
|||||||
Self::do_payout_nominator(who, era, validators)
|
Self::do_payout_nominator(who, 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.
|
/// Make one validator's payout for one era.
|
||||||
///
|
///
|
||||||
/// - `who` is the controller account of the validator to pay out.
|
/// - `who` is the controller account of the validator to pay out.
|
||||||
@@ -1684,6 +1704,25 @@ decl_module! {
|
|||||||
Self::do_payout_validator(who, era)
|
Self::do_payout_validator(who, 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
|
||||||
|
/// `T::MaxNominatorRewardedPerValidator`, will also receive their rewards.
|
||||||
|
/// - `era` may be any era between `[current_era - history_depth; current_era]`.
|
||||||
|
///
|
||||||
|
/// The origin of this call must be _Signed_. Any account can call this function, even if
|
||||||
|
/// it is not one of the stakers.
|
||||||
|
///
|
||||||
|
/// # <weight>
|
||||||
|
/// - Time complexity: at most O(MaxNominatorRewardedPerValidator).
|
||||||
|
/// - Contains a limited number of reads and writes.
|
||||||
|
/// # </weight>
|
||||||
|
#[weight = SimpleDispatchInfo::FixedNormal(500_000)]
|
||||||
|
fn payout_stakers(origin, validator_stash: T::AccountId, era: EraIndex) -> DispatchResult {
|
||||||
|
ensure_signed(origin)?;
|
||||||
|
Self::do_payout_stakers(validator_stash, era)
|
||||||
|
}
|
||||||
|
|
||||||
/// Rebond a portion of the stash scheduled to be unlocked.
|
/// Rebond a portion of the stash scheduled to be unlocked.
|
||||||
///
|
///
|
||||||
/// # <weight>
|
/// # <weight>
|
||||||
@@ -1857,6 +1896,43 @@ decl_module! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Trait> Module<T> {
|
impl<T: Trait> Module<T> {
|
||||||
|
/// Migrate `last_reward` to `claimed_rewards`
|
||||||
|
pub fn migrate_last_reward_to_claimed_rewards() {
|
||||||
|
use frame_support::migration::{StorageIterator, put_storage_value};
|
||||||
|
// Migrate from `last_reward` to `claimed_rewards`.
|
||||||
|
// We will construct a vector from `current_era - history_depth` to `last_reward`
|
||||||
|
// for each validator and nominator.
|
||||||
|
//
|
||||||
|
// Old Staking Ledger
|
||||||
|
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)]
|
||||||
|
struct OldStakingLedger<AccountId, Balance: HasCompact> {
|
||||||
|
pub stash: AccountId,
|
||||||
|
#[codec(compact)]
|
||||||
|
pub total: Balance,
|
||||||
|
#[codec(compact)]
|
||||||
|
pub active: Balance,
|
||||||
|
pub unlocking: Vec<UnlockChunk<Balance>>,
|
||||||
|
pub last_reward: Option<EraIndex>,
|
||||||
|
}
|
||||||
|
// Current era and history depth
|
||||||
|
let current_era = Self::current_era().unwrap_or(0);
|
||||||
|
let history_depth = Self::history_depth();
|
||||||
|
let last_payout_era = current_era.saturating_sub(history_depth);
|
||||||
|
// Convert all ledgers to the new format.
|
||||||
|
for (hash, old_ledger) in StorageIterator::<OldStakingLedger<T::AccountId, BalanceOf<T>>>::new(b"Staking", b"Ledger").drain() {
|
||||||
|
let last_reward = old_ledger.last_reward.unwrap_or(0);
|
||||||
|
let new_ledger = StakingLedger {
|
||||||
|
stash: old_ledger.stash,
|
||||||
|
total: old_ledger.total,
|
||||||
|
active: old_ledger.active,
|
||||||
|
unlocking: old_ledger.unlocking,
|
||||||
|
claimed_rewards: (last_payout_era..=last_reward).collect(),
|
||||||
|
};
|
||||||
|
put_storage_value(b"Staking", b"Ledger", &hash, new_ledger);
|
||||||
|
}
|
||||||
|
MigrateEra::put(current_era);
|
||||||
|
}
|
||||||
|
|
||||||
/// The total balance that can be slashed from a stash account as of right now.
|
/// The total balance that can be slashed from a stash account as of right now.
|
||||||
pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf<T> {
|
pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf<T> {
|
||||||
Self::bonded(stash).and_then(Self::ledger).map(|l| l.active).unwrap_or_default()
|
Self::bonded(stash).and_then(Self::ledger).map(|l| l.active).unwrap_or_default()
|
||||||
@@ -1916,6 +1992,15 @@ impl<T: Trait> Module<T> {
|
|||||||
if validators.len() > MAX_NOMINATIONS {
|
if validators.len() > MAX_NOMINATIONS {
|
||||||
return Err(Error::<T>::InvalidNumberOfNominations.into());
|
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
|
// Note: if era has no reward to be claimed, era may be future. better not to update
|
||||||
// `nominator_ledger.last_reward` in this case.
|
// `nominator_ledger.last_reward` in this case.
|
||||||
@@ -1924,11 +2009,12 @@ impl<T: Trait> Module<T> {
|
|||||||
|
|
||||||
let mut nominator_ledger = <Ledger<T>>::get(&who).ok_or_else(|| Error::<T>::NotController)?;
|
let mut nominator_ledger = <Ledger<T>>::get(&who).ok_or_else(|| Error::<T>::NotController)?;
|
||||||
|
|
||||||
if nominator_ledger.last_reward.map(|last_reward| last_reward >= era).unwrap_or(false) {
|
nominator_ledger.claimed_rewards.retain(|&x| x >= current_era.saturating_sub(history_depth));
|
||||||
return Err(Error::<T>::InvalidEraToReward.into());
|
match nominator_ledger.claimed_rewards.binary_search(&era) {
|
||||||
|
Ok(_) => Err(Error::<T>::AlreadyClaimed)?,
|
||||||
|
Err(pos) => nominator_ledger.claimed_rewards.insert(pos, era),
|
||||||
}
|
}
|
||||||
|
|
||||||
nominator_ledger.last_reward = Some(era);
|
|
||||||
<Ledger<T>>::insert(&who, &nominator_ledger);
|
<Ledger<T>>::insert(&who, &nominator_ledger);
|
||||||
|
|
||||||
let mut reward = Perbill::zero();
|
let mut reward = Perbill::zero();
|
||||||
@@ -1972,17 +2058,29 @@ impl<T: Trait> Module<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn do_payout_validator(who: T::AccountId, era: EraIndex) -> DispatchResult {
|
fn do_payout_validator(who: 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
|
// Note: if era has no reward to be claimed, era may be future. better not to update
|
||||||
// `ledger.last_reward` in this case.
|
// `ledger.last_reward` in this case.
|
||||||
let era_payout = <ErasValidatorReward<T>>::get(&era)
|
let era_payout = <ErasValidatorReward<T>>::get(&era)
|
||||||
.ok_or_else(|| Error::<T>::InvalidEraToReward)?;
|
.ok_or_else(|| Error::<T>::InvalidEraToReward)?;
|
||||||
|
|
||||||
let mut ledger = <Ledger<T>>::get(&who).ok_or_else(|| Error::<T>::NotController)?;
|
let mut ledger = <Ledger<T>>::get(&who).ok_or_else(|| Error::<T>::NotController)?;
|
||||||
if ledger.last_reward.map(|last_reward| last_reward >= era).unwrap_or(false) {
|
|
||||||
return Err(Error::<T>::InvalidEraToReward.into());
|
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.last_reward = Some(era);
|
|
||||||
<Ledger<T>>::insert(&who, &ledger);
|
<Ledger<T>>::insert(&who, &ledger);
|
||||||
|
|
||||||
let era_reward_points = <ErasRewardPoints<T>>::get(&era);
|
let era_reward_points = <ErasRewardPoints<T>>::get(&era);
|
||||||
@@ -2013,6 +2111,108 @@ impl<T: Trait> Module<T> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn do_payout_stakers(
|
||||||
|
validator_stash: T::AccountId,
|
||||||
|
era: EraIndex,
|
||||||
|
) -> DispatchResult {
|
||||||
|
/* Validate input data */
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
.ok_or_else(|| Error::<T>::InvalidEraToReward)?;
|
||||||
|
|
||||||
|
let controller = Self::bonded(&validator_stash).ok_or(Error::<T>::NotStash)?;
|
||||||
|
let mut ledger = <Ledger<T>>::get(&controller).ok_or_else(|| Error::<T>::NotController)?;
|
||||||
|
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
|
||||||
|
let exposure = <ErasStakersClipped<T>>::get(&era, &ledger.stash);
|
||||||
|
|
||||||
|
/* Input data seems good, no errors allowed after this point */
|
||||||
|
|
||||||
|
<Ledger<T>>::insert(&controller, &ledger);
|
||||||
|
|
||||||
|
// Get Era reward points. It has TOTAL and INDIVIDUAL
|
||||||
|
// Find the fraction of the era reward that belongs to the validator
|
||||||
|
// Take that fraction of the eras rewards to split to nominator and validator
|
||||||
|
//
|
||||||
|
// Then look at the validator, figure out the proportion of their reward
|
||||||
|
// which goes to them and each of their nominators.
|
||||||
|
|
||||||
|
let era_reward_points = <ErasRewardPoints<T>>::get(&era);
|
||||||
|
let total_reward_points = era_reward_points.total;
|
||||||
|
let validator_reward_points = era_reward_points.individual.get(&ledger.stash)
|
||||||
|
.map(|points| *points)
|
||||||
|
.unwrap_or_else(|| Zero::zero());
|
||||||
|
|
||||||
|
// Nothing to do if they have no reward points.
|
||||||
|
if validator_reward_points.is_zero() { return Ok(())}
|
||||||
|
|
||||||
|
// This is the fraction of the total reward that the validator and the
|
||||||
|
// nominators will get.
|
||||||
|
let validator_total_reward_part = Perbill::from_rational_approximation(
|
||||||
|
validator_reward_points,
|
||||||
|
total_reward_points,
|
||||||
|
);
|
||||||
|
|
||||||
|
// This is how much validator + nominators are entitled to.
|
||||||
|
let validator_total_payout = validator_total_reward_part * era_payout;
|
||||||
|
|
||||||
|
let validator_prefs = Self::eras_validator_prefs(&era, &validator_stash);
|
||||||
|
// Validator first gets a cut off the top.
|
||||||
|
let validator_commission = validator_prefs.commission;
|
||||||
|
let validator_commission_payout = validator_commission * validator_total_payout;
|
||||||
|
|
||||||
|
let validator_leftover_payout = validator_total_payout - validator_commission_payout;
|
||||||
|
// Now let's calculate how this is split to the validator.
|
||||||
|
let validator_exposure_part = Perbill::from_rational_approximation(
|
||||||
|
exposure.own,
|
||||||
|
exposure.total,
|
||||||
|
);
|
||||||
|
let validator_staking_payout = validator_exposure_part * validator_leftover_payout;
|
||||||
|
|
||||||
|
// We can now make total validator payout:
|
||||||
|
if let Some(imbalance) = Self::make_payout(
|
||||||
|
&ledger.stash,
|
||||||
|
validator_staking_payout + validator_commission_payout
|
||||||
|
) {
|
||||||
|
Self::deposit_event(RawEvent::Reward(ledger.stash, imbalance.peek()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lets now calculate how this is split to the nominators.
|
||||||
|
// Sort nominators by highest to lowest exposure, but only keep `max_nominator_payouts` of them.
|
||||||
|
for nominator in exposure.others.iter() {
|
||||||
|
let nominator_exposure_part = Perbill::from_rational_approximation(
|
||||||
|
nominator.value,
|
||||||
|
exposure.total,
|
||||||
|
);
|
||||||
|
|
||||||
|
let nominator_reward: BalanceOf<T> = nominator_exposure_part * validator_leftover_payout;
|
||||||
|
// We can now make nominator payout:
|
||||||
|
if let Some(imbalance) = Self::make_payout(&nominator.who, nominator_reward) {
|
||||||
|
Self::deposit_event(RawEvent::Reward(nominator.who.clone(), imbalance.peek()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Update the ledger for a controller. This will also update the stash lock. The lock will
|
/// Update the ledger for a controller. This will also update the stash lock. The lock will
|
||||||
/// will lock the entire funds except paying for further transactions.
|
/// will lock the entire funds except paying for further transactions.
|
||||||
fn update_ledger(
|
fn update_ledger(
|
||||||
|
|||||||
@@ -34,11 +34,7 @@ use sp_io;
|
|||||||
use sp_phragmen::{
|
use sp_phragmen::{
|
||||||
build_support_map, evaluate_support, reduce, ExtendedBalance, StakedAssignment, PhragmenScore,
|
build_support_map, evaluate_support, reduce, ExtendedBalance, StakedAssignment, PhragmenScore,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::*;
|
||||||
EraIndex, GenesisConfig, Module, Trait, StakerStatus, ValidatorPrefs, RewardDestination,
|
|
||||||
Nominators, inflation, SessionInterface, Exposure, ErasStakers, ErasRewardPoints,
|
|
||||||
CompactAssignments, ValidatorIndex, NominatorIndex, Validators, OffchainAccuracy,
|
|
||||||
};
|
|
||||||
|
|
||||||
const INIT_TIMESTAMP: u64 = 30_000;
|
const INIT_TIMESTAMP: u64 = 30_000;
|
||||||
|
|
||||||
@@ -559,6 +555,7 @@ pub fn assert_ledger_consistent(stash: AccountId) {
|
|||||||
|
|
||||||
pub fn bond_validator(stash: u64, ctrl: u64, val: u64) {
|
pub fn bond_validator(stash: u64, ctrl: u64, val: u64) {
|
||||||
let _ = Balances::make_free_balance_be(&stash, val);
|
let _ = Balances::make_free_balance_be(&stash, val);
|
||||||
|
let _ = Balances::make_free_balance_be(&ctrl, val);
|
||||||
assert_ok!(Staking::bond(
|
assert_ok!(Staking::bond(
|
||||||
Origin::signed(stash),
|
Origin::signed(stash),
|
||||||
ctrl,
|
ctrl,
|
||||||
@@ -573,6 +570,7 @@ pub fn bond_validator(stash: u64, ctrl: u64, val: u64) {
|
|||||||
|
|
||||||
pub fn bond_nominator(stash: u64, ctrl: u64, val: u64, target: Vec<u64>) {
|
pub fn bond_nominator(stash: u64, ctrl: u64, val: u64, target: Vec<u64>) {
|
||||||
let _ = Balances::make_free_balance_be(&stash, val);
|
let _ = Balances::make_free_balance_be(&stash, val);
|
||||||
|
let _ = Balances::make_free_balance_be(&ctrl, val);
|
||||||
assert_ok!(Staking::bond(
|
assert_ok!(Staking::bond(
|
||||||
Origin::signed(stash),
|
Origin::signed(stash),
|
||||||
ctrl,
|
ctrl,
|
||||||
@@ -684,8 +682,6 @@ pub fn on_offence_now(
|
|||||||
pub fn horrible_phragmen_with_post_processing(
|
pub fn horrible_phragmen_with_post_processing(
|
||||||
do_reduce: bool,
|
do_reduce: bool,
|
||||||
) -> (CompactAssignments, Vec<ValidatorIndex>, PhragmenScore) {
|
) -> (CompactAssignments, Vec<ValidatorIndex>, PhragmenScore) {
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
let mut backing_stake_of: BTreeMap<AccountId, Balance> = BTreeMap::new();
|
let mut backing_stake_of: BTreeMap<AccountId, Balance> = BTreeMap::new();
|
||||||
|
|
||||||
// self stake
|
// self stake
|
||||||
@@ -867,7 +863,7 @@ pub fn prepare_submission_with(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Make all validator and nominator request their payment
|
/// Make all validator and nominator request their payment
|
||||||
pub fn make_all_reward_payment(era: EraIndex) {
|
pub fn make_all_reward_payment_before_migration(era: EraIndex) {
|
||||||
let validators_with_reward = ErasRewardPoints::<Test>::get(era).individual.keys()
|
let validators_with_reward = ErasRewardPoints::<Test>::get(era).individual.keys()
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@@ -898,6 +894,20 @@ pub fn make_all_reward_payment(era: EraIndex) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Make all validator and nominator request their payment
|
||||||
|
pub fn make_all_reward_payment(era: EraIndex) {
|
||||||
|
let validators_with_reward = ErasRewardPoints::<Test>::get(era).individual.keys()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// reward validators
|
||||||
|
for validator_controller in validators_with_reward.iter().filter_map(Staking::bonded) {
|
||||||
|
let ledger = <Ledger<Test>>::get(&validator_controller).unwrap();
|
||||||
|
|
||||||
|
assert_ok!(Staking::payout_stakers(Origin::signed(1337), ledger.stash, era));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! assert_session_era {
|
macro_rules! assert_session_era {
|
||||||
($session:expr, $era:expr) => {
|
($session:expr, $era:expr) => {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user