Newer
Older
/// # <weight>
/// - The transaction's complexity is proportional to the size of `targets`,
/// which is capped at `MAX_NOMINATIONS`.
/// - Both the reads and writes follow a similar pattern.
/// # </weight>
#[weight = SimpleDispatchInfo::FixedNormal(750_000)]
fn nominate(origin, targets: Vec<<T::Lookup as StaticLookup>::Source>) {
Self::ensure_storage_upgraded();
let controller = ensure_signed(origin)?;
let ledger = Self::ledger(&controller).ok_or("not a controller")?;
let stash = &ledger.stash;
ensure!(!targets.is_empty(), "targets cannot be empty");
let targets = targets.into_iter()
.take(MAX_NOMINATIONS)
.map(|t| T::Lookup::lookup(t))
.collect::<result::Result<Vec<T::AccountId>, _>>()?;
let nominations = Nominations {
targets,
submitted_in: Self::current_era(),
suppressed: false,
};
<Validators<T>>::remove(stash);
<Nominators<T>>::insert(stash, &nominations);
/// Declare no desire to either validate or nominate.
/// Effects will be felt at the beginning of the next era.
/// The dispatch origin for this call must be _Signed_ by the controller, not the stash.
///
/// # <weight>
/// - Independent of the arguments. Insignificant complexity.
/// - Contains one read.
/// - Writes are limited to the `origin` account key.
/// # </weight>
#[weight = SimpleDispatchInfo::FixedNormal(500_000)]
fn chill(origin) {
let controller = ensure_signed(origin)?;
let ledger = Self::ledger(&controller).ok_or("not a controller")?;
Self::chill_stash(&ledger.stash);
/// (Re-)set the payment target for a controller.
///
/// Effects will be felt at the beginning of the next era.
/// The dispatch origin for this call must be _Signed_ by the controller, not the stash.
///
/// # <weight>
/// - Independent of the arguments. Insignificant complexity.
/// - Contains a limited number of reads.
/// - Writes are limited to the `origin` account key.
/// # </weight>
#[weight = SimpleDispatchInfo::FixedNormal(500_000)]
fn set_payee(origin, payee: RewardDestination) {
let controller = ensure_signed(origin)?;
let ledger = Self::ledger(&controller).ok_or("not a controller")?;
let stash = &ledger.stash;
<Payee<T>>::insert(stash, payee);
}
/// (Re-)set the controller of a stash.
///
/// Effects will be felt at the beginning of the next era.
///
/// The dispatch origin for this call must be _Signed_ by the stash, not the controller.
///
/// # <weight>
/// - Independent of the arguments. Insignificant complexity.
/// - Contains a limited number of reads.
/// - Writes are limited to the `origin` account key.
/// # </weight>
#[weight = SimpleDispatchInfo::FixedNormal(750_000)]
fn set_controller(origin, controller: <T::Lookup as StaticLookup>::Source) {
let stash = ensure_signed(origin)?;
let old_controller = Self::bonded(&stash).ok_or("not a stash")?;
let controller = T::Lookup::lookup(controller)?;
if <Ledger<T>>::exists(&controller) {
return Err("controller already paired")
}
if controller != old_controller {
<Bonded<T>>::insert(&stash, &controller);
if let Some(l) = <Ledger<T>>::take(&old_controller) {
<Ledger<T>>::insert(&controller, l);
}
/// The ideal number of validators.
#[weight = SimpleDispatchInfo::FreeOperational]
fn set_validator_count(origin, #[compact] new: u32) {
ensure_root(origin)?;
ValidatorCount::put(new);
/// Force there to be no new eras indefinitely.
///
/// # <weight>
/// - No arguments.
/// # </weight>
#[weight = SimpleDispatchInfo::FreeOperational]
fn force_no_eras(origin) {
ensure_root(origin)?;
ForceEra::put(Forcing::ForceNone);
}
/// Force there to be a new era at the end of the next session. After this, it will be
/// reset to normal (non-forced) behaviour.
///
/// # <weight>
/// # </weight>
#[weight = SimpleDispatchInfo::FreeOperational]
fn force_new_era(origin) {
ensure_root(origin)?;
ForceEra::put(Forcing::ForceNew);
/// Set the validators who cannot be slashed (if any).
#[weight = SimpleDispatchInfo::FreeOperational]
fn set_invulnerables(origin, validators: Vec<T::AccountId>) {
ensure_root(origin)?;
<Invulnerables<T>>::put(validators);
/// Force a current staker to become completely unstaked, immediately.
#[weight = SimpleDispatchInfo::FreeOperational]
fn force_unstake(origin, stash: T::AccountId) {
ensure_root(origin)?;
// remove the lock.
T::Currency::remove_lock(STAKING_ID, &stash);
// remove all staking-related information.
Self::kill_stash(&stash);
}
/// Force there to be a new era at the end of sessions indefinitely.
///
/// # <weight>
/// - One storage write
/// # </weight>
#[weight = SimpleDispatchInfo::FreeOperational]
fn force_new_era_always(origin) {
ensure_root(origin)?;
ForceEra::put(Forcing::ForceAlways);
}
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
/// Cancel enactment of a deferred slash. Can be called by either the root origin or
/// the `T::SlashCancelOrigin`.
/// passing the era and indices of the slashes for that era to kill.
///
/// # <weight>
/// - One storage write.
/// # </weight>
#[weight = SimpleDispatchInfo::FreeOperational]
fn cancel_deferred_slash(origin, era: EraIndex, slash_indices: Vec<u32>) {
T::SlashCancelOrigin::try_origin(origin)
.map(|_| ())
.or_else(ensure_root)
.map_err(|_| "bad origin")?;
let mut slash_indices = slash_indices;
slash_indices.sort_unstable();
let mut unapplied = <Self as Store>::UnappliedSlashes::get(&era);
for (removed, index) in slash_indices.into_iter().enumerate() {
let index = index as usize;
// if `index` is not duplicate, `removed` must be <= index.
ensure!(removed <= index, "duplicate index");
// all prior removals were from before this index, since the
// list is sorted.
let index = index - removed;
ensure!(index < unapplied.len(), "slash record index out of bounds");
unapplied.remove(index);
}
<Self as Store>::UnappliedSlashes::insert(&era, &unapplied);
}
}
}
impl<T: Trait> Module<T> {
// PUBLIC IMMUTABLES
/// 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> {
Self::bonded(stash).and_then(Self::ledger).map(|l| l.active).unwrap_or_default()
// MUTABLES (DANGEROUS)
/// 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.
fn update_ledger(
controller: &T::AccountId,
ledger: &StakingLedger<T::AccountId, BalanceOf<T>>
) {
T::Currency::set_lock(
STAKING_ID,
&ledger.stash,
ledger.total,
T::BlockNumber::max_value(),
<Ledger<T>>::insert(controller, ledger);
}
/// Chill a stash account.
fn chill_stash(stash: &T::AccountId) {
<Validators<T>>::remove(stash);
<Nominators<T>>::remove(stash);
}
/// Ensures storage is upgraded to most recent necessary state.
fn ensure_storage_upgraded() {
migration::perform_migrations::<T>();
/// Actually make a payment to a staker. This uses the currency's reward function
/// to pay the right payee for the given staker account.
fn make_payout(stash: &T::AccountId, amount: BalanceOf<T>) -> Option<PositiveImbalanceOf<T>> {
let dest = Self::payee(stash);
match dest {
RewardDestination::Controller => Self::bonded(stash)
.and_then(|controller|
T::Currency::deposit_into_existing(&controller, amount).ok()
),
RewardDestination::Stash =>
T::Currency::deposit_into_existing(stash, amount).ok(),
RewardDestination::Staked => Self::bonded(stash)
.and_then(|c| Self::ledger(&c).map(|l| (c, l)))
.and_then(|(controller, mut l)| {
l.active += amount;
l.total += amount;
let r = T::Currency::deposit_into_existing(stash, amount).ok();
Self::update_ledger(&controller, &l);
/// Reward a given validator by a specific amount. Add the reward to the validator's, and its
/// nominators' balance, pro-rata based on their exposure, after having removed the validator's
/// pre-payout cut.
fn reward_validator(stash: &T::AccountId, reward: BalanceOf<T>) -> PositiveImbalanceOf<T> {
let off_the_table = Self::validators(stash).commission * reward;
let reward = reward.saturating_sub(off_the_table);
let mut imbalance = <PositiveImbalanceOf<T>>::zero();
let validator_cut = if reward.is_zero() {
Zero::zero()
} else {
let exposure = Self::stakers(stash);
let total = exposure.total.max(One::one());
let per_u64 = Perbill::from_rational_approximation(i.value, total);
imbalance.maybe_subsume(Self::make_payout(&i.who, per_u64 * reward));
let per_u64 = Perbill::from_rational_approximation(exposure.own, total);
per_u64 * reward
imbalance.maybe_subsume(Self::make_payout(stash, validator_cut + off_the_table));
/// Session has just ended. Provide the validator set for the next session if it's an era-end, along
/// with the exposure of the prior validator set.
fn new_session(session_index: SessionIndex)
-> Option<(Vec<T::AccountId>, Vec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>)>)>
{
let era_length = session_index.checked_sub(Self::current_era_start_session_index()).unwrap_or(0);
match ForceEra::get() {
Forcing::ForceNew => ForceEra::kill(),
Forcing::NotForcing if era_length >= T::SessionsPerEra::get() => (),
let validators = T::SessionInterface::validators();
let prior = validators.into_iter()
.map(|v| { let e = Self::stakers(&v); (v, e) })
.collect();
Self::new_era(session_index).map(move |new| (new, prior))
}
/// The era has changed - enact new staking set.
///
/// NOTE: This always happens immediately before a session change to ensure that new validators
/// get a chance to set their session keys.
fn new_era(start_session_index: SessionIndex) -> Option<Vec<T::AccountId>> {
let points = CurrentEraPointsEarned::take();
let now = T::Time::now();
let previous_era_start = <CurrentEraStart<T>>::mutate(|v| {
sp_std::mem::replace(v, now)
});
let era_duration = now - previous_era_start;
if !era_duration.is_zero() {
let validators = Self::current_elected();
let validator_len: BalanceOf<T> = (validators.len() as u32).into();
let total_rewarded_stake = Self::slot_stake() * validator_len;
let (total_payout, max_payout) = inflation::compute_total_payout(
&T::RewardCurve::get(),
total_rewarded_stake.clone(),
T::Currency::total_issuance(),
// Duration of era; more than u64::MAX is rewarded as u64::MAX.
era_duration.saturated_into::<u64>(),
);
let mut total_imbalance = <PositiveImbalanceOf<T>>::zero();
for (v, p) in validators.iter().zip(points.individual.into_iter()) {
if p != 0 {
let reward = Perbill::from_rational_approximation(p, points.total) * total_payout;
total_imbalance.subsume(Self::reward_validator(v, reward));
}
// assert!(total_imbalance.peek() == total_payout)
let total_payout = total_imbalance.peek();
let rest = max_payout.saturating_sub(total_payout);
Self::deposit_event(RawEvent::Reward(total_payout, rest));
T::Reward::on_unbalanced(total_imbalance);
T::RewardRemainder::on_unbalanced(T::Currency::issue(rest));
let current_era = CurrentEra::mutate(|s| { *s += 1; *s });
CurrentEraStartSessionIndex::mutate(|v| {
*v = start_session_index;
});
let bonding_duration = T::BondingDuration::get();
BondedEras::mutate(|bonded| {
bonded.push((current_era, start_session_index));
if current_era > bonding_duration {
let first_kept = current_era - bonding_duration;
// prune out everything that's from before the first-kept index.
let n_to_prune = bonded.iter()
.take_while(|&&(era_idx, _)| era_idx < first_kept)
.count();
// kill slashing metadata.
for (pruned_era, _) in bonded.drain(..n_to_prune) {
slashing::clear_era_metadata::<T>(pruned_era);
}
if let Some(&(_, first_session)) = bonded.first() {
T::SessionInterface::prune_historical_up_to(first_session);
}
let (_slot_stake, maybe_new_validators) = Self::select_validators();
Self::apply_unapplied_slashes(current_era);
/// Apply previously-unapplied slashes on the beginning of a new era, after a delay.
fn apply_unapplied_slashes(current_era: EraIndex) {
let slash_defer_duration = T::SlashDeferDuration::get();
<Self as Store>::EarliestUnappliedSlash::mutate(|earliest| if let Some(ref mut earliest) = earliest {
let keep_from = current_era.saturating_sub(slash_defer_duration);
for era in (*earliest)..keep_from {
let era_slashes = <Self as Store>::UnappliedSlashes::take(&era);
for slash in era_slashes {
slashing::apply_slash::<T>(slash);
}
}
*earliest = (*earliest).max(keep_from)
})
}
/// Select a new validator set from the assembled stakers and their role preferences.
///
/// Returns the new `SlotStake` value and a set of newly selected _stash_ IDs.
///
/// Assumes storage is coherent with the declaration.
fn select_validators() -> (BalanceOf<T>, Option<Vec<T::AccountId>>) {
let mut all_nominators: Vec<(T::AccountId, Vec<T::AccountId>)> = Vec::new();
let all_validator_candidates_iter = <Validators<T>>::enumerate();
let all_validators = all_validator_candidates_iter.map(|(who, _pref)| {
let self_vote = (who.clone(), vec![who.clone()]);
all_nominators.push(self_vote);
who
}).collect::<Vec<T::AccountId>>();
let nominator_votes = <Nominators<T>>::enumerate().map(|(nominator, nominations)| {
let Nominations { submitted_in, mut targets, suppressed: _ } = nominations;
// Filter out nomination targets which were nominated before the most recent
// slashing span.
targets.retain(|stash| {
<Self as Store>::SlashingSpans::get(&stash).map_or(
true,
|spans| submitted_in >= spans.last_start(),
)
});
(nominator, targets)
});
all_nominators.extend(nominator_votes);
let maybe_phragmen_result = phragmen::elect::<_, _, _, T::CurrencyToVote>(
Self::validator_count() as usize,
Self::minimum_validator_count().max(1) as usize,
all_validators,
all_nominators,
Self::slashable_balance_of,
if let Some(phragmen_result) = maybe_phragmen_result {
let elected_stashes = phragmen_result.winners.iter()
.map(|(s, _)| s.clone())
.collect::<Vec<T::AccountId>>();
let assignments = phragmen_result.assignments;
let to_votes = |b: BalanceOf<T>|
<T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(b) as ExtendedBalance;
let to_balance = |e: ExtendedBalance|
<T::CurrencyToVote as Convert<ExtendedBalance, BalanceOf<T>>>::convert(e);
let mut supports = phragmen::build_support_map::<_, _, _, T::CurrencyToVote>(
&elected_stashes,
&assignments,
Self::slashable_balance_of,
);
if cfg!(feature = "equalize") {
let mut staked_assignments
: Vec<(T::AccountId, Vec<PhragmenStakedAssignment<T::AccountId>>)>
= Vec::with_capacity(assignments.len());
for (n, assignment) in assignments.iter() {
let mut staked_assignment
: Vec<PhragmenStakedAssignment<T::AccountId>>
= Vec::with_capacity(assignment.len());
// If this is a self vote, then we don't need to equalise it at all. While the
// staking system does not allow nomination and validation at the same time,
// this must always be 100% support.
if assignment.len() == 1 && assignment[0].0 == *n {
continue;
}
for (c, per_thing) in assignment.iter() {
let nominator_stake = to_votes(Self::slashable_balance_of(n));
let other_stake = *per_thing * nominator_stake;
staked_assignment.push((c.clone(), other_stake));
}
staked_assignments.push((n.clone(), staked_assignment));
}
let tolerance = 0_u128;
let iterations = 2_usize;
phragmen::equalize::<_, _, T::CurrencyToVote, _>(
&mut supports,
tolerance,
iterations,
Self::slashable_balance_of,
);
for v in Self::current_elected().iter() {
<Stakers<T>>::remove(v);
// Populate Stakers and figure out the minimum stake behind a slot.
let mut slot_stake = BalanceOf::<T>::max_value();
for (c, s) in supports.into_iter() {
// build `struct exposure` from `support`
let exposure = Exposure {
own: to_balance(s.own),
// This might reasonably saturate and we cannot do much about it. The sum of
// someone's stake might exceed the balance type if they have the maximum amount
// of balance and receive some support. This is super unlikely to happen, yet
// we simulate it in some tests.
total: to_balance(s.total),
others: s.others
.into_iter()
.map(|(who, value)| IndividualExposure { who, value: to_balance(value) })
.collect::<Vec<IndividualExposure<_, _>>>(),
};
if exposure.total < slot_stake {
slot_stake = exposure.total;
<Stakers<T>>::insert(&c, exposure.clone());
<SlotStake<T>>::put(&slot_stake);
// Set the new validator set in sessions.
<CurrentElected<T>>::put(&elected_stashes);
// In order to keep the property required by `n_session_ending`
// that we must return the new validator set even if it's the same as the old,
// as long as any underlying economic conditions have changed, we don't attempt
// to do any optimization where we compare against the prior set.
(slot_stake, Some(elected_stashes))
} else {
// There were not enough candidates for even our minimal level of functionality.
// This is bad.
// We should probably disable all functionality except for block production
// and let the chain keep producing blocks until we can decide on a sufficiently
// substantial set.
/// Remove all associated data of a stash account from the staking system.
///
/// Assumes storage is upgraded before calling.
///
/// This is called :
/// - Immediately when an account's balance falls below existential deposit.
/// - after a `withdraw_unbond()` call that frees all of a stash's bonded balance.
fn kill_stash(stash: &T::AccountId) {
if let Some(controller) = <Bonded<T>>::take(stash) {
<Ledger<T>>::remove(&controller);
}
<Payee<T>>::remove(stash);
<Validators<T>>::remove(stash);
<Nominators<T>>::remove(stash);
slashing::clear_stash_metadata::<T>(stash);
}
/// Add reward points to validators using their stash account ID.
///
/// Validators are keyed by stash account ID and must be in the current elected set.
///
/// For each element in the iterator the given number of points in u32 is added to the
/// validator, thus duplicates are handled.
///
/// At the end of the era each the total payout will be distributed among validator
/// relatively to their points.
///
/// COMPLEXITY: Complexity is `number_of_validator_to_reward x current_elected_len`.
/// If you need to reward lots of validator consider using `reward_by_indices`.
pub fn reward_by_ids(validators_points: impl IntoIterator<Item = (T::AccountId, u32)>) {
CurrentEraPointsEarned::mutate(|rewards| {
let current_elected = <Module<T>>::current_elected();
for (validator, points) in validators_points.into_iter() {
if let Some(index) = current_elected.iter()
.position(|elected| *elected == validator)
{
rewards.add_points_to_index(index as u32, points);
}
}
});
}
/// Add reward points to validators using their validator index.
///
/// For each element in the iterator the given number of points in u32 is added to the
/// validator, thus duplicates are handled.
pub fn reward_by_indices(validators_points: impl IntoIterator<Item = (u32, u32)>) {
// TODO: This can be optimised once #3302 is implemented.
let current_elected_len = <Module<T>>::current_elected().len() as u32;
CurrentEraPointsEarned::mutate(|rewards| {
for (validator_index, points) in validators_points.into_iter() {
if validator_index < current_elected_len {
rewards.add_points_to_index(validator_index, points);
}
}
});
/// Ensures that at the end of the current session there will be a new era.
fn ensure_new_era() {
match ForceEra::get() {
Forcing::ForceAlways | Forcing::ForceNew => (),
_ => ForceEra::put(Forcing::ForceNew),
}
}
impl<T: Trait> session::OnSessionEnding<T::AccountId> for Module<T> {
fn on_session_ending(_ending: SessionIndex, start_session: SessionIndex) -> Option<Vec<T::AccountId>> {
Self::ensure_storage_upgraded();
Self::new_session(start_session - 1).map(|(new, _old)| new)
}
}
impl<T: Trait> OnSessionEnding<T::AccountId, Exposure<T::AccountId, BalanceOf<T>>> for Module<T> {
fn on_session_ending(_ending: SessionIndex, start_session: SessionIndex)
-> Option<(Vec<T::AccountId>, Vec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>)>)>
{
Self::ensure_storage_upgraded();
Self::new_session(start_session - 1)
impl<T: Trait> OnFreeBalanceZero<T::AccountId> for Module<T> {
fn on_free_balance_zero(stash: &T::AccountId) {
Self::ensure_storage_upgraded();
Self::kill_stash(stash);
/// Add reward points to block authors:
/// * 20 points to the block producer for producing a (non-uncle) block in the relay chain,
/// * 2 points to the block producer for each reference to a previously unreferenced uncle, and
/// * 1 point to the producer of each referenced uncle block.
impl<T: Trait + authorship::Trait> authorship::EventHandler<T::AccountId, T::BlockNumber> for Module<T> {
fn note_author(author: T::AccountId) {
}
fn note_uncle(author: T::AccountId, _age: T::BlockNumber) {
Self::reward_by_ids(vec![
(<authorship::Module<T>>::author(), 2),
(author, 1)
])
/// A `Convert` implementation that finds the stash of the given controller account,
/// if any.
pub struct StashOf<T>(sp_std::marker::PhantomData<T>);
impl<T: Trait> Convert<T::AccountId, Option<T::AccountId>> for StashOf<T> {
fn convert(controller: T::AccountId) -> Option<T::AccountId> {
<Module<T>>::ledger(&controller).map(|l| l.stash)
}
}
/// A typed conversion from stash account ID to the current exposure of nominators
/// on that account.
pub struct ExposureOf<T>(sp_std::marker::PhantomData<T>);
impl<T: Trait> Convert<T::AccountId, Option<Exposure<T::AccountId, BalanceOf<T>>>>
for ExposureOf<T>
{
fn convert(validator: T::AccountId) -> Option<Exposure<T::AccountId, BalanceOf<T>>> {
Some(<Module<T>>::stakers(&validator))
}
}
impl<T: Trait> SelectInitialValidators<T::AccountId> for Module<T> {
fn select_initial_validators() -> Option<Vec<T::AccountId>> {
<Module<T>>::select_validators().1
}
}
/// This is intended to be used with `FilterHistoricalOffences`.
impl <T: Trait> OnOffenceHandler<T::AccountId, session::historical::IdentificationTuple<T>> for Module<T> where
T: session::Trait<ValidatorId = <T as system::Trait>::AccountId>,
T: session::historical::Trait<
FullIdentification = Exposure<<T as system::Trait>::AccountId, BalanceOf<T>>,
FullIdentificationOf = ExposureOf<T>,
>,
T::SessionHandler: session::SessionHandler<<T as system::Trait>::AccountId>,
T::OnSessionEnding: session::OnSessionEnding<<T as system::Trait>::AccountId>,
T::SelectInitialValidators: session::SelectInitialValidators<<T as system::Trait>::AccountId>,
T::ValidatorIdOf: Convert<<T as system::Trait>::AccountId, Option<<T as system::Trait>::AccountId>>
{
fn on_offence(
offenders: &[OffenceDetails<T::AccountId, session::historical::IdentificationTuple<T>>],
slash_fraction: &[Perbill],
slash_session: SessionIndex,
<Module<T>>::ensure_storage_upgraded();
let reward_proportion = SlashRewardFraction::get();
let era_now = Self::current_era();
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
let window_start = era_now.saturating_sub(T::BondingDuration::get());
let current_era_start_session = CurrentEraStartSessionIndex::get();
// fast path for current-era report - most likely.
let slash_era = if slash_session >= current_era_start_session {
era_now
} else {
let eras = BondedEras::get();
// reverse because it's more likely to find reports from recent eras.
match eras.iter().rev().filter(|&&(_, ref sesh)| sesh <= &slash_session).next() {
None => return, // before bonding period. defensive - should be filtered out.
Some(&(ref slash_era, _)) => *slash_era,
}
};
<Self as Store>::EarliestUnappliedSlash::mutate(|earliest| {
if earliest.is_none() {
*earliest = Some(era_now)
}
});
let slash_defer_duration = T::SlashDeferDuration::get();
for (details, slash_fraction) in offenders.iter().zip(slash_fraction) {
let stash = &details.offender.0;
let exposure = &details.offender.1;
// Skip if the validator is invulnerable.
if Self::invulnerables().contains(stash) {
continue
}
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
let unapplied = slashing::compute_slash::<T>(slashing::SlashParams {
stash,
slash: *slash_fraction,
exposure,
slash_era,
window_start,
now: era_now,
reward_proportion,
});
if let Some(mut unapplied) = unapplied {
unapplied.reporters = details.reporters.clone();
if slash_defer_duration == 0 {
// apply right away.
slashing::apply_slash::<T>(unapplied);
} else {
// defer to end of some `slash_defer_duration` from now.
<Self as Store>::UnappliedSlashes::mutate(
era_now,
move |for_later| for_later.push(unapplied),
);
}
}
}
}
}
/// Filter historical offences out and only allow those from the bonding period.
pub struct FilterHistoricalOffences<T, R> {
_inner: sp_std::marker::PhantomData<(T, R)>,
}
impl<T, Reporter, Offender, R, O> ReportOffence<Reporter, Offender, O>
for FilterHistoricalOffences<Module<T>, R> where
T: Trait,
R: ReportOffence<Reporter, Offender, O>,
O: Offence<Offender>,
{
fn report_offence(reporters: Vec<Reporter>, offence: O) {
<Module<T>>::ensure_storage_upgraded();
// disallow any slashing from before the current bonding period.
let offence_session = offence.session_index();
let bonded_eras = BondedEras::get();
if bonded_eras.first().filter(|(_, start)| offence_session >= *start).is_some() {
R::report_offence(reporters, offence)
} else {
<Module<T>>::deposit_event(
RawEvent::OldSlashingReportDiscarded(offence_session)