Files
pezkuwi-sdk/bizinikiwi/pezframe/staking-async/src/tests/election_data_provider.rs
T
pezkuwichain 1c0e57d984 feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
2025-12-14 00:04:10 +03:00

945 lines
30 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 pezframe_election_provider_support::ElectionDataProvider;
#[test]
fn set_minimum_active_stake_is_correct() {
ExtBuilder::default()
.nominate(false)
.add_staker(61, 2_000, StakerStatus::<AccountId>::Nominator(vec![21]))
.add_staker(71, 10, StakerStatus::<AccountId>::Nominator(vec![21]))
.add_staker(81, 50, StakerStatus::<AccountId>::Nominator(vec![21]))
.build_and_execute(|| {
// default bounds are unbounded.
assert_ok!(<Staking as ElectionDataProvider>::electing_voters(
DataProviderBounds::default(),
0
));
assert_eq!(MinimumActiveStake::<Test>::get(), 10);
// remove staker with lower bond by limiting the number of voters and check
// `MinimumActiveStake` again after electing voters.
let bounds = ElectionBoundsBuilder::default().voters_count(5.into()).build();
assert_ok!(<Staking as ElectionDataProvider>::electing_voters(bounds.voters, 0));
assert_eq!(MinimumActiveStake::<Test>::get(), 50);
});
}
#[test]
fn set_minimum_active_stake_lower_bond_works() {
// lower non-zero active stake below `MinNominatorBond` is the minimum active stake if
// it is selected as part of the npos voters.
ExtBuilder::default().has_stakers(true).nominate(true).build_and_execute(|| {
assert_eq!(MinNominatorBond::<Test>::get(), 1);
assert_eq!(<Test as Config>::VoterList::count(), 4);
assert_ok!(Staking::bond(RuntimeOrigin::signed(4), 5, RewardDestination::Staked,));
assert_ok!(Staking::nominate(RuntimeOrigin::signed(4), vec![11]));
assert_eq!(<Test as Config>::VoterList::count(), 5);
let voters_before =
<Staking as ElectionDataProvider>::electing_voters(DataProviderBounds::default(), 0)
.unwrap();
assert_eq!(MinimumActiveStake::<Test>::get(), 5);
// update minimum nominator bond.
MinNominatorBond::<Test>::set(10);
assert_eq!(MinNominatorBond::<Test>::get(), 10);
// voter list still considers nominator 4 for voting, even though its active stake is
// lower than `MinNominatorBond`.
assert_eq!(<Test as Config>::VoterList::count(), 5);
let voters =
<Staking as ElectionDataProvider>::electing_voters(DataProviderBounds::default(), 0)
.unwrap();
assert_eq!(voters_before, voters);
// minimum active stake is lower than `MinNominatorBond`.
assert_eq!(MinimumActiveStake::<Test>::get(), 5);
});
}
#[test]
fn set_minimum_active_bond_corrupt_state() {
ExtBuilder::default()
.has_stakers(true)
.nominate(true)
.add_staker(61, 2_000, StakerStatus::<AccountId>::Nominator(vec![21]))
.build_and_execute(|| {
assert_eq!(Staking::weight_of(&101), 500);
let voters = <Staking as ElectionDataProvider>::electing_voters(
DataProviderBounds::default(),
0,
)
.unwrap();
assert_eq!(voters.len(), 5);
assert_eq!(MinimumActiveStake::<Test>::get(), 500);
Session::roll_until_active_era(10);
assert_ok!(Staking::unbond(RuntimeOrigin::signed(101), 200));
Session::roll_until_active_era(20);
assert_ok!(Staking::unbond(RuntimeOrigin::signed(101), 100));
// corrupt ledger state by lowering max unlocking chunks bounds.
MaxUnlockingChunks::set(1);
let voters = <Staking as ElectionDataProvider>::electing_voters(
DataProviderBounds::default(),
0,
)
.unwrap();
// number of returned voters decreases since ledger entry of stash 101 is now
// corrupt.
assert_eq!(voters.len(), 4);
// minimum active stake does not take into consideration the corrupt entry.
assert_eq!(MinimumActiveStake::<Test>::get(), 2_000);
// voter weight of corrupted ledger entry is 0.
assert_eq!(Staking::weight_of(&101), 0);
// reset max unlocking chunks for try_state to pass.
MaxUnlockingChunks::set(32);
})
}
#[test]
fn voters_include_self_vote() {
ExtBuilder::default().nominate(false).build_and_execute(|| {
// default bounds are unbounded.
assert!(<Validators<Test>>::iter().map(|(x, _)| x).all(|v| Staking::electing_voters(
DataProviderBounds::default(),
0
)
.unwrap()
.into_iter()
.any(|(w, _, t)| { v == w && t[0] == w })))
})
}
#[test]
#[should_panic]
#[cfg(debug_assertions)]
fn only_iterates_max_2_times_max_allowed_len() {
ExtBuilder::default()
.nominate(false)
// the best way to invalidate a bunch of nominators is to have them nominate a lot of
// ppl, but then lower the MaxNomination limit.
.add_staker(61, 2_000, StakerStatus::<AccountId>::Nominator(vec![21, 22, 23, 24, 25]))
.add_staker(71, 2_000, StakerStatus::<AccountId>::Nominator(vec![21, 22, 23, 24, 25]))
.add_staker(81, 2_000, StakerStatus::<AccountId>::Nominator(vec![21, 22, 23, 24, 25]))
.build_and_execute(|| {
let bounds_builder = ElectionBoundsBuilder::default();
// all voters ordered by stake,
assert_eq!(
<Test as Config>::VoterList::iter().collect::<Vec<_>>(),
vec![61, 71, 81, 11, 21, 31]
);
AbsoluteMaxNominations::set(2);
// we want 2 voters now, and in maximum we allow 4 iterations. This is what happens:
// 61 is pruned;
// 71 is pruned;
// 81 is pruned;
// 11 is taken;
// we finish since the 2x limit is reached.
assert_eq!(
Staking::electing_voters(bounds_builder.voters_count(2.into()).build().voters, 0)
.unwrap()
.iter()
.map(|(stash, _, _)| stash)
.copied()
.collect::<Vec<_>>(),
vec![11],
);
});
}
#[test]
fn respects_snapshot_count_limits() {
ExtBuilder::default()
.set_status(41, StakerStatus::Validator)
.build_and_execute(|| {
// sum of all nominators who'd be voters (1), plus the self-votes (4).
assert_eq!(<Test as Config>::VoterList::count(), 5);
let bounds_builder = ElectionBoundsBuilder::default();
// if voter count limit is less..
assert_eq!(
Staking::electing_voters(bounds_builder.voters_count(1.into()).build().voters, 0)
.unwrap()
.len(),
1
);
// if voter count limit is equal..
assert_eq!(
Staking::electing_voters(bounds_builder.voters_count(5.into()).build().voters, 0)
.unwrap()
.len(),
5
);
// if voter count limit is more.
assert_eq!(
Staking::electing_voters(bounds_builder.voters_count(55.into()).build().voters, 0)
.unwrap()
.len(),
5
);
// if target count limit is more..
assert_eq!(
Staking::electable_targets(
bounds_builder.targets_count(6.into()).build().targets,
0,
)
.unwrap()
.len(),
4
);
// if target count limit is equal..
assert_eq!(
Staking::electable_targets(
bounds_builder.targets_count(4.into()).build().targets,
0,
)
.unwrap()
.len(),
4
);
// if target limit count is less, then we return an error.
assert_eq!(
Staking::electable_targets(
bounds_builder.targets_count(1.into()).build().targets,
0
)
.unwrap()
.len(),
1,
);
});
}
#[test]
fn respects_snapshot_size_limits() {
ExtBuilder::default().build_and_execute(|| {
// voters: set size bounds that allows only for 1 voter.
let bounds = ElectionBoundsBuilder::default().voters_size(26.into()).build();
let elected = Staking::electing_voters(bounds.voters, 0).unwrap();
assert!(elected.encoded_size() == 26 as usize);
let prev_len = elected.len();
// larger size bounds means more quota for voters.
let bounds = ElectionBoundsBuilder::default().voters_size(100.into()).build();
let elected = Staking::electing_voters(bounds.voters, 0).unwrap();
assert!(elected.encoded_size() <= 100 as usize);
assert!(elected.len() > 1 && elected.len() > prev_len);
// targets: set size bounds that allows for only one target to fit in the snapshot.
let bounds = ElectionBoundsBuilder::default().targets_size(10.into()).build();
let elected = Staking::electable_targets(bounds.targets, 0).unwrap();
assert!(elected.encoded_size() == 9 as usize);
let prev_len = elected.len();
// larger size bounds means more space for targets.
let bounds = ElectionBoundsBuilder::default().targets_size(100.into()).build();
let elected = Staking::electable_targets(bounds.targets, 0).unwrap();
assert!(elected.encoded_size() <= 100 as usize);
assert!(elected.len() > 1 && elected.len() > prev_len);
});
}
#[test]
fn nomination_quota_checks_at_nominate_works() {
ExtBuilder::default().nominate(false).build_and_execute(|| {
// stash bond of 222 has a nomination quota of 2 targets.
bond(61, 222);
assert_eq!(Staking::api_nominations_quota(222), 2);
// nominating with targets below the nomination quota works.
assert_ok!(Staking::nominate(RuntimeOrigin::signed(61), vec![11]));
assert_ok!(Staking::nominate(RuntimeOrigin::signed(61), vec![11, 21]));
// nominating with targets above the nomination quota returns error.
assert_noop!(
Staking::nominate(RuntimeOrigin::signed(61), vec![11, 21, 31]),
Error::<Test>::TooManyTargets
);
});
}
#[test]
#[should_panic]
#[cfg(debug_assertions)]
fn change_of_absolute_max_nominations() {
use pezframe_election_provider_support::ElectionDataProvider;
ExtBuilder::default()
.add_staker(61, 10, StakerStatus::Nominator(vec![1]))
.add_staker(71, 10, StakerStatus::Nominator(vec![1, 2, 3]))
.balance_factor(10)
.build_and_execute(|| {
// pre-condition
assert_eq!(AbsoluteMaxNominations::get(), 16);
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(101, 2), (71, 3), (61, 1)]
);
// default bounds are unbounded.
let bounds = DataProviderBounds::default();
// 3 validators and 3 nominators
assert_eq!(Staking::electing_voters(bounds, 0).unwrap().len(), 3 + 3);
// abrupt change from 16 to 4, everyone should be fine.
AbsoluteMaxNominations::set(4);
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(101, 2), (71, 3), (61, 1)]
);
assert_eq!(Staking::electing_voters(bounds, 0).unwrap().len(), 3 + 3);
// No one can be chilled on account of non-decodable keys.
for k in Nominators::<Test>::iter_keys() {
assert_noop!(
Staking::chill_other(RuntimeOrigin::signed(1), k),
Error::<Test>::CannotChillOther
);
}
// abrupt change from 4 to 3, everyone should be fine.
AbsoluteMaxNominations::set(3);
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(101, 2), (71, 3), (61, 1)]
);
assert_eq!(Staking::electing_voters(bounds, 0).unwrap().len(), 3 + 3);
// As before, no one can be chilled on account of non-decodable keys.
for k in Nominators::<Test>::iter_keys() {
assert_noop!(
Staking::chill_other(RuntimeOrigin::signed(1), k),
Error::<Test>::CannotChillOther
);
}
// abrupt change from 3 to 2, this should cause some nominators to be non-decodable,
// and thus non-existent unless they update.
AbsoluteMaxNominations::set(2);
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(101, 2), (61, 1)]
);
// 101 and 61 still cannot be chilled by someone else.
for k in [101, 61].iter() {
assert_noop!(
Staking::chill_other(RuntimeOrigin::signed(1), *k),
Error::<Test>::CannotChillOther
);
}
// 71 is still in storage..
assert!(Nominators::<Test>::contains_key(71));
// but its value cannot be decoded and default is returned.
assert!(Nominators::<Test>::get(71).is_none());
assert_eq!(Staking::electing_voters(bounds, 0).unwrap().len(), 3 + 2);
assert!(Nominators::<Test>::contains_key(101));
// abrupt change from 2 to 1, this should cause some nominators to be non-decodable,
// and thus non-existent unless they update.
AbsoluteMaxNominations::set(1);
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(61, 1)]
);
// 61 *still* cannot be chilled by someone else.
assert_noop!(
Staking::chill_other(RuntimeOrigin::signed(1), 61),
Error::<Test>::CannotChillOther
);
assert!(Nominators::<Test>::contains_key(71));
assert!(Nominators::<Test>::contains_key(61));
assert!(Nominators::<Test>::get(71).is_none());
assert!(Nominators::<Test>::get(61).is_some());
assert_eq!(Staking::electing_voters(bounds, 0).unwrap().len(), 3 + 1);
// now one of them can revive themselves by re-nominating to a proper value.
assert_ok!(Staking::nominate(RuntimeOrigin::signed(71), vec![1]));
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(71, 1), (61, 1)]
);
// or they can be chilled by any account.
assert!(Nominators::<Test>::contains_key(101));
assert!(Nominators::<Test>::get(101).is_none());
assert_ok!(Staking::chill_other(RuntimeOrigin::signed(71), 101));
assert_eq!(*staking_events().last().unwrap(), Event::Chilled { stash: 101 });
assert!(!Nominators::<Test>::contains_key(101));
assert!(Nominators::<Test>::get(101).is_none());
})
}
#[test]
fn nomination_quota_max_changes_decoding() {
use pezframe_election_provider_support::ElectionDataProvider;
ExtBuilder::default()
.nominate(false)
.set_status(41, StakerStatus::Validator)
.add_staker(60, 10, StakerStatus::Nominator(vec![11]))
.add_staker(70, 10, StakerStatus::Nominator(vec![11, 21, 31]))
.add_staker(30, 10, StakerStatus::Nominator(vec![11, 21, 31, 41]))
.add_staker(50, 10, StakerStatus::Nominator(vec![11, 21, 31, 41]))
.balance_factor(11)
.build_and_execute(|| {
// pre-condition.
assert_eq!(MaxNominationsOf::<Test>::get(), 16);
let unbounded_election = DataProviderBounds::default();
assert_eq!(
Nominators::<Test>::iter()
.map(|(k, n)| (k, n.targets.len()))
.collect::<Vec<_>>(),
vec![(70, 3), (50, 4), (30, 4), (60, 1)]
);
// 4 validators and 4 nominators
assert_eq!(Staking::electing_voters(unbounded_election, 0).unwrap().len(), 4 + 4);
});
}
#[test]
fn api_nominations_quota_works() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(Staking::api_nominations_quota(10), MaxNominationsOf::<Test>::get());
assert_eq!(Staking::api_nominations_quota(333), MaxNominationsOf::<Test>::get());
assert_eq!(Staking::api_nominations_quota(222), 2);
assert_eq!(Staking::api_nominations_quota(111), 1);
})
}
#[test]
fn lazy_quota_npos_voters_works_above_quota() {
ExtBuilder::default()
.nominate(false)
// need to make 22, 23, 24 and 25 validators
.add_staker(22, 1000, StakerStatus::Validator)
.add_staker(23, 1000, StakerStatus::Validator)
.add_staker(24, 1000, StakerStatus::Validator)
.add_staker(25, 1000, StakerStatus::Validator)
.add_staker(
61,
300, // 300 bond has 16 nomination quota.
StakerStatus::<AccountId>::Nominator(vec![21, 22, 23, 24, 25]),
)
.build_and_execute(|| {
// unbond 78 from stash 60 so that it's bonded balance is 222, which has a lower
// nomination quota than at nomination time (max 2 targets).
assert_ok!(Staking::unbond(RuntimeOrigin::signed(61), 78));
assert_eq!(Staking::api_nominations_quota(300 - 78), 2);
// even through 61 has nomination quota of 2 at the time of the election, all the
// nominations (5) will be used.
assert_eq!(
Staking::electing_voters(DataProviderBounds::default(), 0)
.unwrap()
.iter()
.map(|(stash, _, targets)| (*stash, targets.len()))
.collect::<Vec<_>>(),
vec![(11, 1), (21, 1), (31, 1), (22, 1), (23, 1), (24, 1), (25, 1), (61, 5)],
);
});
}
#[test]
fn nominations_quota_limits_size_work() {
ExtBuilder::default()
.nominate(false)
.set_status(41, StakerStatus::Validator)
.add_staker(71, 333, StakerStatus::<AccountId>::Nominator(vec![11, 21, 31, 41]))
.build_and_execute(|| {
// nominations of 71 won't be added due to voter size limit exceeded.
let bounds = ElectionBoundsBuilder::default().voters_size(101.into()).build();
assert_eq!(
Staking::electing_voters(bounds.voters, 0)
.unwrap()
.iter()
.map(|(stash, _, targets)| (*stash, targets.len()))
.collect::<Vec<_>>(),
vec![(41, 1), (11, 1), (21, 1), (31, 1)],
);
assert_eq!(
*staking_events().last().unwrap(),
Event::SnapshotVotersSizeExceeded { size: 100 }
);
// however, if the election voter size bounds were larger, the snapshot would
// include the electing voters of 70.
let bounds = ElectionBoundsBuilder::default().voters_size(1_000.into()).build();
assert_eq!(
Staking::electing_voters(bounds.voters, 0)
.unwrap()
.iter()
.map(|(stash, _, targets)| (*stash, targets.len()))
.collect::<Vec<_>>(),
vec![(41, 1), (11, 1), (21, 1), (31, 1), (71, 4)],
);
});
}
mod sorted_list_provider {
use super::*;
use pezframe_election_provider_support::SortedListProvider;
#[test]
fn re_nominate_does_not_change_counters_or_list() {
ExtBuilder::default().nominate(true).build_and_execute(|| {
// given
let pre_insert_voter_count =
(Nominators::<Test>::count() + Validators::<Test>::count()) as u32;
assert_eq!(<Test as Config>::VoterList::count(), pre_insert_voter_count);
assert_eq!(
<Test as Config>::VoterList::iter().collect::<Vec<_>>(),
vec![11, 21, 31, 101]
);
// when account 101 renominates
assert_ok!(Staking::nominate(RuntimeOrigin::signed(101), vec![31]));
// then counts don't change
assert_eq!(<Test as Config>::VoterList::count(), pre_insert_voter_count);
// and the list is the same
assert_eq!(
<Test as Config>::VoterList::iter().collect::<Vec<_>>(),
vec![11, 21, 31, 101]
);
});
}
#[test]
fn re_validate_does_not_change_counters_or_list() {
ExtBuilder::default().nominate(false).build_and_execute(|| {
// given
let pre_insert_voter_count =
(Nominators::<Test>::count() + Validators::<Test>::count()) as u32;
assert_eq!(<Test as Config>::VoterList::count(), pre_insert_voter_count);
assert_eq!(<Test as Config>::VoterList::iter().collect::<Vec<_>>(), vec![11, 21, 31]);
// when account 11 re-validates
assert_ok!(Staking::validate(RuntimeOrigin::signed(11), Default::default()));
// then counts don't change
assert_eq!(<Test as Config>::VoterList::count(), pre_insert_voter_count);
// and the list is the same
assert_eq!(<Test as Config>::VoterList::iter().collect::<Vec<_>>(), vec![11, 21, 31]);
});
}
}
mod paged_snapshot {
use super::*;
#[test]
fn target_snapshot_works() {
ExtBuilder::default()
.nominate(true)
.set_status(41, StakerStatus::Validator)
.set_status(51, StakerStatus::Validator)
.set_status(101, StakerStatus::Idle)
.build_and_execute(|| {
// all registered validators.
let all_targets = vec![51, 31, 41, 21, 11];
assert_eq_uvec!(
<Test as Config>::TargetList::iter().collect::<Vec<_>>(),
all_targets,
);
// 3 targets per page.
let bounds =
ElectionBoundsBuilder::default().targets_count(3.into()).build().targets;
let targets =
<Staking as ElectionDataProvider>::electable_targets(bounds, 0).unwrap();
assert_eq_uvec!(targets, all_targets.iter().take(3).cloned().collect::<Vec<_>>());
// emulates a no bounds target snapshot request.
let bounds =
ElectionBoundsBuilder::default().targets_count(u32::MAX.into()).build().targets;
let single_page_targets =
<Staking as ElectionDataProvider>::electable_targets(bounds, 0).unwrap();
// complete set of paged targets is the same as single page, no bounds set of
// targets.
assert_eq_uvec!(all_targets, single_page_targets);
})
}
#[test]
fn target_snapshot_multi_page_redundant() {
ExtBuilder::default().build_and_execute(|| {
let all_targets = vec![31, 21, 11];
assert_eq_uvec!(<Test as Config>::TargetList::iter().collect::<Vec<_>>(), all_targets,);
// no bounds.
let bounds =
ElectionBoundsBuilder::default().targets_count(u32::MAX.into()).build().targets;
// target snapshot supports only single-page, thus it is redundant what's the page index
// requested.
let snapshot = Staking::electable_targets(bounds, 0).unwrap();
assert!(
snapshot == all_targets &&
snapshot == Staking::electable_targets(bounds, 1).unwrap() &&
snapshot == Staking::electable_targets(bounds, 2).unwrap() &&
snapshot == Staking::electable_targets(bounds, u32::MAX).unwrap(),
);
})
}
#[test]
fn voter_snapshot_works() {
ExtBuilder::default()
.nominate(true)
.set_status(51, StakerStatus::Validator)
.set_status(41, StakerStatus::Nominator(vec![51]))
.set_status(101, StakerStatus::Validator)
.build_and_execute(|| {
let bounds = ElectionBoundsBuilder::default().voters_count(3.into()).build().voters;
assert_eq!(
<Test as Config>::VoterList::iter()
.collect::<Vec<_>>()
.into_iter()
.map(|v| (v, <Test as Config>::VoterList::get_score(&v).unwrap()))
.collect::<Vec<_>>(),
vec![(51, 5000), (41, 4000), (11, 1000), (21, 1000), (31, 500), (101, 500)],
);
let mut all_voters = vec![];
let voters_page_3 = <Staking as ElectionDataProvider>::electing_voters(bounds, 3)
.unwrap()
.into_iter()
.map(|(a, _, _)| a)
.collect::<Vec<_>>();
all_voters.extend(voters_page_3.clone());
assert_eq!(voters_page_3, vec![51, 41, 11]);
assert_eq!(VoterSnapshotStatus::<Test>::get(), SnapshotStatus::Ongoing(11));
let voters_page_2 = <Staking as ElectionDataProvider>::electing_voters(bounds, 2)
.unwrap()
.into_iter()
.map(|(a, _, _)| a)
.collect::<Vec<_>>();
all_voters.extend(voters_page_2.clone());
assert_eq!(voters_page_2, vec![21, 31, 101]);
// all voters in the list have been consumed.
assert_eq!(VoterSnapshotStatus::<Test>::get(), SnapshotStatus::Consumed);
// thus page 1 and 0 are empty.
assert!(<Staking as ElectionDataProvider>::electing_voters(bounds, 1)
.unwrap()
.is_empty());
assert_eq!(VoterSnapshotStatus::<Test>::get(), SnapshotStatus::Consumed);
assert!(<Staking as ElectionDataProvider>::electing_voters(bounds, 0)
.unwrap()
.is_empty());
// last page has been requested, reset the snapshot status to waiting.
assert_eq!(VoterSnapshotStatus::<Test>::get(), SnapshotStatus::Waiting);
// now request 1 page with bounds where all registered voters fit. u32::MAX
// emulates a no bounds request.
let bounds =
ElectionBoundsBuilder::default().voters_count(u32::MAX.into()).build().targets;
let single_page_voters =
<Staking as ElectionDataProvider>::electing_voters(bounds, 0)
.unwrap()
.into_iter()
.map(|(a, _, _)| a)
.collect::<Vec<_>>();
// complete set of paged voters is the same as single page, no bounds set of
// voters.
assert_eq!(all_voters, single_page_voters);
})
}
#[test]
fn voter_list_locked_during_multi_page_snapshot() {
ExtBuilder::default()
.nominate(true)
.set_status(51, StakerStatus::Validator)
.set_status(41, StakerStatus::Nominator(vec![51]))
.set_status(101, StakerStatus::Validator)
.build_and_execute(|| {
let bounds = ElectionBoundsBuilder::default().voters_count(2.into()).build().voters;
assert_eq!(
<Test as Config>::VoterList::iter()
.collect::<Vec<_>>()
.into_iter()
.map(|v| (v, <Test as Config>::VoterList::get_score(&v).unwrap()))
.collect::<Vec<_>>(),
vec![(51, 5000), (41, 4000), (11, 1000), (21, 1000), (31, 500), (101, 500)],
);
// initially not locked
assert_eq!(pezpallet_bags_list::Lock::<T, VoterBagsListInstance>::get(), None);
let voters_page_3 = <Staking as ElectionDataProvider>::electing_voters(bounds, 3)
.unwrap()
.into_iter()
.map(|(a, _, _)| a)
.collect::<Vec<_>>();
assert_eq!(voters_page_3, vec![51, 41]);
assert_eq!(VoterSnapshotStatus::<Test>::get(), SnapshotStatus::Ongoing(41));
assert_eq!(pezpallet_bags_list::Lock::<T, VoterBagsListInstance>::get(), Some(()));
hypothetically!({});
let voters_page_2 = <Staking as ElectionDataProvider>::electing_voters(bounds, 2)
.unwrap()
.into_iter()
.map(|(a, _, _)| a)
.collect::<Vec<_>>();
// still locked
assert_eq!(voters_page_2, vec![11, 21]);
assert_eq!(VoterSnapshotStatus::<Test>::get(), SnapshotStatus::Ongoing(21));
assert_eq!(pezpallet_bags_list::Lock::<T, VoterBagsListInstance>::get(), Some(()));
let voters_page_1 = <Staking as ElectionDataProvider>::electing_voters(bounds, 1)
.unwrap()
.into_iter()
.map(|(a, _, _)| a)
.collect::<Vec<_>>();
// consumed, and we already unlock
assert_eq_uvec!(voters_page_1, vec![31, 101]);
assert_eq!(VoterSnapshotStatus::<Test>::get(), SnapshotStatus::Consumed);
assert_eq!(pezpallet_bags_list::Lock::<T, VoterBagsListInstance>::get(), None);
// calling page zero will unlock us.
assert!(<Staking as ElectionDataProvider>::electing_voters(bounds, 0)
.unwrap()
.is_empty());
assert_eq!(VoterSnapshotStatus::<Test>::get(), SnapshotStatus::Waiting);
assert_eq!(pezpallet_bags_list::Lock::<T, VoterBagsListInstance>::get(), None);
})
}
#[test]
fn voter_list_not_updated_when_locked() {
ExtBuilder::default()
.nominate(true)
.set_status(51, StakerStatus::Validator)
.set_status(41, StakerStatus::Nominator(vec![51]))
.set_status(101, StakerStatus::Validator)
.build_and_execute(|| {
let bounds = ElectionBoundsBuilder::default().voters_count(2.into()).build().voters;
assert_eq!(
<Test as Config>::VoterList::iter()
.collect::<Vec<_>>()
.into_iter()
.map(|v| (v, <Test as Config>::VoterList::get_score(&v).unwrap()))
.collect::<Vec<_>>(),
vec![(51, 5000), (41, 4000), (11, 1000), (21, 1000), (31, 500), (101, 500)],
);
// initial bag of 51
assert_eq!(
pezpallet_bags_list::ListNodes::<T, VoterBagsListInstance>::get(51)
.unwrap()
.bag_upper,
10_000
);
// original bag of 11
assert_eq!(
pezpallet_bags_list::ListNodes::<T, VoterBagsListInstance>::get(11)
.unwrap()
.bag_upper,
1000
);
// initially not locked
assert_eq!(pezpallet_bags_list::Lock::<T, VoterBagsListInstance>::get(), None);
let voters_page_3 = <Staking as ElectionDataProvider>::electing_voters(bounds, 3)
.unwrap()
.into_iter()
.map(|(a, _, _)| a)
.collect::<Vec<_>>();
assert_eq!(voters_page_3, vec![51, 41]);
assert_eq!(VoterSnapshotStatus::<Test>::get(), SnapshotStatus::Ongoing(41));
assert_eq!(pezpallet_bags_list::Lock::<T, VoterBagsListInstance>::get(), Some(()));
// 51 who is already part of the list might want to unbond. They are already in the
// snapshot, and their position is not updated
hypothetically!({
assert_ok!(Staking::unbond(RuntimeOrigin::signed(51), 500));
// they are still in the original bag
assert_eq!(
pezpallet_bags_list::ListNodes::<T, VoterBagsListInstance>::get(51)
.unwrap()
.bag_upper,
10_000
);
});
// 11 who is not part of the snapshot yet might want to bond a lot extra, this is
// not reflected in this election.
hypothetically!({
crate::asset::set_stakeable_balance::<T>(&11, 10000);
assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), 5000));
// they are still in the original bag
assert_eq!(
pezpallet_bags_list::ListNodes::<T, VoterBagsListInstance>::get(11)
.unwrap()
.bag_upper,
1000
);
});
})
}
}
mod score_provider {
use pezframe_election_provider_support::ScoreProvider;
use super::*;
#[test]
fn no_score_for_chilled_stakers() {
ExtBuilder::default().build_and_execute(|| {
// given 41 being a chilled staker
assert!(
Ledger::<Test>::get(41).is_some() &&
!Validators::<Test>::contains_key(41) &&
!Nominators::<Test>::contains_key(41)
);
// then they will not have a score when bags-list wants to update it.
assert!(<Staking as ScoreProvider<_>>::score(&41).is_none());
});
}
#[test]
fn no_score_for_non_stakers() {
ExtBuilder::default().build_and_execute(|| {
// given 777 being neither a nominator nor a validator in this pallet.
assert!(
!Ledger::<Test>::get(777).is_some() &&
!Validators::<Test>::contains_key(777) &&
!Nominators::<Test>::contains_key(777)
);
// then it will not have a score when bags-list wants to update it.
assert!(<Staking as ScoreProvider<_>>::score(&777).is_none());
});
}
#[test]
fn score_for_validators_nominators() {
ExtBuilder::default().nominate(true).build_and_execute(|| {
// Given 101 being a nominator
assert!(
Ledger::<Test>::get(101).unwrap().active == 500 &&
!Validators::<Test>::contains_key(101) &&
Nominators::<Test>::contains_key(101)
);
// then it will have a score.
assert_eq!(<Staking as ScoreProvider<_>>::score(&101), Some(500));
// given 11 being a validator
assert!(
Ledger::<Test>::get(11).unwrap().active == 1000 &&
Validators::<Test>::contains_key(11) &&
!Nominators::<Test>::contains_key(11)
);
// then it will have a score.
assert_eq!(<Staking as ScoreProvider<_>>::score(&11), Some(1000));
});
}
}
#[test]
fn from_most_staked_to_least_staked() {
ExtBuilder::default()
.nominate(true)
.set_status(51, StakerStatus::Validator)
.set_status(41, StakerStatus::Nominator(vec![51]))
.set_status(101, StakerStatus::Validator)
.set_stake(41, 11000)
.set_stake(51, 2500)
.set_stake(101, 35)
.build_and_execute(|| {
assert_eq!(THRESHOLDS.to_vec(), [10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]);
assert_eq!(
<Test as Config>::VoterList::iter()
.collect::<Vec<_>>()
.into_iter()
.map(|v| (v, <Test as Config>::VoterList::get_score(&v).unwrap()))
.collect::<Vec<_>>(),
vec![(41, 11000), (51, 2500), (11, 1000), (21, 1000), (31, 500), (101, 35)],
);
});
}