diff --git a/src/lib.rs b/src/lib.rs index 0e0ef6b..317bc45 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -321,9 +321,6 @@ pub mod pallet { /// Desired number of candidates. /// /// This should always be less than [`Config::MaxCandidates`] for weights to be correct. - /// - /// IMP: This must be less than the session length, - /// because rewards are distributed for one collator per block. #[pallet::storage] pub type DesiredCandidates = StorageValue<_, u32, ValueQuery>; @@ -692,6 +689,12 @@ pub mod pallet { T::UpdateOrigin::ensure_origin(origin)?; ensure!(max <= T::MaxCandidates::get(), Error::::TooManyDesiredCandidates); + let invulnerables = Invulnerables::::get(); + ensure!( + max.saturating_add(invulnerables.len() as u32) >= T::MinEligibleCollators::get(), + Error::::TooFewEligibleCollators + ); + DesiredCandidates::::set(max); Self::deposit_event(Event::NewDesiredCandidates { desired_candidates: max }); Ok(()) diff --git a/src/tests.rs b/src/tests.rs index 18a6986..ca87206 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -232,7 +232,7 @@ mod set_desired_candidates { CollatorStaking::set_desired_candidates(RuntimeOrigin::signed(1), 2), BadOrigin ); - // rejects bad origin + // rejects too many assert_noop!( CollatorStaking::set_desired_candidates( RuntimeOrigin::signed(RootAccount::get()), @@ -242,6 +242,29 @@ mod set_desired_candidates { ); }); } + + #[test] + fn cannot_set_desired_candidates_if_under_min_collator_limit() { + new_test_ext().execute_with(|| { + initialize_to_block(1); + // given + assert_eq!(DesiredCandidates::::get(), 2); + assert_eq!(::MinEligibleCollators::get(), 1); + register_candidates(3..=3); + + assert_ok!(CollatorStaking::set_invulnerables( + RuntimeOrigin::signed(RootAccount::get()), + vec![] + )); + assert_noop!( + CollatorStaking::set_desired_candidates( + RuntimeOrigin::signed(RootAccount::get()), + 0 + ), + Error::::TooFewEligibleCollators + ); + }); + } } mod add_invulnerable {