1#![cfg_attr(not(feature = "std"), no_std)]
83
84extern crate alloc;
85
86use core::marker::PhantomData;
87use pezframe_support::traits::TypedGet;
88pub use pezpallet::*;
89
90#[cfg(test)]
91mod mock;
92
93#[cfg(test)]
94mod tests;
95
96#[cfg(feature = "runtime-benchmarks")]
97mod benchmarking;
98pub mod migration;
99pub mod weights;
100
101const LOG_TARGET: &str = "runtime::collator-selection";
102
103#[pezframe_support::pezpallet]
104pub mod pezpallet {
105 pub use crate::weights::WeightInfo;
106 use alloc::vec::Vec;
107 use core::ops::Div;
108 use pezframe_support::{
109 dispatch::{DispatchClass, DispatchResultWithPostInfo},
110 pezpallet_prelude::*,
111 traits::{
112 Currency, EnsureOrigin, ExistenceRequirement::KeepAlive, ReservableCurrency,
113 ValidatorRegistration,
114 },
115 BoundedVec, DefaultNoBound, PalletId,
116 };
117 use pezframe_system::{pezpallet_prelude::*, Config as SystemConfig};
118 use pezpallet_session::SessionManager;
119 use pezsp_runtime::{
120 traits::{AccountIdConversion, CheckedSub, Convert, Saturating, Zero},
121 RuntimeDebug,
122 };
123 use pezsp_staking::SessionIndex;
124
125 const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
127
128 type BalanceOf<T> =
129 <<T as Config>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
130
131 pub struct IdentityCollator;
134 impl<T> pezsp_runtime::traits::Convert<T, Option<T>> for IdentityCollator {
135 fn convert(t: T) -> Option<T> {
136 Some(t)
137 }
138 }
139
140 #[pezpallet::config]
142 pub trait Config: pezframe_system::Config {
143 #[allow(deprecated)]
145 type RuntimeEvent: From<Event<Self>>
146 + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
147
148 type Currency: ReservableCurrency<Self::AccountId>;
150
151 type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
153
154 #[pezpallet::constant]
156 type PotId: Get<PalletId>;
157
158 #[pezpallet::constant]
162 type MaxCandidates: Get<u32>;
163
164 #[pezpallet::constant]
168 type MinEligibleCollators: Get<u32>;
169
170 #[pezpallet::constant]
172 type MaxInvulnerables: Get<u32>;
173
174 #[pezpallet::constant]
176 type KickThreshold: Get<BlockNumberFor<Self>>;
177
178 type ValidatorId: Member + Parameter;
180
181 type ValidatorIdOf: Convert<Self::AccountId, Option<Self::ValidatorId>>;
185
186 type ValidatorRegistration: ValidatorRegistration<Self::ValidatorId>;
188
189 type WeightInfo: WeightInfo;
191 }
192
193 #[pezpallet::extra_constants]
194 impl<T: Config> Pezpallet<T> {
195 fn pot_account() -> T::AccountId {
197 Self::account_id()
198 }
199 }
200
201 #[derive(
203 PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen,
204 )]
205 pub struct CandidateInfo<AccountId, Balance> {
206 pub who: AccountId,
208 pub deposit: Balance,
210 }
211
212 #[pezpallet::pezpallet]
213 #[pezpallet::storage_version(STORAGE_VERSION)]
214 pub struct Pezpallet<T>(_);
215
216 #[pezpallet::storage]
218 pub type Invulnerables<T: Config> =
219 StorageValue<_, BoundedVec<T::AccountId, T::MaxInvulnerables>, ValueQuery>;
220
221 #[pezpallet::storage]
227 pub type CandidateList<T: Config> = StorageValue<
228 _,
229 BoundedVec<CandidateInfo<T::AccountId, BalanceOf<T>>, T::MaxCandidates>,
230 ValueQuery,
231 >;
232
233 #[pezpallet::storage]
235 pub type LastAuthoredBlock<T: Config> =
236 StorageMap<_, Twox64Concat, T::AccountId, BlockNumberFor<T>, ValueQuery>;
237
238 #[pezpallet::storage]
242 pub type DesiredCandidates<T> = StorageValue<_, u32, ValueQuery>;
243
244 #[pezpallet::storage]
248 pub type CandidacyBond<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
249
250 #[pezpallet::genesis_config]
251 #[derive(DefaultNoBound)]
252 pub struct GenesisConfig<T: Config> {
253 pub invulnerables: Vec<T::AccountId>,
254 pub candidacy_bond: BalanceOf<T>,
255 pub desired_candidates: u32,
256 }
257
258 #[pezpallet::genesis_build]
259 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
260 fn build(&self) {
261 let duplicate_invulnerables = self
262 .invulnerables
263 .iter()
264 .collect::<alloc::collections::btree_set::BTreeSet<_>>();
265 assert!(
266 duplicate_invulnerables.len() == self.invulnerables.len(),
267 "duplicate invulnerables in genesis."
268 );
269
270 let mut bounded_invulnerables =
271 BoundedVec::<_, T::MaxInvulnerables>::try_from(self.invulnerables.clone())
272 .expect("genesis invulnerables are more than T::MaxInvulnerables");
273 assert!(
274 T::MaxCandidates::get() >= self.desired_candidates,
275 "genesis desired_candidates are more than T::MaxCandidates",
276 );
277
278 bounded_invulnerables.sort();
279
280 DesiredCandidates::<T>::put(self.desired_candidates);
281 CandidacyBond::<T>::put(self.candidacy_bond);
282 Invulnerables::<T>::put(bounded_invulnerables);
283 }
284 }
285
286 #[pezpallet::event]
287 #[pezpallet::generate_deposit(pub(super) fn deposit_event)]
288 pub enum Event<T: Config> {
289 NewInvulnerables { invulnerables: Vec<T::AccountId> },
291 InvulnerableAdded { account_id: T::AccountId },
293 InvulnerableRemoved { account_id: T::AccountId },
295 NewDesiredCandidates { desired_candidates: u32 },
297 NewCandidacyBond { bond_amount: BalanceOf<T> },
299 CandidateAdded { account_id: T::AccountId, deposit: BalanceOf<T> },
301 CandidateBondUpdated { account_id: T::AccountId, deposit: BalanceOf<T> },
303 CandidateRemoved { account_id: T::AccountId },
305 CandidateReplaced { old: T::AccountId, new: T::AccountId, deposit: BalanceOf<T> },
307 InvalidInvulnerableSkipped { account_id: T::AccountId },
310 }
311
312 #[pezpallet::error]
313 pub enum Error<T> {
314 TooManyCandidates,
316 TooFewEligibleCollators,
318 AlreadyCandidate,
320 NotCandidate,
322 TooManyInvulnerables,
324 AlreadyInvulnerable,
326 NotInvulnerable,
328 NoAssociatedValidatorId,
330 ValidatorNotRegistered,
332 InsertToCandidateListFailed,
334 RemoveFromCandidateListFailed,
336 DepositTooLow,
338 UpdateCandidateListFailed,
340 InsufficientBond,
342 TargetIsNotCandidate,
344 IdenticalDeposit,
346 InvalidUnreserve,
348 }
349
350 #[pezpallet::hooks]
351 impl<T: Config> Hooks<BlockNumberFor<T>> for Pezpallet<T> {
352 fn integrity_test() {
353 assert!(T::MinEligibleCollators::get() > 0, "chain must require at least one collator");
354 assert!(
355 T::MaxInvulnerables::get().saturating_add(T::MaxCandidates::get())
356 >= T::MinEligibleCollators::get(),
357 "invulnerables and candidates must be able to satisfy collator demand"
358 );
359 }
360
361 #[cfg(feature = "try-runtime")]
362 fn try_state(_: BlockNumberFor<T>) -> Result<(), pezsp_runtime::TryRuntimeError> {
363 Self::do_try_state()
364 }
365 }
366
367 #[pezpallet::call]
368 impl<T: Config> Pezpallet<T> {
369 #[pezpallet::call_index(0)]
383 #[pezpallet::weight(T::WeightInfo::set_invulnerables(new.len() as u32))]
384 pub fn set_invulnerables(origin: OriginFor<T>, new: Vec<T::AccountId>) -> DispatchResult {
385 T::UpdateOrigin::ensure_origin(origin)?;
386
387 if new.is_empty() {
389 ensure!(
391 CandidateList::<T>::decode_len().unwrap_or_default()
392 >= T::MinEligibleCollators::get() as usize,
393 Error::<T>::TooFewEligibleCollators
394 );
395 }
396
397 ensure!(
400 new.len() as u32 <= T::MaxInvulnerables::get(),
401 Error::<T>::TooManyInvulnerables
402 );
403
404 let mut new_with_keys = Vec::new();
405
406 for account_id in &new {
408 let validator_key = T::ValidatorIdOf::convert(account_id.clone());
410 match validator_key {
411 Some(key) => {
412 if !T::ValidatorRegistration::is_registered(&key) {
414 Self::deposit_event(Event::InvalidInvulnerableSkipped {
415 account_id: account_id.clone(),
416 });
417 continue;
418 }
419 },
421 None => {
423 Self::deposit_event(Event::InvalidInvulnerableSkipped {
424 account_id: account_id.clone(),
425 });
426 continue;
427 },
428 }
429
430 new_with_keys.push(account_id.clone());
431 }
432
433 let mut bounded_invulnerables =
435 BoundedVec::<_, T::MaxInvulnerables>::try_from(new_with_keys)
436 .map_err(|_| Error::<T>::TooManyInvulnerables)?;
437
438 bounded_invulnerables.sort();
440
441 Invulnerables::<T>::put(&bounded_invulnerables);
442 Self::deposit_event(Event::NewInvulnerables {
443 invulnerables: bounded_invulnerables.to_vec(),
444 });
445
446 Ok(())
447 }
448
449 #[pezpallet::call_index(1)]
455 #[pezpallet::weight(T::WeightInfo::set_desired_candidates())]
456 pub fn set_desired_candidates(
457 origin: OriginFor<T>,
458 max: u32,
459 ) -> DispatchResultWithPostInfo {
460 T::UpdateOrigin::ensure_origin(origin)?;
461 if max > T::MaxCandidates::get() {
463 log::warn!("max > T::MaxCandidates; you might need to run benchmarks again");
464 }
465 DesiredCandidates::<T>::put(max);
466 Self::deposit_event(Event::NewDesiredCandidates { desired_candidates: max });
467 Ok(().into())
468 }
469
470 #[pezpallet::call_index(2)]
478 #[pezpallet::weight(T::WeightInfo::set_candidacy_bond(
479 T::MaxCandidates::get(),
480 T::MaxCandidates::get()
481 ))]
482 pub fn set_candidacy_bond(
483 origin: OriginFor<T>,
484 bond: BalanceOf<T>,
485 ) -> DispatchResultWithPostInfo {
486 T::UpdateOrigin::ensure_origin(origin)?;
487 let bond_increased = CandidacyBond::<T>::mutate(|old_bond| -> bool {
488 let bond_increased = *old_bond < bond;
489 *old_bond = bond;
490 bond_increased
491 });
492 let initial_len = CandidateList::<T>::decode_len().unwrap_or_default();
493 let kicked = (bond_increased && initial_len > 0)
494 .then(|| {
495 CandidateList::<T>::mutate(|candidates| -> usize {
498 let first_safe_candidate = candidates
499 .iter()
500 .position(|candidate| candidate.deposit >= bond)
501 .unwrap_or(initial_len);
502 let kicked_candidates = candidates.drain(..first_safe_candidate);
503 for candidate in kicked_candidates {
504 T::Currency::unreserve(&candidate.who, candidate.deposit);
505 LastAuthoredBlock::<T>::remove(candidate.who);
506 }
507 first_safe_candidate
508 })
509 })
510 .unwrap_or_default();
511 Self::deposit_event(Event::NewCandidacyBond { bond_amount: bond });
512 Ok(Some(T::WeightInfo::set_candidacy_bond(
513 bond_increased.then(|| initial_len as u32).unwrap_or_default(),
514 kicked as u32,
515 ))
516 .into())
517 }
518
519 #[pezpallet::call_index(3)]
524 #[pezpallet::weight(T::WeightInfo::register_as_candidate(T::MaxCandidates::get()))]
525 pub fn register_as_candidate(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
526 let who = ensure_signed(origin)?;
527
528 let length: u32 = CandidateList::<T>::decode_len()
530 .unwrap_or_default()
531 .try_into()
532 .unwrap_or_default();
533 ensure!(length < T::MaxCandidates::get(), Error::<T>::TooManyCandidates);
534 ensure!(!Invulnerables::<T>::get().contains(&who), Error::<T>::AlreadyInvulnerable);
535
536 let validator_key = T::ValidatorIdOf::convert(who.clone())
537 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
538 ensure!(
539 T::ValidatorRegistration::is_registered(&validator_key),
540 Error::<T>::ValidatorNotRegistered
541 );
542
543 let deposit = CandidacyBond::<T>::get();
544 CandidateList::<T>::try_mutate(|candidates| -> Result<(), DispatchError> {
546 ensure!(
547 !candidates.iter().any(|candidate_info| candidate_info.who == who),
548 Error::<T>::AlreadyCandidate
549 );
550 T::Currency::reserve(&who, deposit)?;
551 LastAuthoredBlock::<T>::insert(
552 who.clone(),
553 pezframe_system::Pezpallet::<T>::block_number() + T::KickThreshold::get(),
554 );
555 candidates
556 .try_insert(0, CandidateInfo { who: who.clone(), deposit })
557 .map_err(|_| Error::<T>::InsertToCandidateListFailed)?;
558 Ok(())
559 })?;
560
561 Self::deposit_event(Event::CandidateAdded { account_id: who, deposit });
562 Ok(Some(T::WeightInfo::register_as_candidate(length + 1)).into())
566 }
567
568 #[pezpallet::call_index(4)]
574 #[pezpallet::weight(T::WeightInfo::leave_intent(T::MaxCandidates::get()))]
575 pub fn leave_intent(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
576 let who = ensure_signed(origin)?;
577 ensure!(
578 Self::eligible_collators() > T::MinEligibleCollators::get(),
579 Error::<T>::TooFewEligibleCollators
580 );
581 let length = CandidateList::<T>::decode_len().unwrap_or_default();
582 Self::try_remove_candidate(&who, true)?;
584
585 Ok(Some(T::WeightInfo::leave_intent(length.saturating_sub(1) as u32)).into())
586 }
587
588 #[pezpallet::call_index(5)]
593 #[pezpallet::weight(T::WeightInfo::add_invulnerable(
594 T::MaxInvulnerables::get().saturating_sub(1),
595 T::MaxCandidates::get()
596 ))]
597 pub fn add_invulnerable(
598 origin: OriginFor<T>,
599 who: T::AccountId,
600 ) -> DispatchResultWithPostInfo {
601 T::UpdateOrigin::ensure_origin(origin)?;
602
603 let validator_key = T::ValidatorIdOf::convert(who.clone())
605 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
606 ensure!(
607 T::ValidatorRegistration::is_registered(&validator_key),
608 Error::<T>::ValidatorNotRegistered
609 );
610
611 Invulnerables::<T>::try_mutate(|invulnerables| -> DispatchResult {
612 match invulnerables.binary_search(&who) {
613 Ok(_) => return Err(Error::<T>::AlreadyInvulnerable)?,
614 Err(pos) => invulnerables
615 .try_insert(pos, who.clone())
616 .map_err(|_| Error::<T>::TooManyInvulnerables)?,
617 }
618 Ok(())
619 })?;
620
621 let _ = Self::try_remove_candidate(&who, false);
624
625 Self::deposit_event(Event::InvulnerableAdded { account_id: who });
626
627 let weight_used = T::WeightInfo::add_invulnerable(
628 Invulnerables::<T>::decode_len()
629 .unwrap_or_default()
630 .try_into()
631 .unwrap_or(T::MaxInvulnerables::get().saturating_sub(1)),
632 CandidateList::<T>::decode_len()
633 .unwrap_or_default()
634 .try_into()
635 .unwrap_or(T::MaxCandidates::get()),
636 );
637
638 Ok(Some(weight_used).into())
639 }
640
641 #[pezpallet::call_index(6)]
646 #[pezpallet::weight(T::WeightInfo::remove_invulnerable(T::MaxInvulnerables::get()))]
647 pub fn remove_invulnerable(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
648 T::UpdateOrigin::ensure_origin(origin)?;
649
650 ensure!(
651 Self::eligible_collators() > T::MinEligibleCollators::get(),
652 Error::<T>::TooFewEligibleCollators
653 );
654
655 Invulnerables::<T>::try_mutate(|invulnerables| -> DispatchResult {
656 let pos =
657 invulnerables.binary_search(&who).map_err(|_| Error::<T>::NotInvulnerable)?;
658 invulnerables.remove(pos);
659 Ok(())
660 })?;
661
662 Self::deposit_event(Event::InvulnerableRemoved { account_id: who });
663 Ok(())
664 }
665
666 #[pezpallet::call_index(7)]
674 #[pezpallet::weight(T::WeightInfo::update_bond(T::MaxCandidates::get()))]
675 pub fn update_bond(
676 origin: OriginFor<T>,
677 new_deposit: BalanceOf<T>,
678 ) -> DispatchResultWithPostInfo {
679 let who = ensure_signed(origin)?;
680 ensure!(new_deposit >= CandidacyBond::<T>::get(), Error::<T>::DepositTooLow);
681 let length =
685 CandidateList::<T>::try_mutate(|candidates| -> Result<usize, DispatchError> {
686 let idx = candidates
687 .iter()
688 .position(|candidate_info| candidate_info.who == who)
689 .ok_or_else(|| Error::<T>::NotCandidate)?;
690 let candidate_count = candidates.len();
691 let mut info = candidates.remove(idx);
693 let old_deposit = info.deposit;
694 if new_deposit > old_deposit {
695 T::Currency::reserve(&who, new_deposit - old_deposit)?;
696 } else if new_deposit < old_deposit {
697 ensure!(
699 idx.saturating_add(DesiredCandidates::<T>::get() as usize)
700 < candidate_count,
701 Error::<T>::InvalidUnreserve
702 );
703 T::Currency::unreserve(&who, old_deposit - new_deposit);
704 } else {
705 return Err(Error::<T>::IdenticalDeposit.into());
706 }
707
708 info.deposit = new_deposit;
710 let new_pos = candidates
711 .iter()
712 .position(|candidate| candidate.deposit >= new_deposit)
713 .unwrap_or_else(|| candidates.len());
714 candidates
715 .try_insert(new_pos, info)
716 .map_err(|_| Error::<T>::InsertToCandidateListFailed)?;
717
718 Ok(candidate_count)
719 })?;
720
721 Self::deposit_event(Event::CandidateBondUpdated {
722 account_id: who,
723 deposit: new_deposit,
724 });
725 Ok(Some(T::WeightInfo::update_bond(length as u32)).into())
726 }
727
728 #[pezpallet::call_index(8)]
736 #[pezpallet::weight(T::WeightInfo::take_candidate_slot(T::MaxCandidates::get()))]
737 pub fn take_candidate_slot(
738 origin: OriginFor<T>,
739 deposit: BalanceOf<T>,
740 target: T::AccountId,
741 ) -> DispatchResultWithPostInfo {
742 let who = ensure_signed(origin)?;
743
744 ensure!(!Invulnerables::<T>::get().contains(&who), Error::<T>::AlreadyInvulnerable);
745 ensure!(deposit >= CandidacyBond::<T>::get(), Error::<T>::InsufficientBond);
746
747 let validator_key = T::ValidatorIdOf::convert(who.clone())
748 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
749 ensure!(
750 T::ValidatorRegistration::is_registered(&validator_key),
751 Error::<T>::ValidatorNotRegistered
752 );
753
754 let length = CandidateList::<T>::decode_len().unwrap_or_default();
755 let target_info = CandidateList::<T>::try_mutate(
760 |candidates| -> Result<CandidateInfo<T::AccountId, BalanceOf<T>>, DispatchError> {
761 let mut target_info_idx = None;
763 let mut new_info_idx = None;
764 for (idx, candidate_info) in candidates.iter().enumerate() {
765 ensure!(candidate_info.who != who, Error::<T>::AlreadyCandidate);
769 if candidate_info.who == target {
773 target_info_idx = Some(idx);
774 }
775 if new_info_idx.is_none() && candidate_info.deposit >= deposit {
778 new_info_idx = Some(idx);
779 }
780 }
781 let target_info_idx =
782 target_info_idx.ok_or(Error::<T>::TargetIsNotCandidate)?;
783
784 let target_info = candidates.remove(target_info_idx);
786 ensure!(deposit > target_info.deposit, Error::<T>::InsufficientBond);
787
788 let new_pos = new_info_idx
791 .map(|i| i.saturating_sub(1))
792 .unwrap_or_else(|| candidates.len());
793 let new_info = CandidateInfo { who: who.clone(), deposit };
794 candidates
796 .try_insert(new_pos, new_info)
797 .expect("candidate count previously decremented; qed");
798
799 Ok(target_info)
800 },
801 )?;
802 T::Currency::reserve(&who, deposit)?;
803 T::Currency::unreserve(&target_info.who, target_info.deposit);
804 LastAuthoredBlock::<T>::remove(target_info.who.clone());
805 LastAuthoredBlock::<T>::insert(
806 who.clone(),
807 pezframe_system::Pezpallet::<T>::block_number() + T::KickThreshold::get(),
808 );
809
810 Self::deposit_event(Event::CandidateReplaced { old: target, new: who, deposit });
811 Ok(Some(T::WeightInfo::take_candidate_slot(length as u32)).into())
812 }
813 }
814
815 impl<T: Config> Pezpallet<T> {
816 pub fn account_id() -> T::AccountId {
818 T::PotId::get().into_account_truncating()
819 }
820
821 fn eligible_collators() -> u32 {
824 CandidateList::<T>::decode_len()
825 .unwrap_or_default()
826 .saturating_add(Invulnerables::<T>::decode_len().unwrap_or_default())
827 .try_into()
828 .unwrap_or(u32::MAX)
829 }
830
831 fn try_remove_candidate(
833 who: &T::AccountId,
834 remove_last_authored: bool,
835 ) -> Result<(), DispatchError> {
836 CandidateList::<T>::try_mutate(|candidates| -> Result<(), DispatchError> {
837 let idx = candidates
838 .iter()
839 .position(|candidate_info| candidate_info.who == *who)
840 .ok_or(Error::<T>::NotCandidate)?;
841 let deposit = candidates[idx].deposit;
842 T::Currency::unreserve(who, deposit);
843 candidates.remove(idx);
844 if remove_last_authored {
845 LastAuthoredBlock::<T>::remove(who.clone())
846 };
847 Ok(())
848 })?;
849 Self::deposit_event(Event::CandidateRemoved { account_id: who.clone() });
850 Ok(())
851 }
852
853 pub fn assemble_collators() -> Vec<T::AccountId> {
857 let desired_candidates = DesiredCandidates::<T>::get() as usize;
859 let mut collators = Invulnerables::<T>::get().to_vec();
860 collators.extend(
861 CandidateList::<T>::get()
862 .iter()
863 .rev()
864 .cloned()
865 .take(desired_candidates)
866 .map(|candidate_info| candidate_info.who),
867 );
868 collators
869 }
870
871 pub fn kick_stale_candidates(candidates: impl IntoIterator<Item = T::AccountId>) -> u32 {
876 let now = pezframe_system::Pezpallet::<T>::block_number();
877 let kick_threshold = T::KickThreshold::get();
878 let min_collators = T::MinEligibleCollators::get();
879 candidates
880 .into_iter()
881 .filter_map(|c| {
882 let last_block = LastAuthoredBlock::<T>::get(c.clone());
883 let since_last = now.saturating_sub(last_block);
884
885 let is_invulnerable = Invulnerables::<T>::get().contains(&c);
886 let is_lazy = since_last >= kick_threshold;
887
888 if is_invulnerable {
889 let _ = Self::try_remove_candidate(&c, false);
893 None
894 } else {
895 if Self::eligible_collators() <= min_collators || !is_lazy {
896 Some(c)
899 } else {
900 let _ = Self::try_remove_candidate(&c, true);
902 None
903 }
904 }
905 })
906 .count()
907 .try_into()
908 .expect("filter_map operation can't result in a bounded vec larger than its original; qed")
909 }
910
911 #[cfg(any(test, feature = "try-runtime"))]
923 pub fn do_try_state() -> Result<(), pezsp_runtime::TryRuntimeError> {
924 let desired_candidates = DesiredCandidates::<T>::get();
925
926 pezframe_support::ensure!(
927 desired_candidates <= T::MaxCandidates::get(),
928 "Shouldn't demand more candidates than the pezpallet config allows."
929 );
930
931 pezframe_support::ensure!(
932 desired_candidates.saturating_add(T::MaxInvulnerables::get()) >=
933 T::MinEligibleCollators::get(),
934 "Invulnerable set together with desired candidates should be able to meet the collator quota."
935 );
936
937 Ok(())
938 }
939 }
940
941 impl<T: Config + pezpallet_authorship::Config>
944 pezpallet_authorship::EventHandler<T::AccountId, BlockNumberFor<T>> for Pezpallet<T>
945 {
946 fn note_author(author: T::AccountId) {
947 let pot = Self::account_id();
948 let reward = T::Currency::free_balance(&pot)
950 .checked_sub(&T::Currency::minimum_balance())
951 .unwrap_or_else(Zero::zero)
952 .div(2u32.into());
953 let _success = T::Currency::transfer(&pot, &author, reward, KeepAlive);
955 debug_assert!(_success.is_ok());
956 LastAuthoredBlock::<T>::insert(author, pezframe_system::Pezpallet::<T>::block_number());
957
958 pezframe_system::Pezpallet::<T>::register_extra_weight_unchecked(
959 T::WeightInfo::note_author(),
960 DispatchClass::Mandatory,
961 );
962 }
963 }
964
965 impl<T: Config> SessionManager<T::AccountId> for Pezpallet<T> {
967 fn new_session(index: SessionIndex) -> Option<Vec<T::AccountId>> {
968 log::info!(
969 "assembling new collators for new session {} at #{:?}",
970 index,
971 <pezframe_system::Pezpallet<T>>::block_number(),
972 );
973
974 let candidates_len_before: u32 = CandidateList::<T>::decode_len()
978 .unwrap_or_default()
979 .try_into()
980 .expect("length is at most `T::MaxCandidates`, so it must fit in `u32`; qed");
981 let active_candidates_count = Self::kick_stale_candidates(
982 CandidateList::<T>::get()
983 .iter()
984 .map(|candidate_info| candidate_info.who.clone()),
985 );
986 let removed = candidates_len_before.saturating_sub(active_candidates_count);
987 let result = Self::assemble_collators();
988
989 pezframe_system::Pezpallet::<T>::register_extra_weight_unchecked(
990 T::WeightInfo::new_session(removed, candidates_len_before),
991 DispatchClass::Mandatory,
992 );
993 Some(result)
994 }
995 fn start_session(_: SessionIndex) {
996 }
998 fn end_session(_: SessionIndex) {
999 }
1001 }
1002}
1003
1004pub struct StakingPotAccountId<R>(PhantomData<R>);
1006impl<R> TypedGet for StakingPotAccountId<R>
1007where
1008 R: crate::Config,
1009{
1010 type Type = <R as pezframe_system::Config>::AccountId;
1011 fn get() -> Self::Type {
1012 <crate::Pezpallet<R>>::account_id()
1013 }
1014}