// This file is part of Bizinikiwi. // Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute // 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 bizinikiwi_test_utils::assert_eq_uvec; use pezframe_support::assert_ok; use pezsp_npos_elections::Support; 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::::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::::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> = 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 = Exposure { total: total_stake, own: own_stake, others }; // when let (exposure_metadata, exposure_page): ( PagedExposureMetadata, Vec>, ) = 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::::iter_prefix((era, who)) .map(|(page, expo)| (page, (expo.page_total, expo.others.len() as u32))) .collect::>(); 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::::planned_era(), 2); assert_eq!(Rotator::::active_era(), 1); assert_eq!(ErasTotalStake::::get(Rotator::::planned_era()), 0); assert_eq!( ErasStakersOverview::::iter_prefix(Rotator::::planned_era()).count(), 0 ); assert_eq!( ErasStakersPaged::::iter_prefix((Rotator::::planned_era(),)).count(), 0 ); // stuff that goes into first page let exposures_page1 = bounded_vec![ // validator 1, own-stake and other stake ( 1, Exposure:: { total: 1000 + 250, own: 1000, others: vec![IndividualExposure { who: 101, value: 250 }] } ), // validator 2, other stake only ( 2, Exposure:: { total: 250, own: 0, others: vec![IndividualExposure { who: 101, value: 250 }] } ), // validator 3, part of other stake only ( 3, Exposure:: { total: 250, own: 0, others: vec![IndividualExposure { who: 101, value: 250 }] } ), // validator 4, nothing // validator 5, part of other stake only ( 5, Exposure:: { total: 250, own: 0, others: vec![IndividualExposure { who: 101, value: 250 }] } ), ]; // stuff that goes into second page let exposure_page2: BoundedExposuresOf = bounded_vec![ // validator 1, other stake only ( 1, Exposure:: { total: 250, own: 0, others: vec![IndividualExposure { who: 101, value: 250 }] } ), // validator 2, own-stake and other stake ( 2, Exposure:: { total: 1000 + 250, own: 1000, others: vec![IndividualExposure { who: 101, value: 250 }] } ), // validator 3, part of other stake only ( 3, Exposure:: { total: 250, own: 0, others: vec![IndividualExposure { who: 101, value: 250 }] } ), // validator 4, part of other stake only ( 4, Exposure:: { 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::::get(current_era), 0); // insert page 1 of exposures assert_eq!( EraElectionPlanner::::store_stakers_info(exposures_page1, current_era).to_vec(), vec![1, 2, 3, 5] ); // overviews after inserting first page of exposures. assert_eq!( ErasStakersOverview::::get(current_era, &1).unwrap(), PagedExposureMetadata { total: 1250, own: 1000, nominator_count: 1, page_count: 1 }, ); assert_eq!( ErasStakersOverview::::get(current_era, &2).unwrap(), PagedExposureMetadata { total: 250, own: 0, nominator_count: 1, page_count: 1 }, ); assert_eq!( ErasStakersOverview::::get(current_era, &3).unwrap(), PagedExposureMetadata { total: 250, own: 0, nominator_count: 1, page_count: 1 }, ); assert!(ErasStakersOverview::::get(current_era, &4).is_none()); assert_eq!( ErasStakersOverview::::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::::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::::store_stakers_info(exposure_page2, current_era).to_vec(), vec![1, 2, 3, 4] ); // overviews after inserting second page of exposures. assert_eq!( ErasStakersOverview::::get(current_era, &1).unwrap(), PagedExposureMetadata { total: 1500, own: 1000, nominator_count: 2, page_count: 2 }, ); assert_eq!( ErasStakersOverview::::get(current_era, &2).unwrap(), PagedExposureMetadata { total: 1500, own: 1000, nominator_count: 2, page_count: 2 }, ); assert_eq!( ErasStakersOverview::::get(current_era, &3).unwrap(), PagedExposureMetadata { total: 500, own: 0, nominator_count: 2, page_count: 2 }, ); assert_eq!( ErasStakersOverview::::get(current_era, &4).unwrap(), PagedExposureMetadata { total: 250, own: 0, nominator_count: 1, page_count: 1 }, ); assert_eq!( ErasStakersOverview::::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::::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::::planned_era(), 2); assert_eq!(Rotator::::active_era(), 1); assert_eq!(ErasTotalStake::::get(Rotator::::planned_era()), 0); assert_eq!( ErasStakersOverview::::iter_prefix(Rotator::::planned_era()).count(), 0 ); assert_eq!( ErasStakersPaged::::iter_prefix((Rotator::::planned_era(),)).count(), 0 ); // stuff that goes into first page let exposures_page1 = bounded_vec![ // validator 1, own-stake and other stake ( 1, Exposure:: { 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:: { 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:: { 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:: { total: 500, own: 0, others: vec![ IndividualExposure { who: 101, value: 250 }, IndividualExposure { who: 102, value: 250 }, ] } ), ]; let exposure_page2: BoundedExposuresOf = bounded_vec![ // validator 1, other stake only ( 1, Exposure:: { 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:: { 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:: { total: 250, own: 0, others: vec![IndividualExposure { who: 103, value: 250 },] } ), // validator 4, all of other stake only ( 4, Exposure:: { 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::::store_stakers_info(exposures_page1, current_era).to_vec(), vec![1, 2, 3, 5] ); // overviews after inserting first page of exposures. assert_eq!( ErasStakersOverview::::get(current_era, &1).unwrap(), PagedExposureMetadata { total: 1750, own: 1000, nominator_count: 3, page_count: 2 }, ); assert_eq!( ErasStakersOverview::::get(current_era, &2).unwrap(), PagedExposureMetadata { total: 500, own: 0, nominator_count: 2, page_count: 1 }, ); assert_eq!( ErasStakersOverview::::get(current_era, &3).unwrap(), PagedExposureMetadata { total: 750, own: 0, nominator_count: 3, page_count: 2 }, ); assert!(ErasStakersOverview::::get(current_era, &4).is_none()); assert_eq!( ErasStakersOverview::::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::::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::::store_stakers_info(exposure_page2, current_era).to_vec(), vec![1, 2, 3, 4] ); // overviews after inserting second page of exposures. assert_eq!( ErasStakersOverview::::get(current_era, &1).unwrap(), PagedExposureMetadata { total: 2250, own: 1000, nominator_count: 5, page_count: 3 }, ); assert_eq!( ErasStakersOverview::::get(current_era, &2).unwrap(), PagedExposureMetadata { total: 2250, own: 1000, nominator_count: 5, page_count: 3 }, ); assert_eq!( ErasStakersOverview::::get(current_era, &3).unwrap(), PagedExposureMetadata { total: 1000, own: 0, nominator_count: 4, page_count: 2 }, ); assert_eq!( ErasStakersOverview::::get(current_era, &4).unwrap(), PagedExposureMetadata { total: 750, own: 0, nominator_count: 3, page_count: 2 }, ); assert_eq!( ErasStakersOverview::::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::::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::::get().is_empty()); // adds stashes without duplicates, do not overflow bounds. assert_ok!(EraElectionPlanner::::add_electables(vec![1u64, 2, 3].into_iter())); assert_eq!( ElectableStashes::::get().into_inner().into_iter().collect::>(), vec![1, 2, 3] ); // adds with duplicates which are deduplicated implicitly, no not overflow bounds. assert_ok!(EraElectionPlanner::::add_electables(vec![1u64, 2, 4].into_iter())); assert_eq!( ElectableStashes::::get().into_inner().into_iter().collect::>(), 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::::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::::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::::get().into_inner().into_iter().collect::>(), 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::::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::::do_elect_paged_inner(supports), Err(expected_not_included) ); // electable stashes have been collected to the max bounds despite the error. assert_eq!(ElectableStashes::::get().into_iter().collect::>(), vec![1, 2]); let exposure_exists = |acc, era| Eras::::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::::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::::set(expected_elected.len() as u32); // 1. start signal is sent, election result will come next block. Session::roll_until(20); assert_eq!(NextElectionPage::::get(), None); assert!(ElectableStashes::::get().is_empty()); assert_eq!(VoterSnapshotStatus::::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::::get(), None); assert!(ElectableStashes::::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::::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 pezpallet. 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::::get(), None); assert_eq!(VoterSnapshotStatus::::get(), SnapshotStatus::Waiting); assert!(ElectableStashes::::get().is_empty()); // page 2 fetched, next is 1 Session::roll_until(21); assert_eq!(NextElectionPage::::get(), Some(1)); assert_eq!(VoterSnapshotStatus::::get(), SnapshotStatus::Ongoing(31)); assert_eq!( ElectableStashes::::get().into_iter().collect::>(), vec![11, 21, 31] ); assert_eq_uvec!( era_exposures(2), vec![ (11, Exposure:: { total: 1000, own: 1000, others: vec![] }), (21, Exposure:: { total: 1000, own: 1000, others: vec![] }), (31, Exposure:: { 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::::get().into_iter().collect::>(), vec![11, 21, 31, 61, 71, 81] ); assert_eq!(NextElectionPage::::get(), Some(0)); assert_eq!(VoterSnapshotStatus::::get(), SnapshotStatus::Ongoing(81)); assert_eq_uvec!( era_exposures(2), vec![ (31, Exposure:: { total: 500, own: 500, others: vec![] }), (21, Exposure:: { total: 1000, own: 1000, others: vec![] }), (81, Exposure:: { total: 1000, own: 1000, others: vec![] }), (71, Exposure:: { total: 1000, own: 1000, others: vec![] }), (11, Exposure:: { total: 1000, own: 1000, others: vec![] }), (61, Exposure:: { total: 1000, own: 1000, others: vec![] }) ] ); // fetch 0, done. Session::roll_until(23); // the electable stashes are now empty assert!(ElectableStashes::::get().is_empty()); assert_eq!(VoterSnapshotStatus::::get(), SnapshotStatus::Waiting); assert_eq!(NextElectionPage::::get(), None); // check exposures assert_eq_uvec!( era_exposures(2), vec![ (31, Exposure:: { total: 500, own: 500, others: vec![] }), ( 21, Exposure:: { total: 1250, own: 1000, others: vec![IndividualExposure { who: 101, value: 250 }] } ), (81, Exposure:: { total: 1000, own: 1000, others: vec![] }), (71, Exposure:: { total: 1000, own: 1000, others: vec![] }), (91, Exposure:: { total: 1000, own: 1000, others: vec![] }), ( 11, Exposure:: { total: 1250, own: 1000, others: vec![IndividualExposure { who: 101, value: 250 }] } ), (61, Exposure:: { 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::::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 = // <::ElectionProvider as ElectionProvider>::Pages::get().into(); // assert_eq!(pages, 3); // // 3 max winners per page. // let max_winners_page = <::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::::insert(s, RewardDestination::Account(s)); // let prefs = ValidatorPrefs { commission: Perbill::from_percent(10), // ..Default::default() }; Validators::::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::(&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, // Pezpallet::::reward_by_ids(vec![(21, 50)]); // Pezpallet::::reward_by_ids(vec![(31, 50)]); // Pezpallet::::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::::exposure_page_count(1, &21), 2); // assert_eq!(Eras::::exposure_page_count(1, &31), 2); // assert_eq!(Eras::::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::(&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 = ::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 = // ::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::::get(), None); // assert!(ElectableStashes::::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::::get(), Some(1)); // // in elect(2) we won't collect any stashes yet. // assert!(ElectableStashes::::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::::get().is_empty()); // // election cursor is updated // assert_eq!(NextElectionPage::::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::::get().into_iter().collect::>(), // vec![11 as AccountId, 21] // ); // // cursor is now none // assert_eq!(NextElectionPage::::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::::get().is_none()); // assert!(ElectableStashes::::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 = ::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 = // ::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::::get(), None); // assert!(ElectableStashes::::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::::get(), Some(1)); // // in elect(2) we won't collect any stashes yet. // assert!(ElectableStashes::::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::::get().is_empty()); // // election cursor is updated // assert_eq!(NextElectionPage::::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::::get().into_iter().collect::>(), // vec![11 as AccountId, 21] // ); // // cursor is now none // assert_eq!(NextElectionPage::::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::::get().is_none()); // assert!(ElectableStashes::::get().is_empty()); // // and the overall staking worked fine. // assert_eq!(staking_events_since_last_call(), vec![Event::StakingElectionFailed]); // }) // } }