// This file is part of Bizinikiwi. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use super::*; use crate::session_rotation::Eras; use pezframe_support::dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo}; use pezsp_runtime::{bounded_btree_map, traits::Dispatchable}; #[test] fn rewards_with_nominator_should_work() { ExtBuilder::default().nominate(true).session_per_era(3).build_and_execute(|| { let init_balance_11 = asset::total_balance::(&11); let init_balance_21 = asset::total_balance::(&21); let init_balance_101 = asset::total_balance::(&101); // Set payees Payee::::insert(11, RewardDestination::Account(11)); Payee::::insert(21, RewardDestination::Account(21)); Payee::::insert(101, RewardDestination::Account(101)); Eras::::reward_active_era(vec![(11, 50)]); Eras::::reward_active_era(vec![(11, 50)]); // This is the second validator of the current elected set. Eras::::reward_active_era(vec![(21, 50)]); // Compute total payout now for whole duration of the session. let validator_payout_0 = validator_payout_for(time_per_era()); let maximum_payout = total_payout_for(time_per_era()); assert_eq_uvec!(Session::validators(), vec![11, 21]); assert_eq!(asset::total_balance::(&11), init_balance_11); assert_eq!(asset::total_balance::(&21), init_balance_21); assert_eq!(asset::total_balance::(&101), init_balance_101); assert_eq!( ErasRewardPoints::::get(active_era()), EraRewardPoints { total: 50 * 3, individual: bounded_btree_map![11 => 100, 21 => 50] } ); let part_for_11 = Perbill::from_rational::(1000, 1250); let part_for_21 = Perbill::from_rational::(1000, 1250); let part_for_101_from_11 = Perbill::from_rational::(250, 1250); let part_for_101_from_21 = Perbill::from_rational::(250, 1250); Session::roll_until_active_era(2); assert_eq!( staking_events_since_last_call(), vec![ Event::SessionRotated { starting_session: 4, active_era: 1, planned_era: 2 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 5, active_era: 1, planned_era: 2 }, Event::EraPaid { era_index: 1, validator_payout: validator_payout_0, remainder: maximum_payout - validator_payout_0 }, Event::SessionRotated { starting_session: 6, active_era: 2, planned_era: 2 } ] ); assert_eq!(mock::RewardRemainderUnbalanced::get(), maximum_payout - validator_payout_0); // make note of total issuance before rewards. let pre_issuance = asset::total_issuance::(); mock::make_all_reward_payment(1); assert_eq!( mock::staking_events_since_last_call(), vec![ Event::PayoutStarted { era_index: 1, validator_stash: 11, page: 0, next: None }, Event::Rewarded { stash: 11, dest: RewardDestination::Account(11), amount: 4000 }, Event::Rewarded { stash: 101, dest: RewardDestination::Account(101), amount: 1000 }, Event::PayoutStarted { era_index: 1, validator_stash: 21, page: 0, next: None }, Event::Rewarded { stash: 21, dest: RewardDestination::Account(21), amount: 2000 }, Event::Rewarded { stash: 101, dest: RewardDestination::Account(101), amount: 500 } ] ); // total issuance should have increased let post_issuance = asset::total_issuance::(); assert_eq!(post_issuance, pre_issuance + validator_payout_0); assert_eq_error_rate!( asset::total_balance::(&11), init_balance_11 + part_for_11 * validator_payout_0 * 2 / 3, 2, ); assert_eq_error_rate!( asset::total_balance::(&21), init_balance_21 + part_for_21 * validator_payout_0 * 1 / 3, 2, ); assert_eq_error_rate!( asset::total_balance::(&101), init_balance_101 + part_for_101_from_11 * validator_payout_0 * 2 / 3 + part_for_101_from_21 * validator_payout_0 * 1 / 3, 2 ); assert_eq_uvec!(Session::validators(), vec![11, 21]); Eras::::reward_active_era(vec![(11, 1)]); // Compute total payout now for whole duration as other parameter won't change let total_payout_1 = validator_payout_for(time_per_era()); Session::roll_until_active_era(3); assert_eq!( mock::RewardRemainderUnbalanced::get(), maximum_payout * 2 - validator_payout_0 - total_payout_1, ); assert_eq!( mock::staking_events_since_last_call(), vec![ Event::SessionRotated { starting_session: 7, active_era: 2, planned_era: 3 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 8, active_era: 2, planned_era: 3 }, Event::EraPaid { era_index: 2, validator_payout: 7500, remainder: 7500 }, Event::SessionRotated { starting_session: 9, active_era: 3, planned_era: 3 } ] ); mock::make_all_reward_payment(2); assert_eq!(asset::total_issuance::(), post_issuance + total_payout_1); assert_eq_error_rate!( asset::total_balance::(&11), init_balance_11 + part_for_11 * (validator_payout_0 * 2 / 3 + total_payout_1), 2, ); assert_eq_error_rate!( asset::total_balance::(&21), init_balance_21 + part_for_21 * validator_payout_0 * 1 / 3, 2, ); assert_eq_error_rate!( asset::total_balance::(&101), init_balance_101 + part_for_101_from_11 * (validator_payout_0 * 2 / 3 + total_payout_1) + part_for_101_from_21 * validator_payout_0 * 1 / 3, 2 ); }); } #[test] fn rewards_no_nominator_should_work() { ExtBuilder::default().nominate(false).build_and_execute(|| { assert_eq_uvec!(Session::validators(), vec![11, 21]); // with no backers assert_eq_uvec!( era_exposures(1), vec![ (11, Exposure:: { total: 1000, own: 1000, others: vec![] }), (21, Exposure:: { total: 1000, own: 1000, others: vec![] }) ] ); // give them some points reward_all_elected(); // go to next active era Session::roll_until_active_era(2); let _ = staking_events_since_last_call(); // payout era 1 make_all_reward_payment(1); // payout works assert_eq!( staking_events_since_last_call(), vec![ Event::PayoutStarted { era_index: 1, validator_stash: 11, page: 0, next: None }, Event::Rewarded { stash: 11, dest: RewardDestination::Staked, amount: 3750 }, Event::PayoutStarted { era_index: 1, validator_stash: 21, page: 0, next: None }, Event::Rewarded { stash: 21, dest: RewardDestination::Staked, amount: 3750 } ] ); }); } #[test] fn nominating_and_rewards_should_work() { ExtBuilder::default() .nominate(false) .set_status(41, StakerStatus::Validator) .build_and_execute(|| { // initial validators, note that 41 has more stake than 11 assert_eq_uvec!(Session::validators(), vec![41, 21]); // bond two monitors, both favouring 11 bond_nominator(1, 5000, vec![11, 41]); bond_virtual_nominator(3, 333, 5000, vec![11]); assert_eq!( staking_events_since_last_call(), vec![ Event::Bonded { stash: 1, amount: 5000 }, Event::Bonded { stash: 3, amount: 5000 }, ] ); // reward our two winning validators Eras::::reward_active_era(vec![(41, 1)]); Eras::::reward_active_era(vec![(21, 1)]); Session::roll_until_active_era(2); assert_eq!( staking_events_since_last_call(), vec![ Event::SessionRotated { starting_session: 4, active_era: 1, planned_era: 2 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 5, active_era: 1, planned_era: 2 }, Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 7500 }, Event::SessionRotated { starting_session: 6, active_era: 2, planned_era: 2 } ] ); // 11 now has more votes assert_eq_uvec!(Session::validators(), vec![11, 41]); assert_eq!(ErasStakersPaged::::iter_prefix_values((active_era(),)).count(), 2); assert_eq!( Staking::eras_stakers(active_era(), &11), Exposure { total: 7500, own: 1000, others: vec![ IndividualExposure { who: 1, value: 1500 }, IndividualExposure { who: 3, value: 5000 } ] } ); assert_eq!( Staking::eras_stakers(active_era(), &41), Exposure { total: 7500, own: 4000, others: vec![IndividualExposure { who: 1, value: 3500 }] } ); // payout era 1, in which 21 and 41 were validators with no nominators. mock::make_all_reward_payment(1); assert_eq!( staking_events_since_last_call(), vec![ Event::PayoutStarted { era_index: 1, validator_stash: 21, page: 0, next: None }, Event::Rewarded { stash: 21, dest: RewardDestination::Staked, amount: 3750 }, Event::PayoutStarted { era_index: 1, validator_stash: 41, page: 0, next: None }, Event::Rewarded { stash: 41, dest: RewardDestination::Staked, amount: 3750 } ] ); reward_all_elected(); Session::roll_until_active_era(3); // ignore session rotation events, we've seen them before. let _ = staking_events_since_last_call(); // for era 2 we had a nominator too, who is rewarded. mock::make_all_reward_payment(2); assert_eq!( staking_events_since_last_call(), vec![ Event::PayoutStarted { era_index: 2, validator_stash: 11, page: 0, next: None }, Event::Rewarded { stash: 11, dest: RewardDestination::Staked, amount: 500 }, Event::Rewarded { stash: 1, dest: RewardDestination::Stash, amount: 750 }, Event::Rewarded { stash: 3, dest: RewardDestination::Account(333), amount: 2500 }, Event::PayoutStarted { era_index: 2, validator_stash: 41, page: 0, next: None }, Event::Rewarded { stash: 41, dest: RewardDestination::Staked, amount: 2000 }, Event::Rewarded { stash: 1, dest: RewardDestination::Stash, amount: 1750 } ] ); }); } #[test] fn reward_destination_staked() { ExtBuilder::default().nominate(false).build_and_execute(|| { // initial conditions assert!(Session::validators().contains(&11)); assert_eq!(Staking::payee(11.into()), Some(RewardDestination::Staked)); assert_eq!(asset::total_balance::(&11), 1001); assert_eq!( Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, active: 1000, unlocking: Default::default(), } ); // reward era 1 and payout at era 2 Eras::::reward_active_era(vec![(11, 1)]); Session::roll_until_active_era(2); let _ = staking_events_since_last_call(); mock::make_all_reward_payment(1); assert_eq!(ClaimedRewards::::get(1, &11), vec![0]); assert_eq!( staking_events_since_last_call(), vec![ Event::PayoutStarted { era_index: 1, validator_stash: 11, page: 0, next: None }, Event::Rewarded { stash: 11, dest: RewardDestination::Staked, amount: 7500 } ] ); // ledger must have been increased assert_eq!( Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 8500, active: 8500, unlocking: Default::default(), } ); // balance also updated assert_eq!(asset::total_balance::(&11), 1001 + 7500); }); } #[test] fn reward_to_stake_works() { ExtBuilder::default() .nominate(false) .set_status(31, StakerStatus::Idle) .set_status(41, StakerStatus::Idle) .set_stake(21, 2000) .try_state(false) .build_and_execute(|| { assert_eq!(ValidatorCount::::get(), 2); // Confirm account 10 and 20 are validators assert!(>::contains_key(&11) && >::contains_key(&21)); assert_eq!(Staking::eras_stakers(active_era(), &11).total, 1000); assert_eq!(Staking::eras_stakers(active_era(), &21).total, 2000); // Give the man some money. let _ = asset::set_stakeable_balance::(&10, 1000); let _ = asset::set_stakeable_balance::(&20, 1000); // Bypass logic and change current exposure Eras::::upsert_exposure(0, &21, Exposure { total: 69, own: 69, others: vec![] }); >::insert( &20, StakingLedgerInspect { stash: 21, total: 69, active: 69, unlocking: Default::default(), }, ); // Compute total payout now for whole duration as other parameter won't change let validator_payout_0 = validator_payout_for(time_per_era()); Pezpallet::::reward_by_ids(vec![(11, 1)]); Pezpallet::::reward_by_ids(vec![(21, 1)]); // New era --> rewards are paid --> stakes are changed Session::roll_until_active_era(2); make_all_reward_payment(1); assert_eq!(Staking::eras_stakers(active_era(), &11).total, 1000); assert_eq!(Staking::eras_stakers(active_era(), &21).total, 2000); let _11_balance = asset::stakeable_balance::(&11); assert_eq!(_11_balance, 1000 + validator_payout_0 / 2); // Trigger another new era as the info are frozen before the era start. Session::roll_until_active_era(3); // -- new infos assert_eq!( Staking::eras_stakers(active_era(), &11).total, 1000 + validator_payout_0 / 2 ); assert_eq!( Staking::eras_stakers(active_era(), &21).total, 2000 + validator_payout_0 / 2 ); }); } #[test] fn reward_destination_stash() { ExtBuilder::default().nominate(false).build_and_execute(|| { // initial conditions assert!(Session::validators().contains(&11)); assert_ok!(Staking::set_payee(RuntimeOrigin::signed(11), RewardDestination::Stash)); assert_eq!(asset::total_balance::(&11), 1001); assert_eq!( Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, active: 1000, unlocking: Default::default(), } ); // reward era 1 and payout at era 2 Eras::::reward_active_era(vec![(11, 1)]); Session::roll_until_active_era(2); let _ = staking_events_since_last_call(); mock::make_all_reward_payment(1); assert_eq!(ClaimedRewards::::get(1, &11), vec![0]); assert_eq!( staking_events_since_last_call(), vec![ Event::PayoutStarted { era_index: 1, validator_stash: 11, page: 0, next: None }, Event::Rewarded { stash: 11, dest: RewardDestination::Stash, amount: 7500 } ] ); // ledger same, balance increased assert_eq!(asset::total_balance::(&11), 1001 + 7500); assert_eq!( Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, active: 1000, unlocking: Default::default(), } ); }); } #[test] fn reward_destination_account() { ExtBuilder::default().nominate(false).build_and_execute(|| { // initial conditions assert!(Session::validators().contains(&11)); assert_ok!(Staking::set_payee(RuntimeOrigin::signed(11), RewardDestination::Account(7))); assert_eq!(asset::total_balance::(&11), 1001); assert_eq!( Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, active: 1000, unlocking: Default::default(), } ); // reward era 1 and payout at era 2 Eras::::reward_active_era(vec![(11, 1)]); Session::roll_until_active_era(2); let _ = staking_events_since_last_call(); mock::make_all_reward_payment(1); assert_eq!(ClaimedRewards::::get(1, &11), vec![0]); assert_eq!( staking_events_since_last_call(), vec![ Event::PayoutStarted { era_index: 1, validator_stash: 11, page: 0, next: None }, Event::Rewarded { stash: 11, dest: RewardDestination::Account(7), amount: 7500 } ] ); // balance and ledger the same, 7 is unded assert_eq!(asset::total_balance::(&11), 1001); assert_eq!( Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, active: 1000, unlocking: Default::default(), } ); assert_eq!(asset::total_balance::(&7), 7500); }); } #[test] fn validator_prefs_no_commission() { ExtBuilder::default().build_and_execute(|| { Eras::::reward_active_era(vec![(11, 1)]); Session::roll_until_active_era(2); let _ = staking_events_since_last_call(); mock::make_all_reward_payment(1); assert_eq!( staking_events_since_last_call(), vec![ Event::PayoutStarted { era_index: 1, validator_stash: 11, page: 0, next: None }, Event::Rewarded { stash: 11, dest: RewardDestination::Staked, amount: 6000 }, Event::Rewarded { stash: 101, dest: RewardDestination::Staked, amount: 1500 } ] ); }); } #[test] fn validator_prefs_100_commission() { ExtBuilder::default().build_and_execute(|| { let commission = Perbill::from_percent(100); Eras::::reward_active_era(vec![(11, 1)]); Eras::::set_validator_prefs(1, &11, ValidatorPrefs { commission, ..Default::default() }); Session::roll_until_active_era(2); let _ = staking_events_since_last_call(); mock::make_all_reward_payment(1); assert_eq!( staking_events_since_last_call(), vec![ Event::PayoutStarted { era_index: 1, validator_stash: 11, page: 0, next: None }, Event::Rewarded { stash: 11, dest: RewardDestination::Staked, amount: 7500 } ] ); }); } #[test] fn validator_payment_some_commission_prefs_work() { ExtBuilder::default().build_and_execute(|| { let commission = Perbill::from_percent(40); Eras::::reward_active_era(vec![(11, 1)]); Eras::::set_validator_prefs(1, &11, ValidatorPrefs { commission, ..Default::default() }); Session::roll_until_active_era(2); let _ = staking_events_since_last_call(); mock::make_all_reward_payment(1); assert_eq!( staking_events_since_last_call(), vec![ Event::PayoutStarted { era_index: 1, validator_stash: 11, page: 0, next: None }, Event::Rewarded { stash: 11, dest: RewardDestination::Staked, amount: 6600 }, Event::Rewarded { stash: 101, dest: RewardDestination::Staked, amount: 900 } ] ); }); } #[test] fn min_commission_works() { ExtBuilder::default().build_and_execute(|| { // account 11 controls the stash of itself. assert_ok!(Staking::validate( RuntimeOrigin::signed(11), ValidatorPrefs { commission: Perbill::from_percent(5), blocked: false } )); // event emitted should be correct assert_eq!( *staking_events().last().unwrap(), Event::ValidatorPrefsSet { stash: 11, prefs: ValidatorPrefs { commission: Perbill::from_percent(5), blocked: false } } ); assert_ok!(Staking::set_staking_configs( RuntimeOrigin::root(), ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Set(Perbill::from_percent(10)), ConfigOp::Noop, )); // can't make it less than 10 now assert_noop!( Staking::validate( RuntimeOrigin::signed(11), ValidatorPrefs { commission: Perbill::from_percent(5), blocked: false } ), Error::::CommissionTooLow ); // can only change to higher. assert_ok!(Staking::validate( RuntimeOrigin::signed(11), ValidatorPrefs { commission: Perbill::from_percent(10), blocked: false } )); assert_ok!(Staking::validate( RuntimeOrigin::signed(11), ValidatorPrefs { commission: Perbill::from_percent(15), blocked: false } )); }) } #[test] fn set_min_commission_works_with_admin_origin() { ExtBuilder::default().build_and_execute(|| { // no minimum commission set initially assert_eq!(MinCommission::::get(), Zero::zero()); // root can set min commission assert_ok!(Staking::set_min_commission(RuntimeOrigin::root(), Perbill::from_percent(10))); assert_eq!(MinCommission::::get(), Perbill::from_percent(10)); // Non privileged origin can not set min_commission assert_noop!( Staking::set_min_commission(RuntimeOrigin::signed(2), Perbill::from_percent(15)), BadOrigin ); // Admin Origin can set min commission assert_ok!(Staking::set_min_commission( RuntimeOrigin::signed(1), Perbill::from_percent(15), )); // setting commission below min_commission fails assert_noop!( Staking::validate( RuntimeOrigin::signed(11), ValidatorPrefs { commission: Perbill::from_percent(14), blocked: false } ), Error::::CommissionTooLow ); // setting commission >= min_commission works assert_ok!(Staking::validate( RuntimeOrigin::signed(11), ValidatorPrefs { commission: Perbill::from_percent(15), blocked: false } )); }) } #[test] fn force_apply_min_commission_works() { let prefs = |c| ValidatorPrefs { commission: Perbill::from_percent(c), blocked: false }; let validators = || Validators::::iter().collect::>(); ExtBuilder::default().build_and_execute(|| { assert_ok!(Staking::validate(RuntimeOrigin::signed(31), prefs(10))); assert_ok!(Staking::validate(RuntimeOrigin::signed(21), prefs(5))); // Given assert_eq!(validators(), vec![(31, prefs(10)), (21, prefs(5)), (11, prefs(0))]); MinCommission::::set(Perbill::from_percent(5)); // When applying to a commission greater than min assert_ok!(Staking::force_apply_min_commission(RuntimeOrigin::signed(1), 31)); // Then the commission is not changed assert_eq!(validators(), vec![(31, prefs(10)), (21, prefs(5)), (11, prefs(0))]); // When applying to a commission that is equal to min assert_ok!(Staking::force_apply_min_commission(RuntimeOrigin::signed(1), 21)); // Then the commission is not changed assert_eq!(validators(), vec![(31, prefs(10)), (21, prefs(5)), (11, prefs(0))]); // When applying to a commission that is less than the min assert_ok!(Staking::force_apply_min_commission(RuntimeOrigin::signed(1), 11)); // Then the commission is bumped to the min assert_eq!(validators(), vec![(31, prefs(10)), (21, prefs(5)), (11, prefs(5))]); // When applying commission to a validator that doesn't exist then storage is not altered assert_noop!( Staking::force_apply_min_commission(RuntimeOrigin::signed(1), 420), Error::::NotStash ); }); } #[test] fn claim_reward_at_the_last_era_and_no_double_claim_and_invalid_claim() { // should check that: // * rewards get paid until history_depth for both validators and nominators // * an invalid era to claim doesn't update last_reward // * double claim of one era fails ExtBuilder::default().nominate(true).build_and_execute(|| { // Consumed weight for all payout_stakers dispatches that fail let err_weight = ::WeightInfo::payout_stakers_alive_staked(0); // Check state Payee::::insert(11, RewardDestination::Account(11)); Payee::::insert(101, RewardDestination::Account(101)); // reward for era 1 Pezpallet::::reward_by_ids(vec![(11, 1)]); Session::roll_until_active_era(2); // reward for era 2 Pezpallet::::reward_by_ids(vec![(11, 1)]); Session::roll_until_active_era(3); // reward for era 3 Pezpallet::::reward_by_ids(vec![(11, 1)]); // go to the history depth era Session::roll_until_active_era(HistoryDepth::get() + 1); let _ = staking_events_since_last_call(); // Last kept is 1: assert_noop!( Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 0, 0), // Fail: Era out of history Error::::InvalidEraToReward.with_weight(err_weight) ); assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 1, 0)); assert_eq!( staking_events_since_last_call(), vec![ Event::PayoutStarted { era_index: 1, validator_stash: 11, page: 0, next: None }, Event::Rewarded { stash: 11, dest: RewardDestination::Account(11), amount: 6000 }, Event::Rewarded { stash: 101, dest: RewardDestination::Account(101), amount: 1500 } ] ); assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 2, 0)); assert_eq!( staking_events_since_last_call(), vec![ Event::PayoutStarted { era_index: 2, validator_stash: 11, page: 0, next: None }, Event::Rewarded { stash: 11, dest: RewardDestination::Account(11), amount: 6000 }, Event::Rewarded { stash: 101, dest: RewardDestination::Account(101), amount: 1500 } ] ); assert_noop!( Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 2, 0), // Fail: Double claim Error::::AlreadyClaimed.with_weight(err_weight) ); assert_noop!( Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, active_era(), 0), // Fail: Era ongoing Error::::InvalidEraToReward.with_weight(err_weight) ); }); } #[test] fn nominators_over_max_exposure_page_size_are_rewarded() { ExtBuilder::default().build_and_execute(|| { // bond one nominator more than the max exposure page size to validator 11 in era 1 for i in 0..=MaxExposurePageSize::get() { let stash = 10_000 + i as AccountId; let balance = 10_000 + i as Balance; asset::set_stakeable_balance::(&stash, balance); assert_ok!(Staking::bond( RuntimeOrigin::signed(stash), balance, RewardDestination::Stash )); assert_ok!(Staking::nominate(RuntimeOrigin::signed(stash), vec![11])); } // enact new staker set -- era 2 Session::roll_until_active_era(2); // reward for era 2 Pezpallet::::reward_by_ids(vec![(11, 1)]); Session::roll_until_active_era(3); mock::make_all_reward_payment(2); // Assert nominators from 1 to Max are rewarded let mut i: u32 = 0; while i < MaxExposurePageSize::get() { let stash = 10_000 + i as AccountId; let balance = 10_000 + i as Balance; assert!(asset::stakeable_balance::(&stash) > balance); i += 1; } // Assert overflowing nominators from page 1 are also rewarded let stash = 10_000 + i as AccountId; assert!(asset::stakeable_balance::(&stash) > (10_000 + i) as Balance); }); } #[test] fn test_nominators_are_rewarded_for_all_exposure_page() { ExtBuilder::default().build_and_execute(|| { // 3 pages of exposure let nominator_count = 2 * MaxExposurePageSize::get() + 1; for i in 0..nominator_count { let stash = 10_000 + i as AccountId; let balance = 10_000 + i as Balance; asset::set_stakeable_balance::(&stash, balance); assert_ok!(Staking::bond( RuntimeOrigin::signed(stash), balance, RewardDestination::Stash )); assert_ok!(Staking::nominate(RuntimeOrigin::signed(stash), vec![11])); } // enact Session::roll_until_active_era(2); // give rewards Pezpallet::::reward_by_ids(vec![(11, 1)]); Session::roll_until_active_era(3); mock::make_all_reward_payment(2); assert_eq!(Eras::::exposure_page_count(2, &11), 3); // Assert all nominators are rewarded according to their stake for i in 0..nominator_count { // balance of the nominator after the reward payout. let current_balance = asset::stakeable_balance::(&((10000 + i) as AccountId)); // balance of the nominator in the previous iteration. let previous_balance = asset::stakeable_balance::(&((10000 + i - 1) as AccountId)); // balance before the reward. let original_balance = 10_000 + i as Balance; assert!(current_balance > original_balance); // since the stake of the nominator is increasing for each iteration, the final balance // after the reward should also be higher than the previous iteration. assert!(current_balance > previous_balance); } }); } #[test] fn test_multi_page_payout_stakers_by_page() { ExtBuilder::default().has_stakers(false).build_and_execute(|| { let balance = 1000; // Track the exposure of the validator and all nominators. let mut total_exposure = balance; // Create a validator: bond_validator(11, balance); // Default(64) assert_eq!(Validators::::count(), 1); // Create nominators, targeting stash of validators for i in 0..100 { let bond_amount = balance + i as Balance; bond_nominator(1000 + i, bond_amount, vec![11]); // with multi page reward payout, payout exposure is same as total exposure. total_exposure += bond_amount; } // enact the above changes Session::roll_until_active_era(2); // give rewards Staking::reward_by_ids(vec![(11, 1)]); // 100 nominators fit into 2 pages of exposure assert_eq!(MaxExposurePageSize::get(), 64); assert_eq!(Eras::::exposure_page_count(2, &11), 2); // compute and ensure the reward amount is greater than zero. let payout = validator_payout_for(time_per_era()); Session::roll_until_active_era(3); // verify the exposures are calculated correctly. let actual_exposure_0 = Eras::::get_paged_exposure(2, &11, 0).unwrap(); assert_eq!(actual_exposure_0.total(), total_exposure); assert_eq!(actual_exposure_0.own(), 1000); assert_eq!(actual_exposure_0.others().len(), 64); let actual_exposure_1 = Eras::::get_paged_exposure(2, &11, 1).unwrap(); assert_eq!(actual_exposure_1.total(), total_exposure); // own stake is only included once in the first page assert_eq!(actual_exposure_1.own(), 0); assert_eq!(actual_exposure_1.others().len(), 100 - 64); let pre_payout_total_issuance = pezpallet_balances::TotalIssuance::::get(); RewardOnUnbalanceWasCalled::set(false); // flush any events let _ = staking_events_since_last_call(); let controller_balance_before_p0_payout = asset::stakeable_balance::(&11); // Payout rewards for first exposure page assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 2, 0)); // verify `Rewarded` events are being executed assert!(matches!( staking_events_since_last_call().as_slice(), &[ Event::PayoutStarted { era_index: 2, validator_stash: 11, page: 0, next: Some(1) }, .., Event::Rewarded { stash: 1063, dest: RewardDestination::Stash, amount: _ }, Event::Rewarded { stash: 1064, dest: RewardDestination::Stash, amount: _ }, ] )); let controller_balance_after_p0_payout = asset::stakeable_balance::(&11); // verify rewards have been paid out but still some left assert!(pezpallet_balances::TotalIssuance::::get() > pre_payout_total_issuance); assert!(pezpallet_balances::TotalIssuance::::get() < pre_payout_total_issuance + payout); // verify the validator has been rewarded assert!(controller_balance_after_p0_payout > controller_balance_before_p0_payout); // Payout the second and last page of nominators assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 2, 1)); // verify `Rewarded` events are being executed for the second page. let events = staking_events_since_last_call(); assert!(matches!( events.as_slice(), &[ Event::PayoutStarted { era_index: 2, validator_stash: 11, page: 1, next: None }, Event::Rewarded { stash: 1065, dest: RewardDestination::Stash, amount: _ }, Event::Rewarded { stash: 1066, dest: RewardDestination::Stash, amount: _ }, .. ] )); // verify the validator was not rewarded the second time assert_eq!(asset::stakeable_balance::(&11), controller_balance_after_p0_payout); // verify all rewards have been paid out assert_eq_error_rate!( pezpallet_balances::TotalIssuance::::get(), pre_payout_total_issuance + payout, 2 ); assert!(RewardOnUnbalanceWasCalled::get()); // Top 64 nominators of validator 11 automatically paid out, including the validator assert!(asset::stakeable_balance::(&11) > balance); for i in 0..100 { assert!(asset::stakeable_balance::(&(1000 + i)) > balance + i as Balance); } // verify rewards are tracked to prevent double claims for page in 0..Eras::::exposure_page_count(2, &11) { assert_eq!(Eras::::is_rewards_claimed(2, &11, page), true); } for i in 4..17 { Staking::reward_by_ids(vec![(11, 1)]); // compute and ensure the reward amount is greater than zero. let payout = validator_payout_for(time_per_era()); let pre_payout_total_issuance = pezpallet_balances::TotalIssuance::::get(); Session::roll_until_active_era(i); RewardOnUnbalanceWasCalled::set(false); mock::make_all_reward_payment(i - 1); assert_eq_error_rate!( pezpallet_balances::TotalIssuance::::get(), pre_payout_total_issuance + payout, 2 ); assert!(RewardOnUnbalanceWasCalled::get()); // verify we track rewards for each era and page for page in 0..Eras::::exposure_page_count(i - 1, &11) { assert_eq!(Eras::::is_rewards_claimed(i - 1, &11, page), true); } } assert_eq!(ClaimedRewards::::get(14, &11), vec![0, 1]); let last_era = 99; let history_depth = HistoryDepth::get(); let last_reward_era = last_era - 1; let first_claimable_reward_era = last_era - history_depth; for i in 17..=last_era { Staking::reward_by_ids(vec![(11, 1)]); // compute and ensure the reward amount is greater than zero. let _ = validator_payout_for(time_per_era()); Session::roll_until_active_era(i); } // With manual pruning, old eras are NOT automatically cleaned up // ClaimedRewards for old eras will remain until manually pruned // Check eras that had rewards claimed (2-14, based on test setup) for era in 2..15 { // These eras had rewards and should still have ClaimedRewards since pruning is manual assert!(!ClaimedRewards::::get(era, &11).is_empty()); } // verify only page 0 is marked as claimed assert_ok!(Staking::payout_stakers_by_page( RuntimeOrigin::signed(1337), 11, first_claimable_reward_era, 0 )); assert_eq!(ClaimedRewards::::get(first_claimable_reward_era, &11), vec![0]); // verify page 0 and 1 are marked as claimed assert_ok!(Staking::payout_stakers_by_page( RuntimeOrigin::signed(1337), 11, first_claimable_reward_era, 1 )); assert_eq!(ClaimedRewards::::get(first_claimable_reward_era, &11), vec![0, 1]); // verify only page 0 is marked as claimed assert_ok!(Staking::payout_stakers_by_page( RuntimeOrigin::signed(1337), 11, last_reward_era, 0 )); assert_eq!(ClaimedRewards::::get(last_reward_era, &11), vec![0]); // verify page 0 and 1 are marked as claimed assert_ok!(Staking::payout_stakers_by_page( RuntimeOrigin::signed(1337), 11, last_reward_era, 1 )); assert_eq!(ClaimedRewards::::get(last_reward_era, &11), vec![0, 1]); // Out of order claims works. assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 69, 0)); assert_eq!(ClaimedRewards::::get(69, &11), vec![0]); assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 23, 1)); assert_eq!(ClaimedRewards::::get(23, &11), vec![1]); assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 42, 0)); assert_eq!(ClaimedRewards::::get(42, &11), vec![0]); }); } #[test] fn test_multi_page_payout_stakers_backward_compatible() { ExtBuilder::default().has_stakers(false).build_and_execute(|| { let balance = 1000; // Track the exposure of the validator and all nominators. let mut total_exposure = balance; // Create a validator: bond_validator(11, balance); // Default(64) assert_eq!(Validators::::count(), 1); let err_weight = ::WeightInfo::payout_stakers_alive_staked(0); // Create nominators, targeting stash of validators for i in 0..100 { let bond_amount = balance + i as Balance; bond_nominator(1000 + i, bond_amount, vec![11]); // with multi page reward payout, payout exposure is same as total exposure. total_exposure += bond_amount; } Session::roll_until_active_era(2); Staking::reward_by_ids(vec![(11, 1)]); // Since `MaxExposurePageSize = 64`, there are two pages of validator exposure. assert_eq!(Eras::::exposure_page_count(2, &11), 2); // compute and ensure the reward amount is greater than zero. let payout = validator_payout_for(time_per_era()); Session::roll_until_active_era(3); // verify the exposures are calculated correctly. let actual_exposure_0 = Eras::::get_paged_exposure(2, &11, 0).unwrap(); assert_eq!(actual_exposure_0.total(), total_exposure); assert_eq!(actual_exposure_0.own(), 1000); assert_eq!(actual_exposure_0.others().len(), 64); let actual_exposure_1 = Eras::::get_paged_exposure(2, &11, 1).unwrap(); assert_eq!(actual_exposure_1.total(), total_exposure); // own stake is only included once in the first page assert_eq!(actual_exposure_1.own(), 0); assert_eq!(actual_exposure_1.others().len(), 100 - 64); let pre_payout_total_issuance = pezpallet_balances::TotalIssuance::::get(); RewardOnUnbalanceWasCalled::set(false); let controller_balance_before_p0_payout = asset::stakeable_balance::(&11); // Payout rewards for first exposure page assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 2)); // page 0 is claimed assert_noop!( Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 2, 0), Error::::AlreadyClaimed.with_weight(err_weight) ); let controller_balance_after_p0_payout = asset::stakeable_balance::(&11); // verify rewards have been paid out but still some left assert!(pezpallet_balances::TotalIssuance::::get() > pre_payout_total_issuance); assert!(pezpallet_balances::TotalIssuance::::get() < pre_payout_total_issuance + payout); // verify the validator has been rewarded assert!(controller_balance_after_p0_payout > controller_balance_before_p0_payout); // This should payout the second and last page of nominators assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 2)); // cannot claim any more pages assert_noop!( Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 2), Error::::AlreadyClaimed.with_weight(err_weight) ); // verify the validator was not rewarded the second time assert_eq!(asset::stakeable_balance::(&11), controller_balance_after_p0_payout); // verify all rewards have been paid out assert_eq_error_rate!( pezpallet_balances::TotalIssuance::::get(), pre_payout_total_issuance + payout, 2 ); assert!(RewardOnUnbalanceWasCalled::get()); // verify all nominators of validator 11 are paid out, including the validator // Validator payout goes to controller. assert!(asset::stakeable_balance::(&11) > balance); for i in 0..100 { assert!(asset::stakeable_balance::(&(1000 + i)) > balance + i as Balance); } // verify rewards are tracked to prevent double claims for page in 0..Eras::::exposure_page_count(2, &11) { assert_eq!(Eras::::is_rewards_claimed(2, &11, page), true); } for i in 4..17 { Staking::reward_by_ids(vec![(11, 1)]); // compute and ensure the reward amount is greater than zero. let payout = validator_payout_for(time_per_era()); let pre_payout_total_issuance = pezpallet_balances::TotalIssuance::::get(); Session::roll_until_active_era(i); RewardOnUnbalanceWasCalled::set(false); mock::make_all_reward_payment(i - 1); assert_eq_error_rate!( pezpallet_balances::TotalIssuance::::get(), pre_payout_total_issuance + payout, 2 ); assert!(RewardOnUnbalanceWasCalled::get()); // verify we track rewards for each era and page for page in 0..Eras::::exposure_page_count(i - 1, &11) { assert_eq!(Eras::::is_rewards_claimed(i - 1, &11, page), true); } } assert_eq!(ClaimedRewards::::get(14, &11), vec![0, 1]); let last_era = 99; let history_depth = HistoryDepth::get(); let last_reward_era = last_era - 1; let first_claimable_reward_era = last_era - history_depth; for i in 17..=last_era { Staking::reward_by_ids(vec![(11, 1)]); // compute and ensure the reward amount is greater than zero. let _ = validator_payout_for(time_per_era()); Session::roll_until_active_era(i); } // With manual pruning, old eras are NOT automatically cleaned up // ClaimedRewards for old eras will remain until manually pruned // Check eras that had rewards claimed (2-14, based on test setup) for era in 2..15 { // These eras had rewards and should still have ClaimedRewards since pruning is manual assert!(!ClaimedRewards::::get(era, &11).is_empty()); } // verify only page 0 is marked as claimed assert_ok!(Staking::payout_stakers( RuntimeOrigin::signed(1337), 11, first_claimable_reward_era )); assert_eq!(ClaimedRewards::::get(first_claimable_reward_era, &11), vec![0]); // verify page 0 and 1 are marked as claimed assert_ok!(Staking::payout_stakers( RuntimeOrigin::signed(1337), 11, first_claimable_reward_era, )); assert_eq!(ClaimedRewards::::get(first_claimable_reward_era, &11), vec![0, 1]); // change order and verify only page 1 is marked as claimed assert_ok!(Staking::payout_stakers_by_page( RuntimeOrigin::signed(1337), 11, last_reward_era, 1 )); assert_eq!(ClaimedRewards::::get(last_reward_era, &11), vec![1]); // verify page 0 is claimed even when explicit page is not passed assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, last_reward_era,)); assert_eq!(ClaimedRewards::::get(last_reward_era, &11), vec![1, 0]); // cannot claim any more pages assert_noop!( Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, last_reward_era), Error::::AlreadyClaimed.with_weight(err_weight) ); // Create 4 nominator pages for i in 100..200 { let bond_amount = balance + i as Balance; bond_nominator(1000 + i, bond_amount, vec![11]); } let test_era = last_era + 1; Session::roll_until_active_era(test_era); Staking::reward_by_ids(vec![(11, 1)]); // compute and ensure the reward amount is greater than zero. let _ = validator_payout_for(time_per_era()); Session::roll_until_active_era(test_era + 1); // Out of order claims works. assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, test_era, 2)); assert_eq!(ClaimedRewards::::get(test_era, &11), vec![2]); assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, test_era)); assert_eq!(ClaimedRewards::::get(test_era, &11), vec![2, 0]); // cannot claim page 2 again assert_noop!( Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, test_era, 2), Error::::AlreadyClaimed.with_weight(err_weight) ); assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, test_era)); assert_eq!(ClaimedRewards::::get(test_era, &11), vec![2, 0, 1]); assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, test_era)); assert_eq!(ClaimedRewards::::get(test_era, &11), vec![2, 0, 1, 3]); }); } #[test] fn test_page_count_and_size() { ExtBuilder::default().has_stakers(false).build_and_execute(|| { let balance = 1000; // Track the exposure of the validator and all nominators. // Create a validator: bond_validator(11, balance); // Default(64) assert_eq!(Validators::::count(), 1); // Create nominators, targeting stash of validators for i in 0..100 { let bond_amount = balance + i as Balance; bond_nominator(1000 + i, bond_amount, vec![11]); } Session::roll_until_active_era(2); // Since max exposure page size is 64, 2 pages of nominators are created. assert_eq!(MaxExposurePageSize::get(), 64); assert_eq!(Eras::::exposure_page_count(2, &11), 2); // first page has 64 nominators assert_eq!(Eras::::get_paged_exposure(2, &11, 0).unwrap().others().len(), 64); // second page has 36 nominators assert_eq!(Eras::::get_paged_exposure(2, &11, 1).unwrap().others().len(), 36); // now lets decrease page size MaxExposurePageSize::set(32); Session::roll_until_active_era(3); // now we expect 4 pages. assert_eq!(Eras::::exposure_page_count(3, &11), 4); // first 3 pages have 32 nominators each assert_eq!(Eras::::get_paged_exposure(3, &11, 0).unwrap().others().len(), 32); assert_eq!(Eras::::get_paged_exposure(3, &11, 1).unwrap().others().len(), 32); assert_eq!(Eras::::get_paged_exposure(3, &11, 2).unwrap().others().len(), 32); assert_eq!(Eras::::get_paged_exposure(3, &11, 3).unwrap().others().len(), 4); // now lets decrease page size even more MaxExposurePageSize::set(5); Session::roll_until_active_era(4); // now we expect the max 20 pages (100/5). assert_eq!(Eras::::exposure_page_count(4, &11), 20); }); } #[test] fn payout_stakers_handles_basic_errors() { ExtBuilder::default().has_stakers(false).build_and_execute(|| { let err_weight = ::WeightInfo::payout_stakers_alive_staked(0); // Same setup as the test above let balance = 1000; bond_validator(11, balance); // Default(64) // Create nominators, targeting stash for i in 0..100 { bond_nominator(1000 + i, balance + i as Balance, vec![11]); } Session::roll_until_active_era(2); Staking::reward_by_ids(vec![(11, 1)]); let _ = validator_payout_for(time_per_era()); Session::roll_until_active_era(3); // Wrong Era, too big assert_noop!( Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 3, 0), Error::::InvalidEraToReward.with_weight(err_weight) ); // Wrong Staker assert_noop!( Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 10, 2, 0), Error::::NotStash.with_weight(err_weight) ); let last_era = 99; for i in 4..=last_era { Staking::reward_by_ids(vec![(11, 1)]); // compute and ensure the reward amount is greater than zero. let _ = validator_payout_for(time_per_era()); Session::roll_until_active_era(i); } let history_depth = HistoryDepth::get(); let expected_last_reward_era = last_era - 1; let expected_start_reward_era = last_era - history_depth; // We are at era last_era=99. Given history_depth=80, we should be able // to payout era starting from expected_start_reward_era=19 through // expected_last_reward_era=98 (80 total eras), but not 18 or 99. assert_noop!( Staking::payout_stakers_by_page( RuntimeOrigin::signed(1337), 11, expected_start_reward_era - 1, 0 ), Error::::InvalidEraToReward.with_weight(err_weight) ); assert_noop!( Staking::payout_stakers_by_page( RuntimeOrigin::signed(1337), 11, expected_last_reward_era + 1, 0 ), Error::::InvalidEraToReward.with_weight(err_weight) ); assert_ok!(Staking::payout_stakers_by_page( RuntimeOrigin::signed(1337), 11, expected_start_reward_era, 0 )); assert_ok!(Staking::payout_stakers_by_page( RuntimeOrigin::signed(1337), 11, expected_last_reward_era, 0 )); // can call page 1 assert_ok!(Staking::payout_stakers_by_page( RuntimeOrigin::signed(1337), 11, expected_last_reward_era, 1 )); // Can't claim again assert_noop!( Staking::payout_stakers_by_page( RuntimeOrigin::signed(1337), 11, expected_start_reward_era, 0 ), Error::::AlreadyClaimed.with_weight(err_weight) ); assert_noop!( Staking::payout_stakers_by_page( RuntimeOrigin::signed(1337), 11, expected_last_reward_era, 0 ), Error::::AlreadyClaimed.with_weight(err_weight) ); assert_noop!( Staking::payout_stakers_by_page( RuntimeOrigin::signed(1337), 11, expected_last_reward_era, 1 ), Error::::AlreadyClaimed.with_weight(err_weight) ); // invalid page assert_noop!( Staking::payout_stakers_by_page( RuntimeOrigin::signed(1337), 11, expected_last_reward_era, 2 ), Error::::InvalidPage.with_weight(err_weight) ); }); } #[test] fn test_commission_paid_across_pages() { ExtBuilder::default().has_stakers(false).build_and_execute(|| { let balance = 1; let commission = 50; // Create a validator: bond_validator(11, balance); assert_ok!(Staking::validate( RuntimeOrigin::signed(11), ValidatorPrefs { commission: Perbill::from_percent(commission), blocked: false } )); assert_eq!(Validators::::count(), 1); // Create nominators, targeting stash of validators for i in 0..200 { let bond_amount = balance + i as Balance; bond_nominator(1000 + i, bond_amount, vec![11]); } Session::roll_until_active_era(2); Staking::reward_by_ids(vec![(11, 1)]); // Since `MaxExposurePageSize = 64`, there are four pages of validator // exposure. assert_eq!(Eras::::exposure_page_count(2, &11), 4); // compute and ensure the reward amount is greater than zero. let payout = validator_payout_for(time_per_era()); Session::roll_until_active_era(3); let initial_balance = asset::stakeable_balance::(&11); // Payout rewards for first exposure page assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 2, 0)); let controller_balance_after_p0_payout = asset::stakeable_balance::(&11); // some commission is paid assert!(initial_balance < controller_balance_after_p0_payout); // payout all pages for i in 1..4 { let before_balance = asset::stakeable_balance::(&11); assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 2, i)); let after_balance = asset::stakeable_balance::(&11); // some commission is paid for every page assert!(before_balance < after_balance); } assert_eq_error_rate!(asset::stakeable_balance::(&11), initial_balance + payout / 2, 1,); }); } #[test] fn payout_stakers_handles_weight_refund() { // Note: this test relies on the assumption that `payout_stakers_alive_staked` is solely used by // `payout_stakers` to calculate the weight of each payout op. ExtBuilder::default().has_stakers(false).build_and_execute(|| { use crate::Call as StakingCall; let max_nom_rewarded = MaxExposurePageSize::get(); // Make sure the configured value is meaningful for our use. assert!(max_nom_rewarded >= 4); let half_max_nom_rewarded = max_nom_rewarded / 2; // Sanity check our max and half max nominator quantities. assert!(half_max_nom_rewarded > 0); assert!(max_nom_rewarded > half_max_nom_rewarded); let max_nom_rewarded_weight = ::WeightInfo::payout_stakers_alive_staked(max_nom_rewarded); let half_max_nom_rewarded_weight = ::WeightInfo::payout_stakers_alive_staked(half_max_nom_rewarded); let zero_nom_payouts_weight = ::WeightInfo::payout_stakers_alive_staked(0); assert!(zero_nom_payouts_weight.any_gt(Weight::zero())); assert!(half_max_nom_rewarded_weight.any_gt(zero_nom_payouts_weight)); assert!(max_nom_rewarded_weight.any_gt(half_max_nom_rewarded_weight)); let balance = 1000; bond_validator(11, balance); // Era 2 Session::roll_until_active_era(2); // Reward just the validator. Staking::reward_by_ids(vec![(11, 1)]); // Add some `half_max_nom_rewarded` nominators who will start backing the validator in the // next era. for i in 0..half_max_nom_rewarded { bond_nominator((1000 + i).into(), balance + i as Balance, vec![11]); } // Era 3 Session::roll_until_active_era(3); // Collect payouts when there are no nominators let call = RuntimeCall::Staking(StakingCall::payout_stakers_by_page { validator_stash: 11, era: 2, page: 0, }); let info = call.get_dispatch_info(); let result = call.dispatch(RuntimeOrigin::signed(20)); assert_ok!(result); assert_eq!(extract_actual_weight(&result, &info), zero_nom_payouts_weight); // The validator is not rewarded in this era; so there will be zero payouts to claim for // this era. // next era -- with nominators now Session::roll_until_active_era(4); // Collect payouts for an era where the validator did not receive any points. let call = RuntimeCall::Staking(StakingCall::payout_stakers_by_page { validator_stash: 11, era: 3, page: 0, }); let info = call.get_dispatch_info(); let result = call.dispatch(RuntimeOrigin::signed(20)); assert_ok!(result); assert_eq!(extract_actual_weight(&result, &info), zero_nom_payouts_weight); // Reward the validator and its nominators. Staking::reward_by_ids(vec![(11, 1)]); // Era 5 Session::roll_until_active_era(5); // Collect payouts when the validator has `half_max_nom_rewarded` nominators. let call = RuntimeCall::Staking(StakingCall::payout_stakers_by_page { validator_stash: 11, era: 4, page: 0, }); let info = call.get_dispatch_info(); let result = call.dispatch(RuntimeOrigin::signed(20)); assert_ok!(result); assert_eq!(extract_actual_weight(&result, &info), half_max_nom_rewarded_weight); // Add enough nominators so that we are at the limit. They will be active nominators // in the next era. for i in half_max_nom_rewarded..max_nom_rewarded { bond_nominator((1000 + i).into(), balance + i as Balance, vec![11]); } // Era 6 Session::roll_until_active_era(6); // We now have `max_nom_rewarded` nominators actively nominating our validator. // Reward the validator so we can collect for everyone in the next era. Staking::reward_by_ids(vec![(11, 1)]); // Era 7 Session::roll_until_active_era(7); // Collect payouts when the validator had `half_max_nom_rewarded` nominators. let call = RuntimeCall::Staking(StakingCall::payout_stakers_by_page { validator_stash: 11, era: 6, page: 0, }); let info = call.get_dispatch_info(); let result = call.dispatch(RuntimeOrigin::signed(20)); assert_ok!(result); assert_eq!(extract_actual_weight(&result, &info), max_nom_rewarded_weight); // Try and collect payouts for an era that has already been collected. let call = RuntimeCall::Staking(StakingCall::payout_stakers_by_page { validator_stash: 11, era: 6, page: 0, }); let info = call.get_dispatch_info(); let result = call.dispatch(RuntimeOrigin::signed(20)); assert!(result.is_err()); // When there is an error the consumed weight == weight when there are 0 nominator payouts. assert_eq!(extract_actual_weight(&result, &info), zero_nom_payouts_weight); }); } #[test] fn test_runtime_api_pending_rewards() { ExtBuilder::default().build_and_execute(|| { // GIVEN let err_weight = ::WeightInfo::payout_stakers_alive_staked(0); let stake = 100; // validator with non-paged exposure, rewards marked in legacy claimed rewards. let validator_one = 301; // validator with non-paged exposure, rewards marked in paged claimed rewards. let validator_two = 302; // validator with paged exposure. let validator_three = 303; // Set staker for v in validator_one..=validator_three { let _ = asset::set_stakeable_balance::(&v, stake); assert_ok!(Staking::bond(RuntimeOrigin::signed(v), stake, RewardDestination::Staked)); } // Add reward points let reward = EraRewardPoints { total: 1, individual: bounded_btree_map![validator_one => 1, validator_two => 1, validator_three => 1], }; ErasRewardPoints::::insert(0, reward); // build exposure let mut individual_exposures: Vec> = vec![]; for i in 0..=MaxExposurePageSize::get() { individual_exposures.push(IndividualExposure { who: i.into(), value: stake }); } let exposure = Exposure:: { total: stake * (MaxExposurePageSize::get() as Balance + 2), own: stake, others: individual_exposures, }; // add exposure for validators Eras::::upsert_exposure(0, &validator_one, exposure.clone()); Eras::::upsert_exposure(0, &validator_two, exposure.clone()); // add some reward to be distributed ErasValidatorReward::::insert(0, 1000); // SCENARIO: Validator with paged exposure (two pages). // validators have not claimed rewards, so pending rewards is true. assert!(Eras::::pending_rewards(0, &validator_one)); assert!(Eras::::pending_rewards(0, &validator_two)); // and payout works assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_one, 0)); assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_two, 0)); // validators have two pages of exposure, so pending rewards is still true. assert!(Eras::::pending_rewards(0, &validator_one)); assert!(Eras::::pending_rewards(0, &validator_two)); // payout again only for validator one assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_one, 0)); // now pending rewards is false for validator one assert!(!Eras::::pending_rewards(0, &validator_one)); // and payout fails for validator one assert_noop!( Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_one, 0), Error::::AlreadyClaimed.with_weight(err_weight) ); // while pending reward is true for validator two assert!(Eras::::pending_rewards(0, &validator_two)); // and payout works again for validator two. assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_two, 0)); }); }