diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 21400b0b8d..b09d026eb3 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -473,7 +473,7 @@ impl ExtBuilder { (41, balance_factor * 2000), (100, 2000 * balance_factor), (101, 2000 * balance_factor), - // This allow us to have a total_payout different from 0. + // This allows us to have a total_payout different from 0. (999, 1_000_000_000_000), ], }.assimilate_storage(&mut storage); @@ -1035,3 +1035,7 @@ pub(crate) fn staking_events() -> Vec> { } }).collect() } + +pub(crate) fn balances(who: &AccountId) -> (Balance, Balance) { + (Balances::free_balance(who), Balances::reserved_balance(who)) +} diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index 73fcb6c2af..9cda151b70 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -28,11 +28,9 @@ use frame_support::{ }; use pallet_balances::Error as BalancesError; use substrate_test_utils::assert_eq_uvec; -use crate::Store; #[test] fn force_unstake_works() { - // Verifies initial conditions of mock ExtBuilder::default().build_and_execute(|| { // Account 11 is stashed and locked, and account 10 is the controller assert_eq!(Staking::bonded(&11), Some(10)); @@ -128,17 +126,18 @@ fn basic_setup_works() { #[test] fn change_controller_works() { ExtBuilder::default().build_and_execute(|| { + // 10 and 11 are bonded as stash controller. assert_eq!(Staking::bonded(&11), Some(10)); - assert!(Session::validators().contains(&11)); // 10 can control 11 who is initially a validator. assert_ok!(Staking::chill(Origin::signed(10))); - assert!(Session::validators().contains(&11)); + // change controller assert_ok!(Staking::set_controller(Origin::signed(11), 5)); - + assert_eq!(Staking::bonded(&11), Some(5)); mock::start_era(1); + // 10 is no longer in control. assert_noop!( Staking::validate(Origin::signed(10), ValidatorPrefs::default()), Error::::NotController, @@ -533,60 +532,71 @@ 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 - ExtBuilder::default().nominate(false).build_and_execute(|| { - assert_eq!(Staking::validator_count(), 2); - - // Set payee to controller - assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); - - // give the man some money. - let initial_balance = 1000; - for i in [1, 2, 3, 10].iter() { - let _ = Balances::make_free_balance_be(i, initial_balance); - } - - // 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])); - - let total_payout = current_total_payout_for_duration(3000); - assert!(total_payout > 100); // Test is meaningful if reward something - >::reward_by_ids(vec![(11, 1)]); - - // new era, pay rewards, +fn nominators_also_get_slashed_pro_rata() { + ExtBuilder::default().build_and_execute(|| { mock::start_era(1); + let slash_percent = Perbill::from_percent(5); + let initial_exposure = Staking::eras_stakers(active_era(), 11); + // 101 is a nominator for 11 + assert_eq!( + initial_exposure.others.first().unwrap().who, + 101, + ); - // Nominator stash didn't collect any. - assert_eq!(Balances::total_balance(&2), initial_balance); + // staked values; + let nominator_stake = Staking::ledger(100).unwrap().active; + let nominator_balance = balances(&101).0; + let validator_stake = Staking::ledger(10).unwrap().active; + let validator_balance = balances(&11).0; + let exposed_stake = initial_exposure.total; + let exposed_validator = initial_exposure.own; + let exposed_nominator = initial_exposure.others.first().unwrap().value; - // 10 goes offline + // 11 goes offline on_offence_now( &[OffenceDetails { offender: ( 11, - Staking::eras_stakers(Staking::active_era().unwrap().index, 11), + initial_exposure.clone(), ), reporters: vec![], }], - &[Perbill::from_percent(5)], + &[slash_percent], ); - let expo = Staking::eras_stakers(Staking::active_era().unwrap().index, 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(&11), initial_balance - validator_slash); - assert_eq!(Balances::total_balance(&2), initial_balance - nominator_slash); + // both stakes must have been decreased. + assert!(Staking::ledger(100).unwrap().active < nominator_stake); + assert!(Staking::ledger(10).unwrap().active < validator_stake); + let slash_amount = slash_percent * exposed_stake; + let validator_share = + Perbill::from_rational_approximation(exposed_validator, exposed_stake) * slash_amount; + let nominator_share = Perbill::from_rational_approximation( + exposed_nominator, + exposed_stake, + ) * slash_amount; + + // both slash amounts need to be positive for the test to make sense. + assert!(validator_share > 0); + assert!(nominator_share > 0); + + // both stakes must have been decreased pro-rata. + assert_eq!( + Staking::ledger(100).unwrap().active, + nominator_stake - nominator_share, + ); + assert_eq!( + Staking::ledger(10).unwrap().active, + validator_stake - validator_share, + ); + assert_eq!( + balances(&101).0, // free balance + nominator_balance - nominator_share, + ); + assert_eq!( + balances(&11).0, // free balance + validator_balance - validator_share, + ); // Because slashing happened. assert!(is_disabled(10)); });