3139ffa25e
- snowbridge-pezpallet-* → pezsnowbridge-pezpallet-* (201 refs) - pallet/ directories → pezpallet/ (4 locations) - Fixed pezpallet.rs self-include recursion bug - Fixed sc-chain-spec hardcoded crate name in derive macro - Reverted .pezpallet_by_name() to .pallet_by_name() (subxt API) - Added BizinikiwiConfig type alias for zombienet tests - Deleted obsolete session state files Verified: pezsnowbridge-pezpallet-*, pezpallet-staking, pezpallet-staking-async, pezframe-benchmarking-cli all pass cargo check
1695 lines
55 KiB
Rust
1695 lines
55 KiB
Rust
// 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::<T>(&11);
|
|
let init_balance_21 = asset::total_balance::<T>(&21);
|
|
let init_balance_101 = asset::total_balance::<T>(&101);
|
|
|
|
// Set payees
|
|
Payee::<T>::insert(11, RewardDestination::Account(11));
|
|
Payee::<T>::insert(21, RewardDestination::Account(21));
|
|
Payee::<T>::insert(101, RewardDestination::Account(101));
|
|
|
|
Eras::<T>::reward_active_era(vec![(11, 50)]);
|
|
Eras::<T>::reward_active_era(vec![(11, 50)]);
|
|
// This is the second validator of the current elected set.
|
|
Eras::<T>::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::<T>(&11), init_balance_11);
|
|
assert_eq!(asset::total_balance::<T>(&21), init_balance_21);
|
|
assert_eq!(asset::total_balance::<T>(&101), init_balance_101);
|
|
assert_eq!(
|
|
ErasRewardPoints::<T>::get(active_era()),
|
|
EraRewardPoints { total: 50 * 3, individual: bounded_btree_map![11 => 100, 21 => 50] }
|
|
);
|
|
let part_for_11 = Perbill::from_rational::<u32>(1000, 1250);
|
|
let part_for_21 = Perbill::from_rational::<u32>(1000, 1250);
|
|
let part_for_101_from_11 = Perbill::from_rational::<u32>(250, 1250);
|
|
let part_for_101_from_21 = Perbill::from_rational::<u32>(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::<T>();
|
|
|
|
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::<T>();
|
|
assert_eq!(post_issuance, pre_issuance + validator_payout_0);
|
|
|
|
assert_eq_error_rate!(
|
|
asset::total_balance::<T>(&11),
|
|
init_balance_11 + part_for_11 * validator_payout_0 * 2 / 3,
|
|
2,
|
|
);
|
|
assert_eq_error_rate!(
|
|
asset::total_balance::<T>(&21),
|
|
init_balance_21 + part_for_21 * validator_payout_0 * 1 / 3,
|
|
2,
|
|
);
|
|
assert_eq_error_rate!(
|
|
asset::total_balance::<T>(&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::<T>::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::<T>(), post_issuance + total_payout_1);
|
|
|
|
assert_eq_error_rate!(
|
|
asset::total_balance::<T>(&11),
|
|
init_balance_11 + part_for_11 * (validator_payout_0 * 2 / 3 + total_payout_1),
|
|
2,
|
|
);
|
|
assert_eq_error_rate!(
|
|
asset::total_balance::<T>(&21),
|
|
init_balance_21 + part_for_21 * validator_payout_0 * 1 / 3,
|
|
2,
|
|
);
|
|
assert_eq_error_rate!(
|
|
asset::total_balance::<T>(&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::<AccountId, Balance> { total: 1000, own: 1000, others: vec![] }),
|
|
(21, Exposure::<AccountId, Balance> { 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::<T>::reward_active_era(vec![(41, 1)]);
|
|
Eras::<T>::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::<T>::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::<T>(&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::<T>::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::<T>::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::<T>(&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::<T>::get(), 2);
|
|
// Confirm account 10 and 20 are validators
|
|
assert!(<Validators<T>>::contains_key(&11) && <Validators<T>>::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::<T>(&10, 1000);
|
|
let _ = asset::set_stakeable_balance::<T>(&20, 1000);
|
|
|
|
// Bypass logic and change current exposure
|
|
Eras::<T>::upsert_exposure(0, &21, Exposure { total: 69, own: 69, others: vec![] });
|
|
<Ledger<T>>::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::<T>::reward_by_ids(vec![(11, 1)]);
|
|
Pezpallet::<T>::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::<T>(&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::<T>(&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::<T>::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::<T>::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::<T>(&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::<T>(&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::<T>::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::<T>::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::<T>(&11), 1001);
|
|
assert_eq!(
|
|
Staking::ledger(11.into()).unwrap(),
|
|
StakingLedgerInspect {
|
|
stash: 11,
|
|
total: 1000,
|
|
active: 1000,
|
|
unlocking: Default::default(),
|
|
}
|
|
);
|
|
assert_eq!(asset::total_balance::<T>(&7), 7500);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn validator_prefs_no_commission() {
|
|
ExtBuilder::default().build_and_execute(|| {
|
|
Eras::<T>::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::<T>::reward_active_era(vec![(11, 1)]);
|
|
|
|
Eras::<T>::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::<T>::reward_active_era(vec![(11, 1)]);
|
|
|
|
Eras::<T>::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::<T>::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::<T>::get(), Zero::zero());
|
|
|
|
// root can set min commission
|
|
assert_ok!(Staking::set_min_commission(RuntimeOrigin::root(), Perbill::from_percent(10)));
|
|
|
|
assert_eq!(MinCommission::<T>::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::<T>::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::<T>::iter().collect::<Vec<_>>();
|
|
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::<T>::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::<T>::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 = <T as Config>::WeightInfo::payout_stakers_alive_staked(0);
|
|
|
|
// Check state
|
|
Payee::<T>::insert(11, RewardDestination::Account(11));
|
|
Payee::<T>::insert(101, RewardDestination::Account(101));
|
|
|
|
// reward for era 1
|
|
Pezpallet::<T>::reward_by_ids(vec![(11, 1)]);
|
|
|
|
Session::roll_until_active_era(2);
|
|
|
|
// reward for era 2
|
|
Pezpallet::<T>::reward_by_ids(vec![(11, 1)]);
|
|
|
|
Session::roll_until_active_era(3);
|
|
|
|
// reward for era 3
|
|
Pezpallet::<T>::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::<T>::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::<T>::AlreadyClaimed.with_weight(err_weight)
|
|
);
|
|
|
|
assert_noop!(
|
|
Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, active_era(), 0),
|
|
// Fail: Era ongoing
|
|
Error::<T>::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::<T>(&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::<T>::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::<T>(&stash) > balance);
|
|
i += 1;
|
|
}
|
|
|
|
// Assert overflowing nominators from page 1 are also rewarded
|
|
let stash = 10_000 + i as AccountId;
|
|
assert!(asset::stakeable_balance::<T>(&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::<T>(&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::<T>::reward_by_ids(vec![(11, 1)]);
|
|
|
|
Session::roll_until_active_era(3);
|
|
mock::make_all_reward_payment(2);
|
|
|
|
assert_eq!(Eras::<T>::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::<T>(&((10000 + i) as AccountId));
|
|
// balance of the nominator in the previous iteration.
|
|
let previous_balance = asset::stakeable_balance::<T>(&((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::<T>::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::<T>::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::<T>::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::<T>::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::<T>::get();
|
|
RewardOnUnbalanceWasCalled::set(false);
|
|
|
|
// flush any events
|
|
let _ = staking_events_since_last_call();
|
|
|
|
let controller_balance_before_p0_payout = asset::stakeable_balance::<T>(&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::<T>(&11);
|
|
|
|
// verify rewards have been paid out but still some left
|
|
assert!(pezpallet_balances::TotalIssuance::<T>::get() > pre_payout_total_issuance);
|
|
assert!(pezpallet_balances::TotalIssuance::<T>::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::<T>(&11), controller_balance_after_p0_payout);
|
|
|
|
// verify all rewards have been paid out
|
|
assert_eq_error_rate!(
|
|
pezpallet_balances::TotalIssuance::<T>::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::<T>(&11) > balance);
|
|
for i in 0..100 {
|
|
assert!(asset::stakeable_balance::<T>(&(1000 + i)) > balance + i as Balance);
|
|
}
|
|
|
|
// verify rewards are tracked to prevent double claims
|
|
for page in 0..Eras::<T>::exposure_page_count(2, &11) {
|
|
assert_eq!(Eras::<T>::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::<T>::get();
|
|
|
|
Session::roll_until_active_era(i);
|
|
RewardOnUnbalanceWasCalled::set(false);
|
|
mock::make_all_reward_payment(i - 1);
|
|
assert_eq_error_rate!(
|
|
pezpallet_balances::TotalIssuance::<T>::get(),
|
|
pre_payout_total_issuance + payout,
|
|
2
|
|
);
|
|
assert!(RewardOnUnbalanceWasCalled::get());
|
|
|
|
// verify we track rewards for each era and page
|
|
for page in 0..Eras::<T>::exposure_page_count(i - 1, &11) {
|
|
assert_eq!(Eras::<T>::is_rewards_claimed(i - 1, &11, page), true);
|
|
}
|
|
}
|
|
|
|
assert_eq!(ClaimedRewards::<T>::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::<T>::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::<T>::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::<T>::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::<T>::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::<T>::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::<T>::get(69, &11), vec![0]);
|
|
assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 23, 1));
|
|
assert_eq!(ClaimedRewards::<T>::get(23, &11), vec![1]);
|
|
assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 42, 0));
|
|
assert_eq!(ClaimedRewards::<T>::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::<T>::count(), 1);
|
|
|
|
let err_weight = <T as Config>::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::<T>::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::<T>::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::<T>::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::<T>::get();
|
|
RewardOnUnbalanceWasCalled::set(false);
|
|
|
|
let controller_balance_before_p0_payout = asset::stakeable_balance::<T>(&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::<T>::AlreadyClaimed.with_weight(err_weight)
|
|
);
|
|
|
|
let controller_balance_after_p0_payout = asset::stakeable_balance::<T>(&11);
|
|
|
|
// verify rewards have been paid out but still some left
|
|
assert!(pezpallet_balances::TotalIssuance::<T>::get() > pre_payout_total_issuance);
|
|
assert!(pezpallet_balances::TotalIssuance::<T>::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::<T>::AlreadyClaimed.with_weight(err_weight)
|
|
);
|
|
|
|
// verify the validator was not rewarded the second time
|
|
assert_eq!(asset::stakeable_balance::<T>(&11), controller_balance_after_p0_payout);
|
|
|
|
// verify all rewards have been paid out
|
|
assert_eq_error_rate!(
|
|
pezpallet_balances::TotalIssuance::<T>::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::<T>(&11) > balance);
|
|
for i in 0..100 {
|
|
assert!(asset::stakeable_balance::<T>(&(1000 + i)) > balance + i as Balance);
|
|
}
|
|
|
|
// verify rewards are tracked to prevent double claims
|
|
for page in 0..Eras::<T>::exposure_page_count(2, &11) {
|
|
assert_eq!(Eras::<T>::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::<T>::get();
|
|
|
|
Session::roll_until_active_era(i);
|
|
RewardOnUnbalanceWasCalled::set(false);
|
|
mock::make_all_reward_payment(i - 1);
|
|
assert_eq_error_rate!(
|
|
pezpallet_balances::TotalIssuance::<T>::get(),
|
|
pre_payout_total_issuance + payout,
|
|
2
|
|
);
|
|
assert!(RewardOnUnbalanceWasCalled::get());
|
|
|
|
// verify we track rewards for each era and page
|
|
for page in 0..Eras::<T>::exposure_page_count(i - 1, &11) {
|
|
assert_eq!(Eras::<T>::is_rewards_claimed(i - 1, &11, page), true);
|
|
}
|
|
}
|
|
|
|
assert_eq!(ClaimedRewards::<T>::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::<T>::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::<T>::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::<T>::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::<T>::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::<T>::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::<T>::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::<T>::get(test_era, &11), vec![2]);
|
|
|
|
assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, test_era));
|
|
assert_eq!(ClaimedRewards::<T>::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::<T>::AlreadyClaimed.with_weight(err_weight)
|
|
);
|
|
|
|
assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, test_era));
|
|
assert_eq!(ClaimedRewards::<T>::get(test_era, &11), vec![2, 0, 1]);
|
|
|
|
assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, test_era));
|
|
assert_eq!(ClaimedRewards::<T>::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::<T>::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::<T>::exposure_page_count(2, &11), 2);
|
|
|
|
// first page has 64 nominators
|
|
assert_eq!(Eras::<T>::get_paged_exposure(2, &11, 0).unwrap().others().len(), 64);
|
|
// second page has 36 nominators
|
|
assert_eq!(Eras::<T>::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::<T>::exposure_page_count(3, &11), 4);
|
|
// first 3 pages have 32 nominators each
|
|
assert_eq!(Eras::<T>::get_paged_exposure(3, &11, 0).unwrap().others().len(), 32);
|
|
assert_eq!(Eras::<T>::get_paged_exposure(3, &11, 1).unwrap().others().len(), 32);
|
|
assert_eq!(Eras::<T>::get_paged_exposure(3, &11, 2).unwrap().others().len(), 32);
|
|
assert_eq!(Eras::<T>::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::<T>::exposure_page_count(4, &11), 20);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn payout_stakers_handles_basic_errors() {
|
|
ExtBuilder::default().has_stakers(false).build_and_execute(|| {
|
|
let err_weight = <T as Config>::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::<T>::InvalidEraToReward.with_weight(err_weight)
|
|
);
|
|
// Wrong Staker
|
|
assert_noop!(
|
|
Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 10, 2, 0),
|
|
Error::<T>::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::<T>::InvalidEraToReward.with_weight(err_weight)
|
|
);
|
|
assert_noop!(
|
|
Staking::payout_stakers_by_page(
|
|
RuntimeOrigin::signed(1337),
|
|
11,
|
|
expected_last_reward_era + 1,
|
|
0
|
|
),
|
|
Error::<T>::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::<T>::AlreadyClaimed.with_weight(err_weight)
|
|
);
|
|
|
|
assert_noop!(
|
|
Staking::payout_stakers_by_page(
|
|
RuntimeOrigin::signed(1337),
|
|
11,
|
|
expected_last_reward_era,
|
|
0
|
|
),
|
|
Error::<T>::AlreadyClaimed.with_weight(err_weight)
|
|
);
|
|
|
|
assert_noop!(
|
|
Staking::payout_stakers_by_page(
|
|
RuntimeOrigin::signed(1337),
|
|
11,
|
|
expected_last_reward_era,
|
|
1
|
|
),
|
|
Error::<T>::AlreadyClaimed.with_weight(err_weight)
|
|
);
|
|
|
|
// invalid page
|
|
assert_noop!(
|
|
Staking::payout_stakers_by_page(
|
|
RuntimeOrigin::signed(1337),
|
|
11,
|
|
expected_last_reward_era,
|
|
2
|
|
),
|
|
Error::<T>::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::<T>::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::<T>::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::<T>(&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::<T>(&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::<T>(&11);
|
|
assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 2, i));
|
|
let after_balance = asset::stakeable_balance::<T>(&11);
|
|
// some commission is paid for every page
|
|
assert!(before_balance < after_balance);
|
|
}
|
|
|
|
assert_eq_error_rate!(asset::stakeable_balance::<T>(&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 =
|
|
<T as Config>::WeightInfo::payout_stakers_alive_staked(max_nom_rewarded);
|
|
let half_max_nom_rewarded_weight =
|
|
<T as Config>::WeightInfo::payout_stakers_alive_staked(half_max_nom_rewarded);
|
|
let zero_nom_payouts_weight = <T as Config>::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 = <T as Config>::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::<T>(&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::<T>::insert(0, reward);
|
|
|
|
// build exposure
|
|
let mut individual_exposures: Vec<IndividualExposure<AccountId, Balance>> = vec![];
|
|
for i in 0..=MaxExposurePageSize::get() {
|
|
individual_exposures.push(IndividualExposure { who: i.into(), value: stake });
|
|
}
|
|
let exposure = Exposure::<AccountId, Balance> {
|
|
total: stake * (MaxExposurePageSize::get() as Balance + 2),
|
|
own: stake,
|
|
others: individual_exposures,
|
|
};
|
|
|
|
// add exposure for validators
|
|
Eras::<T>::upsert_exposure(0, &validator_one, exposure.clone());
|
|
Eras::<T>::upsert_exposure(0, &validator_two, exposure.clone());
|
|
|
|
// add some reward to be distributed
|
|
ErasValidatorReward::<T>::insert(0, 1000);
|
|
|
|
// SCENARIO: Validator with paged exposure (two pages).
|
|
// validators have not claimed rewards, so pending rewards is true.
|
|
assert!(Eras::<T>::pending_rewards(0, &validator_one));
|
|
assert!(Eras::<T>::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::<T>::pending_rewards(0, &validator_one));
|
|
assert!(Eras::<T>::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::<T>::pending_rewards(0, &validator_one));
|
|
// and payout fails for validator one
|
|
assert_noop!(
|
|
Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_one, 0),
|
|
Error::<T>::AlreadyClaimed.with_weight(err_weight)
|
|
);
|
|
// while pending reward is true for validator two
|
|
assert!(Eras::<T>::pending_rewards(0, &validator_two));
|
|
// and payout works again for validator two.
|
|
assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_two, 0));
|
|
});
|
|
}
|