Files
pezkuwi-sdk/bizinikiwi/pezframe/staking-async/src/tests/election_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

1350 lines
48 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::{EraElectionPlanner, Eras};
use pezframe_support::assert_ok;
use pezsp_npos_elections::Support;
use bizinikiwi_test_utils::assert_eq_uvec;
use crate::tests::session_mock::ReceivedValidatorSets;
#[test]
fn planning_era_offset_less_0() {
// same as `basic_setup_sessions_per_era`, but notice how `PagedElectionProceeded` happens
// one session later, and planning era is incremented one session later
ExtBuilder::default()
.session_per_era(6)
.planning_era_offset(0)
.no_flush_events()
.build_and_execute(|| {
// this essentially makes the session duration 8. After 6 sessions we realize we have do
// start an election (since offset = 0), then it is queued for one session (7), and then
// activated (8).
assert_eq!(Session::current_index(), 8);
assert_eq!(active_era(), 1);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 1, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 2, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 3, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 4, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 5, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 6, active_era: 0, planned_era: 1 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 7, active_era: 0, planned_era: 1 },
Event::EraPaid { era_index: 0, validator_payout: 20000, remainder: 20000 },
Event::SessionRotated { starting_session: 8, active_era: 1, planned_era: 1 }
]
);
Session::roll_until_active_era(2);
assert_eq!(Session::current_index(), 16);
assert_eq!(active_era(), 2);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 9, active_era: 1, planned_era: 1 },
Event::SessionRotated { starting_session: 10, active_era: 1, planned_era: 1 },
Event::SessionRotated { starting_session: 11, active_era: 1, planned_era: 1 },
Event::SessionRotated { starting_session: 12, active_era: 1, planned_era: 1 },
Event::SessionRotated { starting_session: 13, active_era: 1, planned_era: 1 },
Event::SessionRotated { starting_session: 14, active_era: 1, planned_era: 2 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 15, active_era: 1, planned_era: 2 },
Event::EraPaid { era_index: 1, validator_payout: 20000, remainder: 20000 },
Event::SessionRotated { starting_session: 16, active_era: 2, planned_era: 2 }
]
);
});
}
#[test]
fn planning_era_offset_works_1() {
// same as `basic_setup_sessions_per_era`, but notice how `PagedElectionProceeded` happens
// one session later, and planning era is incremented one session later
ExtBuilder::default()
.session_per_era(6)
.planning_era_offset(1)
.no_flush_events()
.build_and_execute(|| {
// this essentially makes the session duration 7. After 5 sessions we realize we have do
// start an election (since offset = 1), then it is queued for one session (6), and then
// activated (7).
assert_eq!(Session::current_index(), 7);
assert_eq!(active_era(), 1);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 1, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 2, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 3, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 4, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 5, active_era: 0, planned_era: 1 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 6, active_era: 0, planned_era: 1 },
Event::EraPaid { era_index: 0, validator_payout: 17500, remainder: 17500 },
Event::SessionRotated { starting_session: 7, active_era: 1, planned_era: 1 }
]
);
Session::roll_until_active_era(2);
assert_eq!(Session::current_index(), 14);
assert_eq!(active_era(), 2);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 8, active_era: 1, planned_era: 1 },
Event::SessionRotated { starting_session: 9, active_era: 1, planned_era: 1 },
Event::SessionRotated { starting_session: 10, active_era: 1, planned_era: 1 },
Event::SessionRotated { starting_session: 11, active_era: 1, planned_era: 1 },
Event::SessionRotated { starting_session: 12, active_era: 1, planned_era: 2 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 13, active_era: 1, planned_era: 2 },
Event::EraPaid { era_index: 1, validator_payout: 17500, remainder: 17500 },
Event::SessionRotated { starting_session: 14, active_era: 2, planned_era: 2 }
]
);
});
}
#[test]
fn planning_era_offset_works_2() {
ExtBuilder::default()
.session_per_era(6)
.planning_era_offset(2)
.no_flush_events()
.build_and_execute(|| {
// start election at 4, and send it over. Buffered at 6, activated at 6. This is the
// expected behavior, and the default in `mock.rs`.
assert_eq!(Session::current_index(), 6);
assert_eq!(active_era(), 1);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 1, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 2, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 3, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 4, active_era: 0, planned_era: 1 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 5, active_era: 0, planned_era: 1 },
Event::EraPaid { era_index: 0, validator_payout: 15000, remainder: 15000 },
Event::SessionRotated { starting_session: 6, active_era: 1, planned_era: 1 }
]
);
Session::roll_until_active_era(2);
assert_eq!(Session::current_index(), 12);
assert_eq!(active_era(), 2);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 7, active_era: 1, planned_era: 1 },
Event::SessionRotated { starting_session: 8, active_era: 1, planned_era: 1 },
Event::SessionRotated { starting_session: 9, active_era: 1, planned_era: 1 },
Event::SessionRotated { starting_session: 10, active_era: 1, planned_era: 2 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 11, active_era: 1, planned_era: 2 },
Event::EraPaid { era_index: 1, validator_payout: 15000, remainder: 15000 },
Event::SessionRotated { starting_session: 12, active_era: 2, planned_era: 2 }
]
);
});
}
#[test]
fn planning_era_offset_works_smart() {
ExtBuilder::default()
.session_per_era(6)
.smart_era_planner()
.no_flush_events()
.build_and_execute(|| {
// This works exactly the same as offset = 2, which means we send and rotate validators
// such that the era duration remains 6 sessions.
assert_eq!(Session::current_index(), 6);
assert_eq!(active_era(), 1);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 1, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 2, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 3, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 4, active_era: 0, planned_era: 1 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 5, active_era: 0, planned_era: 1 },
Event::EraPaid { era_index: 0, validator_payout: 15000, remainder: 15000 },
Event::SessionRotated { starting_session: 6, active_era: 1, planned_era: 1 }
]
);
Session::roll_until_active_era(2);
assert_eq!(Session::current_index(), 12);
assert_eq!(active_era(), 2);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 7, active_era: 1, planned_era: 1 },
Event::SessionRotated { starting_session: 8, active_era: 1, planned_era: 1 },
Event::SessionRotated { starting_session: 9, active_era: 1, planned_era: 1 },
Event::SessionRotated { starting_session: 10, active_era: 1, planned_era: 2 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 11, active_era: 1, planned_era: 2 },
Event::EraPaid { era_index: 1, validator_payout: 15000, remainder: 15000 },
Event::SessionRotated { starting_session: 12, active_era: 2, planned_era: 2 }
]
);
});
}
#[test]
fn planning_era_offset_works_smart_with_delay() {
ExtBuilder::default()
.session_per_era(6)
.election_delay(7)
.smart_era_planner()
.no_flush_events()
.build_and_execute(|| {
// Same as above, but now election takes more time, more than 1 session to be exact.
// Notice how the era duration is kept at 6.
assert_eq!(Session::current_index(), 6);
assert_eq!(active_era(), 1);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 1, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 2, active_era: 0, planned_era: 0 },
Event::SessionRotated { starting_session: 3, active_era: 0, planned_era: 1 },
Event::SessionRotated { starting_session: 4, active_era: 0, planned_era: 1 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 5, active_era: 0, planned_era: 1 },
Event::EraPaid { era_index: 0, validator_payout: 15000, remainder: 15000 },
Event::SessionRotated { starting_session: 6, active_era: 1, planned_era: 1 }
]
);
Session::roll_until_active_era(2);
assert_eq!(Session::current_index(), 12);
assert_eq!(active_era(), 2);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 7, active_era: 1, planned_era: 1 },
Event::SessionRotated { starting_session: 8, active_era: 1, planned_era: 1 },
Event::SessionRotated { starting_session: 9, active_era: 1, planned_era: 2 },
Event::SessionRotated { starting_session: 10, active_era: 1, planned_era: 2 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 11, active_era: 1, planned_era: 2 },
Event::EraPaid { era_index: 1, validator_payout: 15000, remainder: 15000 },
Event::SessionRotated { starting_session: 12, active_era: 2, planned_era: 2 }
]
);
});
}
#[test]
fn new_era_elects_correct_number_of_validators() {
ExtBuilder::default().nominate(true).validator_count(1).build_and_execute(|| {
assert_eq!(ValidatorCount::<Test>::get(), 1);
assert_eq!(session_validators().len(), 1);
})
}
#[test]
fn less_than_needed_candidates_works() {
ExtBuilder::default().validator_count(4).nominate(false).build_and_execute(|| {
assert_eq_uvec!(Session::validators(), vec![31, 21, 11]);
Session::roll_until_active_era(2);
// Previous set is selected.
assert_eq_uvec!(Session::validators(), vec![31, 21, 11]);
// Only has self votes.
assert!(ErasStakersPaged::<T>::iter_prefix_values((active_era(),))
.all(|exposure| exposure.others.is_empty()));
});
}
mod paged_exposures {
use crate::session_rotation::Rotator;
use super::*;
#[test]
fn can_page_exposure() {
let mut others: Vec<IndividualExposure<AccountId, Balance>> = vec![];
let mut total_stake: Balance = 0;
// 19 nominators
for i in 1..20 {
let individual_stake: Balance = 100 * i as Balance;
others.push(IndividualExposure { who: i, value: individual_stake });
total_stake += individual_stake;
}
let own_stake: Balance = 500;
total_stake += own_stake;
assert_eq!(total_stake, 19_500);
// build full exposure set
let exposure: Exposure<AccountId, Balance> =
Exposure { total: total_stake, own: own_stake, others };
// when
let (exposure_metadata, exposure_page): (
PagedExposureMetadata<Balance>,
Vec<ExposurePage<AccountId, Balance>>,
) = exposure.clone().into_pages(3);
// then
// 7 pages of nominators.
assert_eq!(exposure_page.len(), 7);
assert_eq!(exposure_metadata.page_count, 7);
// first page stake = 100 + 200 + 300
assert!(matches!(exposure_page[0], ExposurePage { page_total: 600, .. }));
// second page stake = 0 + 400 + 500 + 600
assert!(matches!(exposure_page[1], ExposurePage { page_total: 1500, .. }));
// verify overview has the total
assert_eq!(exposure_metadata.total, 19_500);
// verify total stake is same as in the original exposure.
assert_eq!(
exposure_page.iter().map(|a| a.page_total).reduce(|a, b| a + b).unwrap(),
19_500 - exposure_metadata.own
);
// verify own stake is correct
assert_eq!(exposure_metadata.own, 500);
// verify number of nominators are same as in the original exposure.
assert_eq!(exposure_page.iter().map(|a| a.others.len()).reduce(|a, b| a + b).unwrap(), 19);
assert_eq!(exposure_metadata.nominator_count, 19);
}
fn exposure_pages(era: u32, who: AccountId) -> Vec<(u32, (Balance, u32))> {
let mut res = ErasStakersPaged::<T>::iter_prefix((era, who))
.map(|(page, expo)| (page, (expo.page_total, expo.others.len() as u32)))
.collect::<Vec<_>>();
res.sort_by_key(|(page, _)| *page);
res
}
#[test]
fn store_stakers_info_elect_works_page_size_1() {
// scenario:
// 2 page election. 5 validators involved.
// Validator 1 has own-stake in first page, other-stake in both pages.
// Validator 2 has own-stake in second page, other-stake in both pages
// Validator 3 has no own-stake, other-stake in both pages
// Validator 4 has no own-stake, other-stake in second page only
// Validator 5 has no own-stake, other-stake in first page only
// all other stakes are 250, and the same 101 account as it makes no difference.
ExtBuilder::default().exposures_page_size(1).build_and_execute(|| {
// roll 1 session such that we are in "planning more"
Session::roll_to_next_session();
assert_eq!(Rotator::<T>::planned_era(), 2);
assert_eq!(Rotator::<T>::active_era(), 1);
assert_eq!(ErasTotalStake::<T>::get(Rotator::<T>::planned_era()), 0);
assert_eq!(
ErasStakersOverview::<T>::iter_prefix(Rotator::<T>::planned_era()).count(),
0
);
assert_eq!(
ErasStakersPaged::<T>::iter_prefix((Rotator::<T>::planned_era(),)).count(),
0
);
// stuff that goes into first page
let exposures_page1 = bounded_vec![
// validator 1, own-stake and other stake
(
1,
Exposure::<AccountId, Balance> {
total: 1000 + 250,
own: 1000,
others: vec![IndividualExposure { who: 101, value: 250 }]
}
),
// validator 2, other stake only
(
2,
Exposure::<AccountId, Balance> {
total: 250,
own: 0,
others: vec![IndividualExposure { who: 101, value: 250 }]
}
),
// validator 3, part of other stake only
(
3,
Exposure::<AccountId, Balance> {
total: 250,
own: 0,
others: vec![IndividualExposure { who: 101, value: 250 }]
}
),
// validator 4, nothing
// validator 5, part of other stake only
(
5,
Exposure::<AccountId, Balance> {
total: 250,
own: 0,
others: vec![IndividualExposure { who: 101, value: 250 }]
}
),
];
// stuff that goes into second page
let exposure_page2: BoundedExposuresOf<Test> = bounded_vec![
// validator 1, other stake only
(
1,
Exposure::<AccountId, Balance> {
total: 250,
own: 0,
others: vec![IndividualExposure { who: 101, value: 250 }]
}
),
// validator 2, own-stake and other stake
(
2,
Exposure::<AccountId, Balance> {
total: 1000 + 250,
own: 1000,
others: vec![IndividualExposure { who: 101, value: 250 }]
}
),
// validator 3, part of other stake only
(
3,
Exposure::<AccountId, Balance> {
total: 250,
own: 0,
others: vec![IndividualExposure { who: 101, value: 250 }]
}
),
// validator 4, part of other stake only
(
4,
Exposure::<AccountId, Balance> {
total: 250,
own: 0,
others: vec![IndividualExposure { who: 101, value: 250 }]
}
),
// validator 5, nothing
];
// our exposures are stored for this era.
let current_era = current_era();
assert_eq!(ErasTotalStake::<T>::get(current_era), 0);
// insert page 1 of exposures
assert_eq!(
EraElectionPlanner::<T>::store_stakers_info(exposures_page1, current_era).to_vec(),
vec![1, 2, 3, 5]
);
// overviews after inserting first page of exposures.
assert_eq!(
ErasStakersOverview::<T>::get(current_era, &1).unwrap(),
PagedExposureMetadata { total: 1250, own: 1000, nominator_count: 1, page_count: 1 },
);
assert_eq!(
ErasStakersOverview::<T>::get(current_era, &2).unwrap(),
PagedExposureMetadata { total: 250, own: 0, nominator_count: 1, page_count: 1 },
);
assert_eq!(
ErasStakersOverview::<T>::get(current_era, &3).unwrap(),
PagedExposureMetadata { total: 250, own: 0, nominator_count: 1, page_count: 1 },
);
assert!(ErasStakersOverview::<T>::get(current_era, &4).is_none());
assert_eq!(
ErasStakersOverview::<T>::get(current_era, &5).unwrap(),
PagedExposureMetadata { total: 250, own: 0, nominator_count: 1, page_count: 1 },
);
// total stake after first page of exposures.
assert_eq!(ErasTotalStake::<T>::get(current_era), 2000);
// details after first page of exposures.
assert_eq!(exposure_pages(current_era, 1), vec![(0, (250, 1))]);
assert_eq!(exposure_pages(current_era, 2), vec![(0, (250, 1))]);
assert_eq!(exposure_pages(current_era, 3), vec![(0, (250, 1))]);
assert_eq!(exposure_pages(current_era, 4), vec![]);
assert_eq!(exposure_pages(current_era, 5), vec![(0, (250, 1))]);
// insert page 2 of exposures
assert_eq!(
EraElectionPlanner::<T>::store_stakers_info(exposure_page2, current_era).to_vec(),
vec![1, 2, 3, 4]
);
// overviews after inserting second page of exposures.
assert_eq!(
ErasStakersOverview::<T>::get(current_era, &1).unwrap(),
PagedExposureMetadata { total: 1500, own: 1000, nominator_count: 2, page_count: 2 },
);
assert_eq!(
ErasStakersOverview::<T>::get(current_era, &2).unwrap(),
PagedExposureMetadata { total: 1500, own: 1000, nominator_count: 2, page_count: 2 },
);
assert_eq!(
ErasStakersOverview::<T>::get(current_era, &3).unwrap(),
PagedExposureMetadata { total: 500, own: 0, nominator_count: 2, page_count: 2 },
);
assert_eq!(
ErasStakersOverview::<T>::get(current_era, &4).unwrap(),
PagedExposureMetadata { total: 250, own: 0, nominator_count: 1, page_count: 1 },
);
assert_eq!(
ErasStakersOverview::<T>::get(current_era, &5).unwrap(),
PagedExposureMetadata { total: 250, own: 0, nominator_count: 1, page_count: 1 },
);
// total stake after second page of exposures.
assert_eq!(ErasTotalStake::<T>::get(current_era), 4000);
// details after second page of exposures.
assert_eq!(exposure_pages(current_era, 1), vec![(0, (250, 1)), (1, (250, 1))]);
assert_eq!(exposure_pages(current_era, 2), vec![(0, (250, 1)), (1, (250, 1))]);
assert_eq!(exposure_pages(current_era, 3), vec![(0, (250, 1)), (1, (250, 1))]);
assert_eq!(exposure_pages(current_era, 4), vec![(0, (250, 1))]);
assert_eq!(exposure_pages(current_era, 5), vec![(0, (250, 1))]);
})
}
#[test]
fn store_stakers_info_elect_works_page_size_2() {
// scenario:
// 2 page election. 5 validators involved.
// Validator 1 has own-stake in first page, other-stake in both pages (3 + 2).
// Validator 2 has own-stake in second page, other-stake in both pages (2 + 3)
// Validator 3 has no own-stake, other-stake in both pages (3 + 1)
// Validator 4 has no own-stake, other-stake in second page only (0 + 3)
// Validator 5 has no own-stake, other-stake in first page only (2 + 0)
// all other stakes are 250, and the same 101 (or more) account as it makes no difference.
ExtBuilder::default().exposures_page_size(2).build_and_execute(|| {
// roll 1 session such that we are in "planning more"
Session::roll_to_next_session();
assert_eq!(Rotator::<T>::planned_era(), 2);
assert_eq!(Rotator::<T>::active_era(), 1);
assert_eq!(ErasTotalStake::<T>::get(Rotator::<T>::planned_era()), 0);
assert_eq!(
ErasStakersOverview::<T>::iter_prefix(Rotator::<T>::planned_era()).count(),
0
);
assert_eq!(
ErasStakersPaged::<T>::iter_prefix((Rotator::<T>::planned_era(),)).count(),
0
);
// stuff that goes into first page
let exposures_page1 = bounded_vec![
// validator 1, own-stake and other stake
(
1,
Exposure::<AccountId, Balance> {
total: 1000 + 750,
own: 1000,
others: vec![
IndividualExposure { who: 101, value: 250 },
IndividualExposure { who: 102, value: 250 },
IndividualExposure { who: 103, value: 250 },
]
}
),
// validator 2, other stake only
(
2,
Exposure::<AccountId, Balance> {
total: 500,
own: 0,
others: vec![
IndividualExposure { who: 101, value: 250 },
IndividualExposure { who: 102, value: 250 },
]
}
),
// validator 3, part of other stake only
(
3,
Exposure::<AccountId, Balance> {
total: 750,
own: 0,
others: vec![
IndividualExposure { who: 101, value: 250 },
IndividualExposure { who: 102, value: 250 },
IndividualExposure { who: 103, value: 250 }
]
}
),
// validator 4, nothing
// validator 5, all of other stake only
(
5,
Exposure::<AccountId, Balance> {
total: 500,
own: 0,
others: vec![
IndividualExposure { who: 101, value: 250 },
IndividualExposure { who: 102, value: 250 },
]
}
),
];
let exposure_page2: BoundedExposuresOf<Test> = bounded_vec![
// validator 1, other stake only
(
1,
Exposure::<AccountId, Balance> {
total: 500,
own: 0,
others: vec![
IndividualExposure { who: 104, value: 250 },
IndividualExposure { who: 105, value: 250 }
]
}
),
// validator 2, own-stake and other stake
(
2,
Exposure::<AccountId, Balance> {
total: 1000 + 750,
own: 1000,
others: vec![
IndividualExposure { who: 103, value: 250 },
IndividualExposure { who: 104, value: 250 },
IndividualExposure { who: 105, value: 250 },
]
}
),
// validator 3, part of other stake only
(
3,
Exposure::<AccountId, Balance> {
total: 250,
own: 0,
others: vec![IndividualExposure { who: 103, value: 250 },]
}
),
// validator 4, all of other stake only
(
4,
Exposure::<AccountId, Balance> {
total: 750,
own: 0,
others: vec![
IndividualExposure { who: 101, value: 250 },
IndividualExposure { who: 102, value: 250 },
IndividualExposure { who: 103, value: 250 }
]
}
),
// validator 5, nothing
];
// our exposures are stored for this era.
let current_era = current_era();
// insert page 1 of exposures
assert_eq!(
EraElectionPlanner::<T>::store_stakers_info(exposures_page1, current_era).to_vec(),
vec![1, 2, 3, 5]
);
// overviews after inserting first page of exposures.
assert_eq!(
ErasStakersOverview::<T>::get(current_era, &1).unwrap(),
PagedExposureMetadata { total: 1750, own: 1000, nominator_count: 3, page_count: 2 },
);
assert_eq!(
ErasStakersOverview::<T>::get(current_era, &2).unwrap(),
PagedExposureMetadata { total: 500, own: 0, nominator_count: 2, page_count: 1 },
);
assert_eq!(
ErasStakersOverview::<T>::get(current_era, &3).unwrap(),
PagedExposureMetadata { total: 750, own: 0, nominator_count: 3, page_count: 2 },
);
assert!(ErasStakersOverview::<T>::get(current_era, &4).is_none());
assert_eq!(
ErasStakersOverview::<T>::get(current_era, &5).unwrap(),
PagedExposureMetadata { total: 500, own: 0, nominator_count: 2, page_count: 1 },
);
// total stake after first page of exposures.
assert_eq!(ErasTotalStake::<T>::get(current_era), 3500);
// details after first page of exposures.
assert_eq!(exposure_pages(current_era, 1), vec![(0, (500, 2)), (1, (250, 1))]);
assert_eq!(exposure_pages(current_era, 2), vec![(0, (500, 2))]);
assert_eq!(exposure_pages(current_era, 3), vec![(0, (500, 2)), (1, (250, 1))]);
assert_eq!(exposure_pages(current_era, 4), vec![]);
assert_eq!(exposure_pages(current_era, 5), vec![(0, (500, 2))]);
// insert page 2 of exposures
assert_eq!(
EraElectionPlanner::<T>::store_stakers_info(exposure_page2, current_era).to_vec(),
vec![1, 2, 3, 4]
);
// overviews after inserting second page of exposures.
assert_eq!(
ErasStakersOverview::<T>::get(current_era, &1).unwrap(),
PagedExposureMetadata { total: 2250, own: 1000, nominator_count: 5, page_count: 3 },
);
assert_eq!(
ErasStakersOverview::<T>::get(current_era, &2).unwrap(),
PagedExposureMetadata { total: 2250, own: 1000, nominator_count: 5, page_count: 3 },
);
assert_eq!(
ErasStakersOverview::<T>::get(current_era, &3).unwrap(),
PagedExposureMetadata { total: 1000, own: 0, nominator_count: 4, page_count: 2 },
);
assert_eq!(
ErasStakersOverview::<T>::get(current_era, &4).unwrap(),
PagedExposureMetadata { total: 750, own: 0, nominator_count: 3, page_count: 2 },
);
assert_eq!(
ErasStakersOverview::<T>::get(current_era, &5).unwrap(),
PagedExposureMetadata { total: 500, own: 0, nominator_count: 2, page_count: 1 },
);
// total stake after second page of exposures.
assert_eq!(ErasTotalStake::<T>::get(current_era), 6750);
// details after second page of exposures.
assert_eq!(
exposure_pages(current_era, 1),
vec![(0, (500, 2)), (1, (500, 2)), (2, (250, 1))]
);
assert_eq!(
exposure_pages(current_era, 2),
vec![(0, (500, 2)), (1, (500, 2)), (2, (250, 1))]
);
assert_eq!(exposure_pages(current_era, 3), vec![(0, (500, 2)), (1, (500, 2))]);
assert_eq!(exposure_pages(current_era, 4), vec![(0, (500, 2)), (1, (250, 1))]);
assert_eq!(exposure_pages(current_era, 5), vec![(0, (500, 2))]);
})
}
}
mod electable_stashes {
use super::*;
#[test]
fn add_electable_stashes_work() {
ExtBuilder::default().try_state(false).build_and_execute(|| {
MaxValidatorSet::set(5);
assert_eq!(MaxValidatorSet::get(), 5);
assert!(ElectableStashes::<Test>::get().is_empty());
// adds stashes without duplicates, do not overflow bounds.
assert_ok!(EraElectionPlanner::<T>::add_electables(vec![1u64, 2, 3].into_iter()));
assert_eq!(
ElectableStashes::<Test>::get().into_inner().into_iter().collect::<Vec<_>>(),
vec![1, 2, 3]
);
// adds with duplicates which are deduplicated implicitly, no not overflow bounds.
assert_ok!(EraElectionPlanner::<T>::add_electables(vec![1u64, 2, 4].into_iter()));
assert_eq!(
ElectableStashes::<Test>::get().into_inner().into_iter().collect::<Vec<_>>(),
vec![1, 2, 3, 4]
);
})
}
#[test]
fn add_electable_stashes_overflow_works() {
ExtBuilder::default().try_state(false).build_and_execute(|| {
MaxValidatorSet::set(5);
assert_eq!(MaxValidatorSet::get(), 5);
assert!(ElectableStashes::<Test>::get().is_empty());
// adds stashes so that bounds are overflown, fails and internal state changes so that
// all slots are filled. error will return the idx of the first account that was not
// included.
let expected_idx_not_included = 5; // stash 6.
assert_eq!(
EraElectionPlanner::<T>::add_electables(
vec![1u64, 2, 3, 4, 5, 6, 7, 8].into_iter()
),
Err(expected_idx_not_included)
);
// the included were added to the electable stashes, despite the error.
assert_eq!(
ElectableStashes::<Test>::get().into_inner().into_iter().collect::<Vec<_>>(),
vec![1, 2, 3, 4, 5]
);
})
}
#[test]
fn overflow_electable_stashes_no_exposures_work() {
// ensures exposures are stored only for the electable stashes that fit within the
// electable stashes bounds in case of overflow.
ExtBuilder::default().try_state(false).build_and_execute(|| {
MaxValidatorSet::set(2);
assert!(ElectableStashes::<Test>::get().is_empty());
let supports = to_bounded_supports(vec![
(1, Support { total: 100, voters: vec![(10, 1_000)] }),
(2, Support { total: 200, voters: vec![(20, 2_000)] }),
(3, Support { total: 300, voters: vec![(30, 3_000)] }),
(4, Support { total: 400, voters: vec![(40, 4_000)] }),
]);
// error due to bounds.
let expected_not_included = 2;
assert_eq!(
EraElectionPlanner::<T>::do_elect_paged_inner(supports),
Err(expected_not_included)
);
// electable stashes have been collected to the max bounds despite the error.
assert_eq!(ElectableStashes::<Test>::get().into_iter().collect::<Vec<_>>(), vec![1, 2]);
let exposure_exists = |acc, era| Eras::<Test>::get_full_exposure(era, &acc).total != 0;
// exposures were only collected for electable stashes in bounds (1 and 2).
assert!(exposure_exists(1, 1));
assert!(exposure_exists(2, 1));
assert!(!exposure_exists(3, 1));
assert!(!exposure_exists(4, 1));
})
}
}
mod paged_on_initialize_era_election_planner {
use pezpallet_staking_async_rc_client::ValidatorSetReport;
use super::*;
#[test]
fn single_page_election_works() {
ExtBuilder::default()
// set desired targets to 3.
.validator_count(3)
.build_and_execute(|| {
// single page.
let pages: BlockNumber = EraElectionPlanner::<T>::election_pages().into();
assert_eq!(pages, 1);
// we will start the next election at the start of block 20
assert_eq!(System::block_number(), 15);
assert_eq!(PlanningEraOffset::get(), 2);
// genesis validators are now in place.
assert_eq!(current_era(), 1);
assert_eq_uvec!(Session::validators(), vec![11, 21, 31]);
// force unstake of 31 to ensure the election results of the next era are
// different than genesis.
assert_ok!(Staking::force_unstake(RuntimeOrigin::root(), 31, 0));
// use all registered validators as potential targets.
let expected_elected = vec![11, 21];
ValidatorCount::<Test>::set(expected_elected.len() as u32);
// 1. start signal is sent, election result will come next block.
Session::roll_until(20);
assert_eq!(NextElectionPage::<Test>::get(), None);
assert!(ElectableStashes::<Test>::get().is_empty());
assert_eq!(VoterSnapshotStatus::<Test>::get(), SnapshotStatus::Waiting);
// 2. starts preparing election at the (election_prediction - n_pages) block.
Session::roll_next();
// electing started, but since single-page, we don't set `NextElectionPage` at all.
assert_eq!(NextElectionPage::<Test>::get(), None);
assert!(ElectableStashes::<Test>::get().is_empty());
// Electable stashes are already drained and sent to RC client.
assert_eq!(
ReceivedValidatorSets::get_last(),
ValidatorSetReport {
id: 2,
leftover: false,
new_validator_set: vec![11, 21],
prune_up_to: None
}
);
assert_eq!(VoterSnapshotStatus::<Test>::get(), SnapshotStatus::Waiting);
assert_eq!(current_era(), 2);
assert_eq!(active_era(), 1);
// check old exposures
assert_eq_uvec!(
era_exposures(1),
vec![
(
11,
Exposure {
total: 1250,
own: 1000 as Balance,
others: vec![IndividualExposure { who: 101, value: 250 }]
}
),
(
21,
Exposure {
total: 1250,
own: 1000 as Balance,
others: vec![IndividualExposure { who: 101, value: 250 }]
}
),
(31, Exposure { total: 500, own: 500 as Balance, others: vec![] }),
]
);
// check new exposures
assert_eq_uvec!(
era_exposures(2),
vec![
(
11,
Exposure {
total: 1250,
own: 1000 as Balance,
others: vec![IndividualExposure { who: 101, value: 250 }]
}
),
(
21,
Exposure {
total: 1250,
own: 1000 as Balance,
others: vec![IndividualExposure { who: 101, value: 250 }]
}
),
]
);
// era progressed and electable stashes have been served to session pallet.
assert_eq_uvec!(Session::validators(), vec![11, 21, 31]);
// 4. in the next era, the validator set does not include 31 anymore which was
// unstaked.
Session::roll_until_active_era(2);
assert_eq_uvec!(Session::validators(), vec![11, 21]);
})
}
#[test]
fn multi_page_election_works() {
ExtBuilder::default()
.add_staker(61, 1000, StakerStatus::Validator)
.add_staker(71, 1000, StakerStatus::Validator)
.add_staker(81, 1000, StakerStatus::Validator)
.add_staker(91, 1000, StakerStatus::Validator)
.multi_page_election_provider(3)
.validator_count(6)
.election_bounds(3, 10)
.build_and_execute(|| {
type A = AccountId;
type B = Balance;
// NOTE: we cannot really enforce MaxBackersPerWinner and ValidatorCount here as our
// election provider in the mock is rather dumb and cannot respect them atm.
// we will start the next election at the start of block 20
assert_eq!(System::block_number(), 15);
assert_eq!(PlanningEraOffset::get(), 2);
// 1. election signal is sent here,
Session::roll_until(20);
assert_eq!(
staking_events_since_last_call(),
vec![Event::SessionRotated {
starting_session: 4,
active_era: 1,
planned_era: 2
}]
);
assert_eq!(NextElectionPage::<Test>::get(), None);
assert_eq!(VoterSnapshotStatus::<Test>::get(), SnapshotStatus::Waiting);
assert!(ElectableStashes::<Test>::get().is_empty());
// page 2 fetched, next is 1
Session::roll_until(21);
assert_eq!(NextElectionPage::<Test>::get(), Some(1));
assert_eq!(VoterSnapshotStatus::<Test>::get(), SnapshotStatus::Ongoing(31));
assert_eq!(
ElectableStashes::<Test>::get().into_iter().collect::<Vec<AccountId>>(),
vec![11, 21, 31]
);
assert_eq_uvec!(
era_exposures(2),
vec![
(11, Exposure::<A, B> { total: 1000, own: 1000, others: vec![] }),
(21, Exposure::<A, B> { total: 1000, own: 1000, others: vec![] }),
(31, Exposure::<A, B> { total: 500, own: 500, others: vec![] }),
]
);
// page 1, next is 0
Session::roll_until(22);
// the electable stashes remain the same.
assert_eq_uvec!(
ElectableStashes::<Test>::get().into_iter().collect::<Vec<_>>(),
vec![11, 21, 31, 61, 71, 81]
);
assert_eq!(NextElectionPage::<Test>::get(), Some(0));
assert_eq!(VoterSnapshotStatus::<Test>::get(), SnapshotStatus::Ongoing(81));
assert_eq_uvec!(
era_exposures(2),
vec![
(31, Exposure::<A, B> { total: 500, own: 500, others: vec![] }),
(21, Exposure::<A, B> { total: 1000, own: 1000, others: vec![] }),
(81, Exposure::<A, B> { total: 1000, own: 1000, others: vec![] }),
(71, Exposure::<A, B> { total: 1000, own: 1000, others: vec![] }),
(11, Exposure::<A, B> { total: 1000, own: 1000, others: vec![] }),
(61, Exposure::<A, B> { total: 1000, own: 1000, others: vec![] })
]
);
// fetch 0, done.
Session::roll_until(23);
// the electable stashes are now empty
assert!(ElectableStashes::<Test>::get().is_empty());
assert_eq!(VoterSnapshotStatus::<Test>::get(), SnapshotStatus::Waiting);
assert_eq!(NextElectionPage::<Test>::get(), None);
// check exposures
assert_eq_uvec!(
era_exposures(2),
vec![
(31, Exposure::<A, B> { total: 500, own: 500, others: vec![] }),
(
21,
Exposure::<A, B> {
total: 1250,
own: 1000,
others: vec![IndividualExposure { who: 101, value: 250 }]
}
),
(81, Exposure::<A, B> { total: 1000, own: 1000, others: vec![] }),
(71, Exposure::<A, B> { total: 1000, own: 1000, others: vec![] }),
(91, Exposure::<A, B> { total: 1000, own: 1000, others: vec![] }),
(
11,
Exposure::<A, B> {
total: 1250,
own: 1000,
others: vec![IndividualExposure { who: 101, value: 250 }]
}
),
(61, Exposure::<A, B> { total: 1000, own: 1000, others: vec![] })
]
);
// and are sent
assert_eq!(
ReceivedValidatorSets::get_last(),
ValidatorSetReport {
id: 2,
leftover: false,
new_validator_set: vec![11, 21, 31, 61, 71, 81, 91],
prune_up_to: None
}
);
assert_eq!(NextElectionPage::<Test>::get(), None);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::PagedElectionProceeded { page: 2, result: Ok(3) },
Event::PagedElectionProceeded { page: 1, result: Ok(3) },
Event::PagedElectionProceeded { page: 0, result: Ok(1) }
]
);
// go to activation of this validator set.
Session::roll_until_active_era(2);
// the new era validators are the expected elected stashes.
assert_eq_uvec!(Session::validators(), vec![11, 21, 31, 61, 71, 81, 91]);
})
}
// #[test]
// fn multi_page_exposure_and_multi_page_elect() {
// todo!("an election with 3 pages, with 4 backers per exposures, which are stored in
// MaxExposurePageSize = 6, ergo transformed into 2 pages of final exposure") }
// #[test]
// fn multi_page_election_with_mulit_page_exposures_rewards_work() {
// ExtBuilder::default()
// .add_staker(61, 61, 1000, StakerStatus::Validator)
// .add_staker(71, 71, 1000, StakerStatus::Validator)
// .add_staker(1, 1, 5, StakerStatus::Nominator(vec![21, 31, 71]))
// .add_staker(2, 2, 5, StakerStatus::Nominator(vec![21, 31, 71]))
// .add_staker(3, 3, 5, StakerStatus::Nominator(vec![21, 31, 71]))
// .multi_page_election_provider(3)
// .max_winners_per_page(3)
// .exposures_page_size(2)
// .build_and_execute(|| {
// // election provider has 3 pages.
// let pages: BlockNumber =
// <<Test as Config>::ElectionProvider as ElectionProvider>::Pages::get().into();
// assert_eq!(pages, 3);
// // 3 max winners per page.
// let max_winners_page = <<Test as Config>::ElectionProvider as
// ElectionProvider>::MaxWinnersPerPage::get(); assert_eq!(max_winners_page, 3);
// // setup validator payee prefs and 10% commission.
// for s in vec![21, 31, 71] {
// Payee::<Test>::insert(s, RewardDestination::Account(s));
// let prefs = ValidatorPrefs { commission: Perbill::from_percent(10),
// ..Default::default() }; Validators::<Test>::insert(s, prefs.clone());
// }
// let init_balance_all = vec![21, 31, 71, 1, 2, 3].iter().fold(0, |mut acc, s| {
// acc += asset::total_balance::<Test>(&s);
// acc
// });
// // progress era.
// assert_eq!(current_era(), 0);
// start_active_era(1);
// assert_eq!(current_era(), 1);
// assert_eq!(Session::validators(), vec![21, 31, 71]);
// // distribute reward,
// Pallet::<Test>::reward_by_ids(vec![(21, 50)]);
// Pallet::<Test>::reward_by_ids(vec![(31, 50)]);
// Pallet::<Test>::reward_by_ids(vec![(71, 50)]);
// let total_payout = validator_payout_for(time_per_era());
// start_active_era(2);
// // all the validators exposed in era 1 have two pages of exposures, since
// exposure // page size is 2.
// assert_eq!(MaxExposurePageSize::get(), 2);
// assert_eq!(Eras::<Test>::exposure_page_count(1, &21), 2);
// assert_eq!(Eras::<Test>::exposure_page_count(1, &31), 2);
// assert_eq!(Eras::<Test>::exposure_page_count(1, &71), 2);
// make_all_reward_payment(1);
// let balance_all = vec![21, 31, 71, 1, 2, 3].iter().fold(0, |mut acc, s| {
// acc += asset::total_balance::<Test>(&s);
// acc
// });
// assert_eq_error_rate!(
// total_payout,
// balance_all - init_balance_all,
// 4
// );
// })
// }
// #[test]
// fn multi_page_election_is_graceful() {
// // demonstrate that in a multi-page election, in some of the `elect(_)` calls fail we won't
// // bail right away.
// ExtBuilder::default().multi_page_election_provider(3).build_and_execute(|| {
// // load some exact data into the election provider, some of which are error or empty.
// let correct_results = <Test as Config>::GenesisElectionProvider::elect(0);
// CustomElectionSupports::set(Some(vec![
// // page 0.
// correct_results.clone(),
// // page 1.
// Err(onchain::Error::FailedToBound),
// // page 2.
// Ok(Default::default()),
// ]));
// // genesis era.
// assert_eq!(current_era(), 0);
// let next_election =
// <Staking as ElectionDataProvider>::next_election_prediction(System::block_number());
// assert_eq!(next_election, 10);
// // try-state sanity check.
// assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number()));
// // 1. election prep hasn't started yet, election cursor and electable stashes are
// // not set yet.
// roll_to_block(6);
// assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number()));
// assert_eq!(NextElectionPage::<Test>::get(), None);
// assert!(ElectableStashes::<Test>::get().is_empty());
// // 2. starts preparing election at the (election_prediction - n_pages) block.
// // fetches lsp (i.e. 2).
// roll_to_block(7);
// assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number()));
// // electing started at cursor is set once the election starts to be prepared.
// assert_eq!(NextElectionPage::<Test>::get(), Some(1));
// // in elect(2) we won't collect any stashes yet.
// assert!(ElectableStashes::<Test>::get().is_empty());
// // 3. progress one block to fetch page 1.
// roll_to_block(8);
// assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number()));
// // in elect(1) we won't collect any stashes yet.
// assert!(ElectableStashes::<Test>::get().is_empty());
// // election cursor is updated
// assert_eq!(NextElectionPage::<Test>::get(), Some(0));
// // 4. progress one block to fetch mps (i.e. 0).
// roll_to_block(9);
// assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number()));
// // some stashes come in.
// assert_eq!(
// ElectableStashes::<Test>::get().into_iter().collect::<Vec<_>>(),
// vec![11 as AccountId, 21]
// );
// // cursor is now none
// assert_eq!(NextElectionPage::<Test>::get(), None);
// // events thus far
// assert_eq!(
// staking_events_since_last_call(),
// vec![
// Event::PagedElectionProceeded { page: 2, result: Ok(0) },
// Event::PagedElectionProceeded { page: 1, result: Err(0) },
// Event::PagedElectionProceeded { page: 0, result: Ok(2) }
// ]
// );
// // upon fetching page 0, the electing started will remain in storage until the
// // era rotates.
// assert_eq!(current_era(), 0);
// // Next block the era will rotate.
// roll_to_block(10);
// assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number()));
// // and all the metadata has been cleared up and ready for the next election.
// assert!(NextElectionPage::<Test>::get().is_none());
// assert!(ElectableStashes::<Test>::get().is_empty());
// // and the overall staking worked fine.
// assert_eq!(staking_events_since_last_call(), vec![Event::StakersElected]);
// })
// }
// #[test]
// fn multi_page_election_fails_if_not_enough_validators() {
// // a graceful multi-page election still fails if not enough validators are provided.
// ExtBuilder::default().multi_page_election_provider(3).build_and_execute(|| {
// // load some exact data into the election provider, some of which are error or
// // empty.
// let correct_results = <Test as Config>::GenesisElectionProvider::elect(0);
// CustomElectionSupports::set(Some(vec![
// // page 0.
// correct_results.clone(),
// // page 1.
// Err(onchain::Error::FailedToBound),
// // page 2.
// Ok(Default::default()),
// ]));
// // genesis era.
// assert_eq!(current_era(), 0);
// let next_election =
// <Staking as ElectionDataProvider>::next_election_prediction(System::block_number());
// assert_eq!(next_election, 10);
// // try-state sanity check.
// assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number()));
// // 1. election prep hasn't started yet, election cursor and electable stashes are
// // not set yet.
// roll_to_block(6);
// assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number()));
// assert_eq!(NextElectionPage::<Test>::get(), None);
// assert!(ElectableStashes::<Test>::get().is_empty());
// // 2. starts preparing election at the (election_prediction - n_pages) block.
// // fetches lsp (i.e. 2).
// roll_to_block(7);
// assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number()));
// // electing started at cursor is set once the election starts to be prepared.
// assert_eq!(NextElectionPage::<Test>::get(), Some(1));
// // in elect(2) we won't collect any stashes yet.
// assert!(ElectableStashes::<Test>::get().is_empty());
// // 3. progress one block to fetch page 1.
// roll_to_block(8);
// assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number()));
// // in elect(1) we won't collect any stashes yet.
// assert!(ElectableStashes::<Test>::get().is_empty());
// // election cursor is updated
// assert_eq!(NextElectionPage::<Test>::get(), Some(0));
// // 4. progress one block to fetch mps (i.e. 0).
// roll_to_block(9);
// assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number()));
// // some stashes come in.
// assert_eq!(
// ElectableStashes::<Test>::get().into_iter().collect::<Vec<_>>(),
// vec![11 as AccountId, 21]
// );
// // cursor is now none
// assert_eq!(NextElectionPage::<Test>::get(), None);
// // events thus far
// assert_eq!(
// staking_events_since_last_call(),
// vec![
// Event::PagedElectionProceeded { page: 2, result: Ok(0) },
// Event::PagedElectionProceeded { page: 1, result: Err(0) },
// Event::PagedElectionProceeded { page: 0, result: Ok(2) }
// ]
// );
// // upon fetching page 0, the electing started will remain in storage until the
// // era rotates.
// assert_eq!(current_era(), 0);
// // Next block the era will rotate.
// roll_to_block(10);
// assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number()));
// // and all the metadata has been cleared up and ready for the next election.
// assert!(NextElectionPage::<Test>::get().is_none());
// assert!(ElectableStashes::<Test>::get().is_empty());
// // and the overall staking worked fine.
// assert_eq!(staking_events_since_last_call(), vec![Event::StakingElectionFailed]);
// })
// }
}