Offences reporting and slashing (#3322)

* Remove offline slashing logic from staking.

* Initial version of reworked offence module, can report offences

* Clean up staking example.

* Commit SlashingOffence

* Force new era on slash.

* Add offenders in the SlashingOffence trait.

* Introduce the ReportOffence trait.

* Rename `Offence`.

* Add on_before_session_ending handler.

* Move offence related stuff under sr-primitives.

* Fix cargo check.

* Import new im-online implementation.

* Adding validator count to historical session storage as it's needed for slash calculations

* Add a comment about offence.

* Add BabeEquivocationOffence

* GrandpaEquivocationOffence

* slash_fraction and fix

* current_era_start_session_index

* UnresponsivnessOffence

* Finalise OnOffenceHandler traits, and stub impl for staking.

* slash_fraction doesn't really need &self

* Note that offenders count is greater than 0

* Add a test to ensure that I got the math right

* Use FullIdentification in offences.

* Use FullIndentification.

* Hook up the offences module.

* Report unresponsive validators

* Make sure eras have the same length.

* Slashing and rewards.

* Fix compilation.

* Distribute rewards.

* Supply validators_count

* Use identificationTuple in Unresponsivness report

* Fix merge.

* Make sure we don't slash if amount is zero.

* We don't return an error from report_offence anymo

* We actually can use vec!

* Prevent division by zero if the reporters is empty

* offence_forces_new_era/nominators_also_get_slashed

* advance_session

* Fix tests.

* Update srml/staking/src/lib.rs

Co-Authored-By: Robert Habermeier <rphmeier@gmail.com>

* slashing_performed_according_exposure

* Check that reporters receive their slice.

* Small clean-up.

* invulnerables_are_not_slashed

* Minor clean ups.

* Improve docs.

* dont_slash_if_fraction_is_zero

* Remove session dependency from offences.

* Introduce sr-staking-primitives

* Move offence under sr_staking_primitives

* rename session_index

* Resolves todos re using SessionIndex

* Fix staking tests.

* Properly scale denominator.

* Fix UnresponsivnessOffence

* Fix compilation.

* Tests for offences.

* Clean offences tests.

* Fix staking doc test.

* Bump spec version

* Fix aura tests.

* Fix node_executor

* Deposit an event on offence.

* Fix compilation of node-runtime

* Remove aura slashing logic.

* Remove HandleReport

* Update docs for timeslot.

* rename with_on_offence_fractions

* Add should_properly_count_offences

* Replace ValidatorIdByIndex with CurrentElectedSet

ValidatorIdByIndex was querying the current_elected set in each call, doing loading (even though its from cache), deserializing and cloning of element.

Instead of this it is more efficient to use `CurrentElectedSet`. As a small bonus, the invariant became a little bit easier: now we just rely on the fact that `keys` and `current_elected` set are of the same length rather than relying on the fact that `validator_id_by_index` would work similar to `<[T]>::get`.

* Clarify babe equivocation

* Fix offences.

* Rename validators_count to validator_set_count

* Fix squaring.

* Update core/sr-staking-primitives/src/offence.rs

Co-Authored-By: Gavin Wood <gavin@parity.io>

* Docs for CurrentElectedSet.

* Don't punish only invulnerables

* Use `get/insert` instead of `mutate`.

* Fix compilation

* Update core/sr-staking-primitives/src/offence.rs

Co-Authored-By: Gavin Wood <gavin@parity.io>

* Update srml/offences/src/lib.rs

Co-Authored-By: Robert Habermeier <rphmeier@gmail.com>

* Update srml/im-online/src/lib.rs

Co-Authored-By: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update srml/im-online/src/lib.rs

Co-Authored-By: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update srml/im-online/src/lib.rs

Co-Authored-By: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update srml/babe/src/lib.rs

Co-Authored-By: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update core/sr-staking-primitives/src/offence.rs

Co-Authored-By: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update core/sr-staking-primitives/src/offence.rs

Co-Authored-By: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update core/sr-staking-primitives/src/offence.rs

Co-Authored-By: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update core/sr-staking-primitives/src/offence.rs

Co-Authored-By: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update core/sr-staking-primitives/src/offence.rs

Co-Authored-By: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Add aura todo.

* Allow multiple reports for single offence report.

* Fix slash_fraction calculation.

* Fix typos.

* Fix compilation and tests.

* Fix staking tests.

* Update srml/im-online/src/lib.rs

Co-Authored-By: Logan Saether <x@logansaether.com>

* Fix doc on time_slot

* Allow slashing only on current era (#3411)

* only slash in current era

* prune journal for last era

* comment own_slash

* emit an event when old slashing events are discarded

* Pave the way for pruning

* Address issues.

* Try to refactor collect_offence_reports

* Other fixes.

* More fixes.
This commit is contained in:
Tomasz Drwięga
2019-08-16 19:54:50 +02:00
committed by Gavin Wood
parent 99f3f07690
commit 6cc4495700
37 changed files with 1775 additions and 597 deletions
+180 -230
View File
@@ -20,6 +20,7 @@ use super::*;
use runtime_io::with_externalities;
use phragmen;
use sr_primitives::traits::OnInitialize;
use sr_staking_primitives::offence::{OffenceDetails, OnOffenceHandler};
use srml_support::{assert_ok, assert_noop, assert_eq_uvec, EnumerableStorageMap};
use mock::*;
use srml_support::traits::{Currency, ReservableCurrency};
@@ -41,11 +42,11 @@ fn basic_setup_works() {
// Account 1 does not control any stash
assert_eq!(Staking::ledger(&1), None);
// ValidatorPrefs are default, thus unstake_threshold is 3, other values are default for their type
// ValidatorPrefs are default
assert_eq!(<Validators<Test>>::enumerate().collect::<Vec<_>>(), vec![
(31, ValidatorPrefs { unstake_threshold: 3, validator_payment: 0 }),
(21, ValidatorPrefs { unstake_threshold: 3, validator_payment: 0 }),
(11, ValidatorPrefs { unstake_threshold: 3, validator_payment: 0 })
(31, ValidatorPrefs::default()),
(21, ValidatorPrefs::default()),
(11, ValidatorPrefs::default())
]);
// Account 100 is the default nominator
@@ -83,9 +84,12 @@ fn basic_setup_works() {
// Initial Era and session
assert_eq!(Staking::current_era(), 0);
// initial slash_count of validators
assert_eq!(Staking::slash_count(&11), 0);
assert_eq!(Staking::slash_count(&21), 0);
// Account 10 has `balance_factor` free balance
assert_eq!(Balances::free_balance(&10), 1);
assert_eq!(Balances::free_balance(&10), 1);
// New era is not being forced
assert_eq!(Staking::force_era(), Forcing::NotForcing);
// All exposures must be correct.
check_exposure_all();
@@ -93,25 +97,6 @@ fn basic_setup_works() {
});
}
#[test]
fn no_offline_should_work() {
// Test the staking module works when no validators are offline
with_externalities(&mut ExtBuilder::default().build(),
|| {
// Slashing begins for validators immediately if found offline
assert_eq!(Staking::offline_slash_grace(), 0);
// Account 10 has not been reported offline
assert_eq!(Staking::slash_count(&10), 0);
// Account 10 has `balance_factor` free balance
assert_eq!(Balances::free_balance(&10), 1);
// Nothing happens to Account 10, as expected
assert_eq!(Staking::slash_count(&10), 0);
assert_eq!(Balances::free_balance(&10), 1);
// New era is not being forced
assert_eq!(Staking::force_era(), Forcing::NotForcing);
});
}
#[test]
fn change_controller_works() {
with_externalities(&mut ExtBuilder::default().build(),
@@ -135,183 +120,6 @@ fn change_controller_works() {
})
}
#[test]
fn invulnerability_should_work() {
// Test that users can be invulnerable from slashing and being kicked
with_externalities(&mut ExtBuilder::default().build(),
|| {
// Make account 11 invulnerable
assert_ok!(Staking::set_invulnerables(Origin::ROOT, vec![11]));
// Give account 11 some funds
let _ = Balances::make_free_balance_be(&11, 70);
// There is no slash grace -- slash immediately.
assert_eq!(Staking::offline_slash_grace(), 0);
// Account 11 has not been slashed
assert_eq!(Staking::slash_count(&11), 0);
// Account 11 has the 70 funds we gave it above
assert_eq!(Balances::free_balance(&11), 70);
// Account 11 should be a validator
assert!(<Validators<Test>>::exists(&11));
// Set account 11 as an offline validator with a large number of reports
// Should exit early if invulnerable
Staking::on_offline_validator(10, 100);
// Show that account 11 has not been touched
assert_eq!(Staking::slash_count(&11), 0);
assert_eq!(Balances::free_balance(&11), 70);
assert!(<Validators<Test>>::exists(&11));
// New era not being forced
// NOTE: new era is always forced once slashing happens -> new validators need to be chosen.
assert_eq!(Staking::force_era(), Forcing::NotForcing);
});
}
#[test]
fn offline_should_slash_and_disable() {
// Test that an offline validator gets slashed and kicked
with_externalities(&mut ExtBuilder::default().build(), || {
// Give account 10 some balance
let _ = Balances::make_free_balance_be(&11, 1000);
// Confirm account 10 is a validator
assert!(<Validators<Test>>::exists(&11));
// Validators get slashed immediately
assert_eq!(Staking::offline_slash_grace(), 0);
// Unstake threshold is 3
assert_eq!(Staking::validators(&11).unstake_threshold, 3);
// Account 10 has not been slashed before
assert_eq!(Staking::slash_count(&11), 0);
// Account 10 has the funds we just gave it
assert_eq!(Balances::free_balance(&11), 1000);
// Account 10 is not yet disabled.
assert!(!is_disabled(10));
// Report account 10 as offline, one greater than unstake threshold
Staking::on_offline_validator(10, 4);
// Confirm user has been reported
assert_eq!(Staking::slash_count(&11), 4);
// Confirm balance has been reduced by 2^unstake_threshold * offline_slash() * amount_at_stake.
let slash_base = Staking::offline_slash() * Staking::stakers(11).total;
assert_eq!(Balances::free_balance(&11), 1000 - 2_u64.pow(3) * slash_base);
// Confirm account 10 has been disabled.
assert!(is_disabled(10));
});
}
#[test]
fn offline_grace_should_delay_slashing() {
// Tests that with grace, slashing is delayed
with_externalities(&mut ExtBuilder::default().build(), || {
// Initialize account 10 with balance
let _ = Balances::make_free_balance_be(&11, 70);
// Verify account 11 has balance
assert_eq!(Balances::free_balance(&11), 70);
// Set offline slash grace
let offline_slash_grace = 1;
assert_ok!(Staking::set_offline_slash_grace(Origin::ROOT, offline_slash_grace));
assert_eq!(Staking::offline_slash_grace(), 1);
// Check unstake_threshold is 3 (default)
let default_unstake_threshold = 3;
assert_eq!(
Staking::validators(&11),
ValidatorPrefs { unstake_threshold: default_unstake_threshold, validator_payment: 0 }
);
// Check slash count is zero
assert_eq!(Staking::slash_count(&11), 0);
// Report account 10 up to the threshold
Staking::on_offline_validator(10, default_unstake_threshold as usize + offline_slash_grace as usize);
// Confirm slash count
assert_eq!(Staking::slash_count(&11), 4);
// Nothing should happen
assert_eq!(Balances::free_balance(&11), 70);
// Report account 10 one more time
Staking::on_offline_validator(10, 1);
assert_eq!(Staking::slash_count(&11), 5);
// User gets slashed
assert!(Balances::free_balance(&11) < 70);
// New era is forced
assert!(is_disabled(10));
});
}
#[test]
fn max_unstake_threshold_works() {
// Tests that max_unstake_threshold gets used when prefs.unstake_threshold is large
with_externalities(&mut ExtBuilder::default().build(), || {
const MAX_UNSTAKE_THRESHOLD: u32 = 10;
// Two users with maximum possible balance
let _ = Balances::make_free_balance_be(&11, u64::max_value());
let _ = Balances::make_free_balance_be(&21, u64::max_value());
// Give them full exposure as a staker
<Stakers<Test>>::insert(&11, Exposure { total: 1000000, own: 1000000, others: vec![]});
<Stakers<Test>>::insert(&21, Exposure { total: 2000000, own: 2000000, others: vec![]});
// Check things are initialized correctly
assert_eq!(Balances::free_balance(&11), u64::max_value());
assert_eq!(Balances::free_balance(&21), u64::max_value());
assert_eq!(Staking::offline_slash_grace(), 0);
// Account 10 will have max unstake_threshold
assert_ok!(Staking::validate(Origin::signed(10), ValidatorPrefs {
unstake_threshold: MAX_UNSTAKE_THRESHOLD,
validator_payment: 0,
}));
// Account 20 could not set their unstake_threshold past 10
assert_noop!(Staking::validate(Origin::signed(20), ValidatorPrefs {
unstake_threshold: MAX_UNSTAKE_THRESHOLD + 1,
validator_payment: 0}),
"unstake threshold too large"
);
// Give Account 20 unstake_threshold 11 anyway, should still be limited to 10
<Validators<Test>>::insert(21, ValidatorPrefs {
unstake_threshold: MAX_UNSTAKE_THRESHOLD + 1,
validator_payment: 0,
});
OfflineSlash::put(Perbill::from_fraction(0.0001));
// Report each user 1 more than the max_unstake_threshold
Staking::on_offline_validator(10, MAX_UNSTAKE_THRESHOLD as usize + 1);
Staking::on_offline_validator(20, MAX_UNSTAKE_THRESHOLD as usize + 1);
// Show that each balance only gets reduced by 2^max_unstake_threshold times 10%
// of their total stake.
assert_eq!(Balances::free_balance(&11), u64::max_value() - 2_u64.pow(MAX_UNSTAKE_THRESHOLD) * 100);
assert_eq!(Balances::free_balance(&21), u64::max_value() - 2_u64.pow(MAX_UNSTAKE_THRESHOLD) * 200);
});
}
#[test]
fn slashing_does_not_cause_underflow() {
// Tests that slashing more than a user has does not underflow
with_externalities(&mut ExtBuilder::default().build(), || {
// Verify initial conditions
assert_eq!(Balances::free_balance(&11), 1000);
assert_eq!(Staking::offline_slash_grace(), 0);
// Set validator preference so that 2^unstake_threshold would cause overflow (greater than 64)
// FIXME: that doesn't overflow.
<Validators<Test>>::insert(11, ValidatorPrefs {
unstake_threshold: 10,
validator_payment: 0,
});
System::set_block_number(1);
Session::on_initialize(System::block_number());
// Should not panic
Staking::on_offline_validator(10, 100);
// Confirm that underflow has not occurred, and account balance is set to zero
assert_eq!(Balances::free_balance(&11), 0);
});
}
#[test]
fn rewards_should_work() {
// should check that:
@@ -748,13 +556,12 @@ fn nominating_and_rewards_should_work() {
#[test]
fn nominators_also_get_slashed() {
// A nominator should be slashed if the validator they nominated is slashed
// Here is the breakdown of roles:
// 10 - is the controller of 11
// 11 - is the stash.
// 2 - is the nominator of 20, 10
with_externalities(&mut ExtBuilder::default().nominate(false).build(), || {
assert_eq!(Staking::validator_count(), 2);
// slash happens immediately.
assert_eq!(Staking::offline_slash_grace(), 0);
// Account 10 has not been reported offline
assert_eq!(Staking::slash_count(&10), 0);
OfflineSlash::put(Perbill::from_percent(12));
// Set payee to controller
assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller));
@@ -765,7 +572,7 @@ fn nominators_also_get_slashed() {
let _ = Balances::make_free_balance_be(i, initial_balance);
}
// 2 will nominate for 10
// 2 will nominate for 10, 20
let nominator_stake = 500;
assert_ok!(Staking::bond(Origin::signed(1), 2, nominator_stake, RewardDestination::default()));
assert_ok!(Staking::nominate(Origin::signed(2), vec![20, 10]));
@@ -781,15 +588,24 @@ fn nominators_also_get_slashed() {
assert_eq!(Balances::total_balance(&2), initial_balance);
// 10 goes offline
Staking::on_offline_validator(10, 4);
let expo = Staking::stakers(10);
let slash_value = Staking::offline_slash() * expo.total * 2_u64.pow(3);
Staking::on_offence(
&[OffenceDetails {
offender: (
11,
Staking::stakers(&11),
),
reporters: vec![],
}],
&[Perbill::from_percent(5)],
);
let expo = Staking::stakers(11);
let slash_value = 50;
let total_slash = expo.total.min(slash_value);
let validator_slash = expo.own.min(total_slash);
let nominator_slash = nominator_stake.min(total_slash - validator_slash);
// initial + first era reward + slash
assert_eq!(Balances::total_balance(&10), initial_balance + total_payout - validator_slash);
assert_eq!(Balances::total_balance(&11), initial_balance - validator_slash);
assert_eq!(Balances::total_balance(&2), initial_balance - nominator_slash);
check_exposure_all();
check_nominator_all();
@@ -907,10 +723,11 @@ fn forcing_new_era_works() {
start_session(6);
assert_eq!(Staking::current_era(), 1);
// back to normal
// back to normal.
// this immediatelly starts a new session.
ForceEra::put(Forcing::NotForcing);
start_session(7);
assert_eq!(Staking::current_era(), 1);
assert_eq!(Staking::current_era(), 2);
start_session(8);
assert_eq!(Staking::current_era(), 2);
@@ -1100,7 +917,6 @@ fn validator_payment_prefs_work() {
});
<Payee<Test>>::insert(&2, RewardDestination::Stash);
<Validators<Test>>::insert(&11, ValidatorPrefs {
unstake_threshold: 3,
validator_payment: validator_cut
});
@@ -1337,13 +1153,6 @@ fn slot_stake_is_least_staked_validator_and_exposure_defines_maximum_punishment(
// -- slot stake should also be updated.
assert_eq!(Staking::slot_stake(), 69 + total_payout_0/2);
// If 10 gets slashed now, it will be slashed by 5% of exposure.total * 2.pow(unstake_thresh)
Staking::on_offline_validator(10, 4);
// Confirm user has been reported
assert_eq!(Staking::slash_count(&11), 4);
// check the balance of 10 (slash will be deducted from free balance.)
assert_eq!(Balances::free_balance(&11), _11_balance - _11_balance*5/100 * 2u64.pow(3));
check_exposure_all();
check_nominator_all();
});
@@ -1365,8 +1174,6 @@ fn on_free_balance_zero_stash_removes_validator() {
assert_eq!(Staking::bonded(&11), Some(10));
// Set some storage items which we expect to be cleaned up
// Initiate slash count storage item
Staking::on_offline_validator(10, 1);
// Set payee information
assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Stash));
@@ -1374,7 +1181,6 @@ fn on_free_balance_zero_stash_removes_validator() {
assert!(<Ledger<Test>>::exists(&10));
assert!(<Bonded<Test>>::exists(&11));
assert!(<Validators<Test>>::exists(&11));
assert!(<SlashCount<Test>>::exists(&11));
assert!(<Payee<Test>>::exists(&11));
// Reduce free_balance of controller to 0
@@ -1389,7 +1195,6 @@ fn on_free_balance_zero_stash_removes_validator() {
assert!(<Ledger<Test>>::exists(&10));
assert!(<Bonded<Test>>::exists(&11));
assert!(<Validators<Test>>::exists(&11));
assert!(<SlashCount<Test>>::exists(&11));
assert!(<Payee<Test>>::exists(&11));
// Reduce free_balance of stash to 0
@@ -1402,7 +1207,6 @@ fn on_free_balance_zero_stash_removes_validator() {
assert!(!<Bonded<Test>>::exists(&11));
assert!(!<Validators<Test>>::exists(&11));
assert!(!<Nominators<Test>>::exists(&11));
assert!(!<SlashCount<Test>>::exists(&11));
assert!(!<Payee<Test>>::exists(&11));
});
}
@@ -1459,7 +1263,6 @@ fn on_free_balance_zero_stash_removes_nominator() {
assert!(!<Bonded<Test>>::exists(&11));
assert!(!<Validators<Test>>::exists(&11));
assert!(!<Nominators<Test>>::exists(&11));
assert!(!<SlashCount<Test>>::exists(&11));
assert!(!<Payee<Test>>::exists(&11));
});
}
@@ -2107,7 +1910,7 @@ fn reward_validator_slashing_validator_doesnt_overflow() {
]});
// Check slashing
Staking::slash_validator(&11, reward_slash);
let _ = Staking::slash_validator(&11, reward_slash, &Staking::stakers(&11), &mut Vec::new());
assert_eq!(Balances::total_balance(&11), stake - 1);
assert_eq!(Balances::total_balance(&2), 1);
})
@@ -2180,3 +1983,150 @@ fn unbonded_balance_is_not_slashable() {
assert_eq!(Staking::slashable_balance_of(&11), 200);
})
}
#[test]
fn era_is_always_same_length() {
// This ensures that the sessions is always of the same length if there is no forcing no
// session changes.
with_externalities(&mut ExtBuilder::default().build(), || {
start_era(1);
assert_eq!(Staking::current_era_start_session_index(), SessionsPerEra::get());
start_era(2);
assert_eq!(Staking::current_era_start_session_index(), SessionsPerEra::get() * 2);
let session = Session::current_index();
ForceEra::put(Forcing::ForceNew);
advance_session();
assert_eq!(Staking::current_era(), 3);
assert_eq!(Staking::current_era_start_session_index(), session + 1);
start_era(4);
assert_eq!(Staking::current_era_start_session_index(), session + SessionsPerEra::get() + 1);
});
}
#[test]
fn offence_forces_new_era() {
with_externalities(&mut ExtBuilder::default().build(), || {
Staking::on_offence(
&[OffenceDetails {
offender: (
11,
Staking::stakers(&11),
),
reporters: vec![],
}],
&[Perbill::from_percent(5)],
);
assert_eq!(Staking::force_era(), Forcing::ForceNew);
});
}
#[test]
fn slashing_performed_according_exposure() {
// This test checks that slashing is performed according the exposure (or more precisely,
// historical exposure), not the current balance.
with_externalities(&mut ExtBuilder::default().build(), || {
assert_eq!(Staking::stakers(&11).own, 1000);
// Handle an offence with a historical exposure.
Staking::on_offence(
&[OffenceDetails {
offender: (
11,
Exposure {
total: 500,
own: 500,
others: vec![],
},
),
reporters: vec![],
}],
&[Perbill::from_percent(50)],
);
// The stash account should be slashed for 250 (50% of 500).
assert_eq!(Balances::free_balance(&11), 1000 - 250);
});
}
#[test]
fn reporters_receive_their_slice() {
// This test verifies that the reporters of the offence receive their slice from the slashed
// amount.
with_externalities(&mut ExtBuilder::default().build(), || {
// The reporters' reward is calculated from the total exposure.
assert_eq!(Staking::stakers(&11).total, 1250);
Staking::on_offence(
&[OffenceDetails {
offender: (
11,
Staking::stakers(&11),
),
reporters: vec![1, 2],
}],
&[Perbill::from_percent(50)],
);
// 1250 x 50% (slash fraction) x 10% (rewards slice)
assert_eq!(Balances::free_balance(&1), 10 + 31);
assert_eq!(Balances::free_balance(&2), 20 + 31);
});
}
#[test]
fn invulnerables_are_not_slashed() {
// For invulnerable validators no slashing is performed.
with_externalities(
&mut ExtBuilder::default().invulnerables(vec![11]).build(),
|| {
assert_eq!(Balances::free_balance(&11), 1000);
assert_eq!(Balances::free_balance(&21), 2000);
assert_eq!(Staking::stakers(&21).total, 1250);
Staking::on_offence(
&[
OffenceDetails {
offender: (11, Staking::stakers(&11)),
reporters: vec![],
},
OffenceDetails {
offender: (21, Staking::stakers(&21)),
reporters: vec![],
},
],
&[Perbill::from_percent(50), Perbill::from_percent(20)],
);
// The validator 11 hasn't been slashed, but 21 has been.
assert_eq!(Balances::free_balance(&11), 1000);
assert_eq!(Balances::free_balance(&21), 1750); // 2000 - (0.2 * 1250)
},
);
}
#[test]
fn dont_slash_if_fraction_is_zero() {
// Don't slash if the fraction is zero.
with_externalities(&mut ExtBuilder::default().build(), || {
assert_eq!(Balances::free_balance(&11), 1000);
Staking::on_offence(
&[OffenceDetails {
offender: (
11,
Staking::stakers(&11),
),
reporters: vec![],
}],
&[Perbill::from_percent(0)],
);
// The validator hasn't been slashed. The new era is not forced.
assert_eq!(Balances::free_balance(&11), 1000);
assert_eq!(Staking::force_era(), Forcing::NotForcing);
});
}