git.delta.rocks / unique-network / refs/commits / d41364fb01e4

difftreelog

feat(collator-selection) method refactoring + unit tests complete

Fahrrader2022-12-22parent: #313a9f3.patch.diff
in: master

9 files changed

modifiedpallets/collator-selection/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/collator-selection/src/benchmarking.rs
+++ b/pallets/collator-selection/src/benchmarking.rs
@@ -130,7 +130,7 @@
 	where_clause { where T: pallet_authorship::Config + session::Config }
 
 	set_invulnerables {
-		let b in 1 .. T::MaxInvulnerables::get();
+		let b in 1 .. T::MaxCollators::get();
 		let new_invulnerables = register_validators::<T>(b);
 		let origin = T::UpdateOrigin::successful_origin();
 	}: {
@@ -142,16 +142,16 @@
 		assert_last_event::<T>(Event::NewInvulnerables{invulnerables: new_invulnerables}.into());
 	}
 
-	set_desired_candidates {
+	set_desired_collators {
 		let max: u32 = 999;
 		let origin = T::UpdateOrigin::successful_origin();
 	}: {
 		assert_ok!(
-			<CollatorSelection<T>>::set_desired_candidates(origin, max.clone())
+			<CollatorSelection<T>>::set_desired_collators(origin, max.clone())
 		);
 	}
 	verify {
-		assert_last_event::<T>(Event::NewDesiredCandidates{desired_candidates: max}.into());
+		assert_last_event::<T>(Event::NewDesiredCollators{desired_collators: max}.into());
 	}
 
 	set_license_bond {
@@ -169,10 +169,10 @@
 	// worse case is when we have all the max-candidate slots filled except one, and we fill that
 	// one.
 	register_as_candidate {
-		let c in 1 .. T::MaxCandidates::get();
+		let c in 1 .. T::MaxCollators::get();
 
 		<LicenseBond<T>>::put(T::Currency::minimum_balance());
-		<DesiredCandidates<T>>::put(c + 1);
+		<DesiredCollators<T>>::put(c + 1);
 
 		register_validators::<T>(c);
 		register_candidates::<T>(c);
@@ -194,9 +194,9 @@
 
 	// worse case is the last candidate leaving.
 	leave_intent {
-		let c in (T::MinCandidates::get() + 1) .. T::MaxCandidates::get();
+		let c in (T::MinCandidates::get() + 1) .. T::MaxCollators::get();
 		<LicenseBond<T>>::put(T::Currency::minimum_balance());
-		<DesiredCandidates<T>>::put(c);
+		<DesiredCollators<T>>::put(c);
 
 		register_validators::<T>(c);
 		register_candidates::<T>(c);
@@ -230,11 +230,11 @@
 
 	// worst case for new session.
 	new_session {
-		let r in 1 .. T::MaxCandidates::get();
-		let c in 1 .. T::MaxCandidates::get();
+		let r in 1 .. T::MaxCollators::get();
+		let c in 1 .. T::MaxCollators::get();
 
 		<LicenseBond<T>>::put(T::Currency::minimum_balance());
-		<DesiredCandidates<T>>::put(c);
+		<DesiredCollators<T>>::put(c);
 		frame_system::Pallet::<T>::set_block_number(0u32.into());
 
 		register_validators::<T>(c);
modifiedpallets/collator-selection/src/lib.rsdiffbeforeafterboth
98 dispatch::{DispatchClass, DispatchResultWithPostInfo},98 dispatch::{DispatchClass, DispatchResultWithPostInfo},
99 inherent::Vec,99 inherent::Vec,
100 pallet_prelude::*,100 pallet_prelude::*,
101 sp_runtime::{101 sp_runtime::traits::{AccountIdConversion, CheckedSub, Saturating, Zero},
102 traits::{AccountIdConversion, CheckedSub, Saturating, Zero},
103 RuntimeDebug,
104 },
105 traits::{102 traits::{
106 Currency, EnsureOrigin, ExistenceRequirement::KeepAlive, ReservableCurrency,103 Currency, EnsureOrigin, ExistenceRequirement::KeepAlive, ReservableCurrency,
107 ValidatorRegistration,104 ValidatorRegistration,
146 /// Account Identifier from which the internal Pot is generated.143 /// Account Identifier from which the internal Pot is generated.
147 type PotId: Get<PalletId>;144 type PotId: Get<PalletId>;
148145
149 /// Maximum number of candidates that we should have. This is enforced in code.146 /// Maximum number of candidates and invulnerables that we should have. This is enforced in code.
150 ///
151 /// This does not take into account the invulnerables.
152 type MaxCandidates: Get<u32>;147 type MaxCollators: Get<u32>;
153
154 /// Minimum number of candidates that we should have. This is used for disaster recovery.
155 ///
156 /// This does not take into account the invulnerables.
157 type MinCandidates: Get<u32>;
158
159 /// Maximum number of invulnerables. This is enforced in code.
160 type MaxInvulnerables: Get<u32>;
161148
162 /// If kicked, how much of the collator's deposit will be slashed and sent to the slash destination.149 /// If kicked, how much of the collator's deposit will be slashed and sent to the slash destination.
163 type SlashRatio: Get<Perbill>;150 type SlashRatio: Get<Perbill>;
177 type WeightInfo: WeightInfo;164 type WeightInfo: WeightInfo;
178 }165 }
179
180 /// Basic information about a collation candidate.
181 #[derive(
182 PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen,
183 )]
184 pub struct LicenseInfo<AccountId, Balance> {
185 /// Account identifier.
186 pub who: AccountId,
187 /// Reserved deposit.
188 pub deposit: Balance,
189 }
190166
191 #[pallet::pallet]167 #[pallet::pallet]
192 #[pallet::generate_store(pub(super) trait Store)]168 #[pallet::generate_store(pub(super) trait Store)]
196 #[pallet::storage]172 #[pallet::storage]
197 #[pallet::getter(fn invulnerables)]173 #[pallet::getter(fn invulnerables)]
198 pub type Invulnerables<T: Config> =174 pub type Invulnerables<T: Config> =
199 StorageValue<_, BoundedVec<T::AccountId, T::MaxInvulnerables>, ValueQuery>;175 StorageValue<_, BoundedVec<T::AccountId, T::MaxCollators>, ValueQuery>;
200176
201 /// The (community) collation license holders.177 /// The (community) collation license holders.
202 #[pallet::storage]178 #[pallet::storage]
209 #[pallet::getter(fn candidates)]185 #[pallet::getter(fn candidates)]
210 pub type Candidates<T: Config> = StorageValue<186 pub type Candidates<T: Config> = StorageValue<
211 _,187 _,
212 BoundedVec<T::AccountId, T::MaxCandidates>, //LicenseInfo<T::AccountId, BalanceOf<T>>, T::MaxCandidates>, // license ID?188 BoundedVec<T::AccountId, T::MaxCollators>, //LicenseInfo<T::AccountId, BalanceOf<T>>, T::MaxCollators>, // license ID?
213 ValueQuery,189 ValueQuery,
214 >;190 >;
215191
228204
229 /// Desired number of candidates.205 /// Desired number of candidates.
230 ///206 ///
231 /// This should ideally always be less than [`Config::MaxCandidates`] for weights to be correct.207 /// This should ideally always be less than [`Config::MaxCollators`] for weights to be correct.
232 #[pallet::storage]208 #[pallet::storage]
233 #[pallet::getter(fn desired_candidates)]209 #[pallet::getter(fn desired_collators)]
234 pub type DesiredCandidates<T> = StorageValue<_, u32, ValueQuery>;210 pub type DesiredCollators<T> = StorageValue<_, u32, ValueQuery>;
235211
236 /// Fixed amount to deposit to become a collator.212 /// Fixed amount to deposit to become a collator.
237 ///213 ///
245 pub invulnerables: Vec<T::AccountId>,221 pub invulnerables: Vec<T::AccountId>,
246 pub license_bond: BalanceOf<T>,222 pub license_bond: BalanceOf<T>,
247 pub kick_threshold: T::BlockNumber,223 pub kick_threshold: T::BlockNumber,
248 pub desired_candidates: u32,224 pub desired_collators: u32,
249 }225 }
250226
251 #[cfg(feature = "std")]227 #[cfg(feature = "std")]
255 invulnerables: Default::default(),231 invulnerables: Default::default(),
256 license_bond: Default::default(),232 license_bond: Default::default(),
257 kick_threshold: T::BlockNumber::one(),233 kick_threshold: T::BlockNumber::one(),
258 desired_candidates: Default::default(),234 desired_collators: Default::default(),
259 }235 }
260 }236 }
261 }237 }
273 );249 );
274250
275 let bounded_invulnerables =251 let bounded_invulnerables =
276 BoundedVec::<_, T::MaxInvulnerables>::try_from(self.invulnerables.clone())252 BoundedVec::<_, T::MaxCollators>::try_from(self.invulnerables.clone())
277 .expect("genesis invulnerables are more than T::MaxInvulnerables");253 .expect("genesis invulnerables are more than T::MaxCollators");
278 assert!(254 assert!(
279 T::MaxCandidates::get() >= self.desired_candidates,255 T::MaxCollators::get() >= self.desired_collators,
280 "genesis desired_candidates are more than T::MaxCandidates",256 "genesis desired_collators are more than T::MaxCollators",
281 );257 );
282258
283 <DesiredCandidates<T>>::put(&self.desired_candidates);259 <DesiredCollators<T>>::put(self.desired_collators);
284 <LicenseBond<T>>::put(&self.license_bond);260 <LicenseBond<T>>::put(self.license_bond);
285 <KickThreshold<T>>::put(&self.kick_threshold);261 <KickThreshold<T>>::put(self.kick_threshold);
286 <Invulnerables<T>>::put(bounded_invulnerables);262 <Invulnerables<T>>::put(bounded_invulnerables);
287 }263 }
288 }264 }
289265
290 #[pallet::event]266 #[pallet::event]
291 #[pallet::generate_deposit(pub(super) fn deposit_event)]267 #[pallet::generate_deposit(pub(super) fn deposit_event)]
292 pub enum Event<T: Config> {268 pub enum Event<T: Config> {
293 NewDesiredCandidates {269 NewDesiredCollators {
294 desired_candidates: u32,270 desired_collators: u32,
295 },271 },
296 NewLicenseBond {272 NewLicenseBond {
297 bond_amount: BalanceOf<T>,273 bond_amount: BalanceOf<T>,
326 pub enum Error<T> {302 pub enum Error<T> {
327 /// Too many candidates303 /// Too many candidates
328 TooManyCandidates,304 TooManyCandidates,
329 /// Too few candidates
330 TooFewCandidates,
331 /// Unknown error305 /// Unknown error
332 Unknown,306 Unknown,
333 /// Permission issue307 /// Permission issue
334 Permission,308 Permission,
335 /// User already holds license to collate309 /// User already holds license to collate
336 AlreadyLicenseHolder,310 AlreadyHoldingLicense,
337 /// User does not hold a license to collate311 /// User does not hold a license to collate
338 NoLicense,312 NoLicense,
339 /// User is already a candidate313 /// User is already a candidate
360 #[pallet::call]334 #[pallet::call]
361 impl<T: Config> Pallet<T> {335 impl<T: Config> Pallet<T> {
362 /// Add a collator to the list of invulnerable (fixed) collators.336 /// Add a collator to the list of invulnerable (fixed) collators.
363 #[pallet::weight(T::WeightInfo::set_invulnerables(1 as u32))] // todo:collator weight337 #[pallet::weight(T::WeightInfo::set_invulnerables(1u32))] // todo:collator weight
364 pub fn add_invulnerable(338 pub fn add_invulnerable(
365 origin: OriginFor<T>,339 origin: OriginFor<T>,
366 new: T::AccountId,340 new: T::AccountId,
379 return Ok(().into());353 return Ok(().into());
380 }354 }
381
382 // todo:collator check license holders, release moneys, promotion!
383 // force_release_license? Error::<T>::lreadyLicenseHolder?
384355
385 <Invulnerables<T>>::try_append(new.clone())356 <Invulnerables<T>>::try_append(new.clone())
386 .map_err(|_| Error::<T>::TooManyInvulnerables)?;357 .map_err(|_| Error::<T>::TooManyInvulnerables)?;
358
359 // try to offboard the new invulnerable if it was a collator candidate before
360 let _ = Self::try_remove_candidate(&new);
361
387 Self::deposit_event(Event::InvulnerableAdded { invulnerable: new });362 Self::deposit_event(Event::InvulnerableAdded { invulnerable: new });
388 Ok(().into())363 Ok(().into())
417 Ok(().into())392 Ok(().into())
418 }393 }
419394
420 /// Set the ideal number of collators (not including the invulnerables).395 /// Set the ideal number of collators. If lowering this number,
421 /// If lowering this number, then the number of running collators could be higher than this figure.396 /// then the number of running collators could be higher than this figure.
422 /// Aside from that edge case, there should be no other way to have more collators than the desired number.397 /// Aside from that edge case, there should be no other way to have more collators than the desired number.
423 #[pallet::weight(T::WeightInfo::set_desired_candidates())]398 #[pallet::weight(T::WeightInfo::set_desired_collators())]
424 pub fn set_desired_candidates(399 pub fn set_desired_collators(origin: OriginFor<T>, max: u32) -> DispatchResultWithPostInfo {
425 origin: OriginFor<T>,
426 max: u32,
427 ) -> DispatchResultWithPostInfo {
428 T::UpdateOrigin::ensure_origin(origin)?;400 T::UpdateOrigin::ensure_origin(origin)?;
429 // we trust origin calls, this is just a for more accurate benchmarking401 // we trust origin calls, this is just a for more accurate benchmarking
430 if max > T::MaxCandidates::get() {402 if max > T::MaxCollators::get() {
431 log::warn!("max > T::MaxCandidates; you might need to run benchmarks again");403 log::warn!("max > T::MaxCollators; you might need to run benchmarks again");
432 }404 }
433 <DesiredCandidates<T>>::put(&max);405 <DesiredCollators<T>>::put(max);
434 Self::deposit_event(Event::NewDesiredCandidates {406 Self::deposit_event(Event::NewDesiredCollators {
435 desired_candidates: max,407 desired_collators: max,
436 });408 });
437 Ok(().into())409 Ok(().into())
438 }410 }
444 bond: BalanceOf<T>,416 bond: BalanceOf<T>,
445 ) -> DispatchResultWithPostInfo {417 ) -> DispatchResultWithPostInfo {
446 T::UpdateOrigin::ensure_origin(origin)?;418 T::UpdateOrigin::ensure_origin(origin)?;
447 <LicenseBond<T>>::put(&bond);419 <LicenseBond<T>>::put(bond);
448 Self::deposit_event(Event::NewLicenseBond { bond_amount: bond });420 Self::deposit_event(Event::NewLicenseBond { bond_amount: bond });
449 Ok(().into())421 Ok(().into())
450 }422 }
470 /// (a) already have registered session keys and (b) be able to reserve the `LicenseBond`.442 /// (a) already have registered session keys and (b) be able to reserve the `LicenseBond`.
471 ///443 ///
472 /// This call is not available to `Invulnerable` collators.444 /// This call is not available to `Invulnerable` collators.
473 #[pallet::weight(T::WeightInfo::register_as_candidate(T::MaxCandidates::get()))] // todo:collator weight445 #[pallet::weight(T::WeightInfo::register_as_candidate(T::MaxCollators::get()))] // todo:collator weight
474 pub fn get_license(origin: OriginFor<T>) -> DispatchResultWithPostInfo {446 pub fn get_license(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
475 // register_as_candidate447 // register_as_candidate
476 let who = ensure_signed(origin)?;448 let who = ensure_signed(origin)?;
477449
478 if Licenses::<T>::contains_key(&who) {450 if Licenses::<T>::contains_key(&who) {
479 return Ok(().into());451 return Err(Error::<T>::AlreadyHoldingLicense.into());
480 }452 }
481453
482 ensure!(454 /*ensure!(
483 !Self::invulnerables().contains(&who),455 !Self::invulnerables().contains(&who),
484 Error::<T>::AlreadyInvulnerable456 Error::<T>::AlreadyInvulnerable
485 );457 );*/
486458
487 let validator_key = T::ValidatorIdOf::convert(who.clone())459 let validator_key = T::ValidatorIdOf::convert(who.clone())
488 .ok_or(Error::<T>::NoAssociatedValidatorId)?;460 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
501 T::Currency::reserve(&who, deposit)?;473 T::Currency::reserve(&who, deposit)?;
502 Licenses::<T>::insert(who.clone(), deposit);474 Licenses::<T>::insert(who.clone(), deposit);
503475
504 /*let current_count =476 /*let current_count =
505 <Licenses<T>>::try_mutate(|licenses| -> Result<usize, DispatchError> {477 <Licenses<T>>::try_mutate(|licenses| -> Result<usize, DispatchError> {
506 if T::OriginPrivilegeCmp::cmp_privilege(&origin, &scheduled.origin) {478 if T::OriginPrivilegeCmp::cmp_privilege(&origin, &scheduled.origin) {
507 return Err(BadOrigin.into());479 return Err(BadOrigin.into());
508 }480 }
509 if candidates.iter().any(|candidate| *candidate == who) {481 if candidates.iter().any(|candidate| *candidate == who) {
510 Err(Error::<T>::AlreadyLicenseHolder)?482 Err(Error::<T>::AlreadyHoldingLicense)?
511 } else {483 } else {
512 T::Currency::reserve(&who, deposit)?;484 T::Currency::reserve(&who, deposit)?;
513 candidates485 candidates
514 .try_push(incoming)486 .try_push(incoming)
515 .map_err(|_| Error::<T>::TooManyCandidates)?;487 .map_err(|_| Error::<T>::TooManyCandidates)?;
516 <LastAuthoredBlock<T>>::insert(488 <LastAuthoredBlock<T>>::insert(
517 who.clone(),489 who.clone(),
518 frame_system::Pallet::<T>::block_number() + Self::kick_threshold(),490 frame_system::Pallet::<T>::block_number() + Self::kick_threshold(),
519 );491 );
520 Ok(candidates.len())492 Ok(candidates.len())
521 }493 }
522 })?;*/494 })?;*/
523495
524 Self::deposit_event(Event::LicenseObtained {496 Self::deposit_event(Event::LicenseObtained {
525 account_id: who,497 account_id: who,
532 /// The account must already hold a license, and cannot offboard immediately during a session.504 /// The account must already hold a license, and cannot offboard immediately during a session.
533 ///505 ///
534 /// This call is not available to `Invulnerable` collators.506 /// This call is not available to `Invulnerable` collators.
535 #[pallet::weight(T::WeightInfo::register_as_candidate(T::MaxCandidates::get()))] // todo:collator weight507 #[pallet::weight(T::WeightInfo::register_as_candidate(T::MaxCollators::get()))] // todo:collator weight
536 pub fn onboard(origin: OriginFor<T>) -> DispatchResultWithPostInfo {508 pub fn onboard(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
537 // register_as_candidate509 // register_as_candidate
538 let who = ensure_signed(origin)?;510 let who = ensure_signed(origin)?;
539511
540 // ensure the user obtained the license.512 // ensure the user obtained the license.
541 ensure!(Licenses::<T>::contains_key(&who), Error::<T>::NoLicense);513 ensure!(Licenses::<T>::contains_key(&who), Error::<T>::NoLicense);
542 // ensure we are below limit.514 // ensure we are below limit.
543 let length = <Candidates<T>>::decode_len().unwrap_or_default();515 let length = <Candidates<T>>::decode_len().unwrap_or_default()
516 + <Invulnerables<T>>::decode_len().unwrap_or_default();
544 ensure!(517 ensure!(
545 (length as u32) < Self::desired_candidates(),518 (length as u32) < Self::desired_collators(),
546 Error::<T>::TooManyCandidates519 Error::<T>::TooManyCandidates
547 );520 );
548 // todo:collator really need it?521 // todo:collator really need it?
551 Error::<T>::AlreadyInvulnerable524 Error::<T>::AlreadyInvulnerable
552 );525 );
553526
554 let deposit = Self::license_bond();
555 // First authored block is current block plus kick threshold to handle session delay
556 /*let incoming = LicenseInfo {527 /*let incoming = LicenseInfo {
557 who: who.clone(),528 who: who.clone(),
558 deposit,529 deposit,
563 if candidates.iter().any(|candidate| *candidate == who) {534 if candidates.iter().any(|candidate| *candidate == who) {
564 Err(Error::<T>::AlreadyCandidate)?535 Err(Error::<T>::AlreadyCandidate)?
565 } else {536 } else {
566 T::Currency::reserve(&who, deposit)?;
567 candidates537 candidates
568 .try_push(who.clone())538 .try_push(who.clone())
569 .map_err(|_| Error::<T>::TooManyCandidates)?;539 .map_err(|_| Error::<T>::TooManyCandidates)?;
540 // First authored block is current block plus kick threshold to handle session delay
570 <LastAuthoredBlock<T>>::insert(541 <LastAuthoredBlock<T>>::insert(
571 who.clone(),542 who.clone(),
572 frame_system::Pallet::<T>::block_number() + Self::kick_threshold(),543 frame_system::Pallet::<T>::block_number() + Self::kick_threshold(),
583 /// session change. The license to `onboard` later at any other time will remain.554 /// session change. The license to `onboard` later at any other time will remain.
584 ///555 ///
585 /// This call will fail if the total number of candidates would drop below `MinCandidates`. todo:collator maybe not556 /// This call will fail if the total number of candidates would drop below `MinCandidates`. todo:collator maybe not
586 #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCandidates::get()))] // todo:collator weight557 #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCollators::get()))] // todo:collator weight
587 pub fn offboard(origin: OriginFor<T>) -> DispatchResultWithPostInfo {558 pub fn offboard(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
588 // leave_intent559 // leave_intent
589 let who = ensure_signed(origin)?;560 let who = ensure_signed(origin)?;
590 // todo:collator invulnerables and candidates should count against min candidates together561 /* todo:collator invulnerables and candidates should count against min candidates together
591 ensure!(562 ensure!(
592 Self::candidates().len() as u32 > T::MinCandidates::get(),563 Self::candidates().len() as u32 > T::MinCandidates::get(),
593 Error::<T>::TooFewCandidates564 Error::<T>::TooFewCandidates
594 );565 );*/
595 let current_count = Self::try_remove_candidate(&who)?;566 let current_count = Self::try_remove_candidate(&who)?;
596567
597 Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into())568 Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into()) // todo:collator weight
598 }569 }
599570
600 /// Forfeit `origin`'s own license. The `LicenseBond` will be unreserved immediately.571 /// Forfeit `origin`'s own license. The `LicenseBond` will be unreserved immediately.
601 ///572 ///
602 /// This call is not available to `Invulnerable` collators.573 /// This call is not available to `Invulnerable` collators.
603 #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCandidates::get()))] // todo:collator weight574 #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCollators::get()))] // todo:collator weight
604 pub fn release_license(origin: OriginFor<T>) -> DispatchResultWithPostInfo {575 pub fn release_license(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
605 // leave_intent576 // leave_intent
606 let who = ensure_signed(origin)?;577 let who = ensure_signed(origin)?;
607 // let current_count = Self::try_remove_candidate(&who, false)?;578
608 Self::try_release_license(&who, false)?;579 let current_count = Self::try_remove_candidate_and_release_license(&who, false, true)?;
609580
610 Ok(().into())581 Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into()) // todo:collator weight
611 }582 }
612583
613 /// Force deregister `origin` as a collator candidate as a governing authority, and revoke its license.584 /// Force deregister `origin` as a collator candidate as a governing authority, and revoke its license.
614 /// Note that the collator can only leave on session change.585 /// Note that the collator can only leave on session change.
615 /// The `LicenseBond` will be unreserved and returned immediately.586 /// The `LicenseBond` will be unreserved and returned immediately.
616 ///587 ///
617 /// This call is not available to `Invulnerable` collators.588 /// This call is not available to `Invulnerable` collators.
618 #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCandidates::get()))] // todo:collator weight589 #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCollators::get()))] // todo:collator weight
619 pub fn force_release_license(590 pub fn force_revoke_license(
620 origin: OriginFor<T>,591 origin: OriginFor<T>,
621 who: T::AccountId,592 who: T::AccountId,
622 ) -> DispatchResultWithPostInfo {593 ) -> DispatchResultWithPostInfo {
623 // leave_intent594 // leave_intent
624 T::UpdateOrigin::ensure_origin(origin)?;595 T::UpdateOrigin::ensure_origin(origin)?;
625596
626 let current_count = Self::try_remove_candidate(&who)?;597 let current_count = Self::try_remove_candidate_and_release_license(&who, false, true)?;
627 Self::try_release_license(&who, false)?;
628598
629 Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into()) // todo:collator weight599 Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into()) // todo:collator weight
630 }600 }
636 T::PotId::get().into_account_truncating()606 T::PotId::get().into_account_truncating()
637 }607 }
608
609 fn try_remove_candidate_and_release_license(
610 who: &T::AccountId,
611 should_slash: bool,
612 ignore_if_not_candidate: bool,
613 ) -> Result<usize, DispatchError> {
614 let current_count = Self::try_remove_candidate(who);
615 let current_count = if ignore_if_not_candidate
616 && current_count == Err(Error::<T>::NotCandidate.into())
617 {
618 <Candidates<T>>::decode_len().unwrap_or_default()
619 } else {
620 current_count?
621 };
622 Self::try_release_license(who, should_slash)?;
623 Ok(current_count)
624 }
638625
639 /// Removes a candidate from the collator pool for the next session if they exist.626 /// Removes a candidate from the collator pool for the next session if they exist.
640 fn try_remove_candidate(who: &T::AccountId) -> Result<usize, DispatchError> {627 fn try_remove_candidate(who: &T::AccountId) -> Result<usize, DispatchError> {
657 /// Removes a candidate if they exist and sends them back their deposit, optionally slashed.644 /// Removes a candidate if they exist and sends them back their deposit, optionally slashed.
658 fn try_release_license(who: &T::AccountId, should_slash: bool) -> DispatchResult {645 fn try_release_license(who: &T::AccountId, should_slash: bool) -> DispatchResult {
659 let mut deposit_returned = BalanceOf::<T>::default();646 let mut deposit_returned = BalanceOf::<T>::default();
660 Licenses::<T>::try_mutate_exists(&who, |deposit| -> DispatchResult {647 Licenses::<T>::try_mutate_exists(who, |deposit| -> DispatchResult {
661 if let Some(deposit) = deposit.take() {648 if let Some(deposit) = deposit.take() {
662 if should_slash {649 if should_slash {
663 let slashed = T::SlashRatio::get() * deposit;650 let slashed = T::SlashRatio::get() * deposit;
690 ///677 ///
691 /// This is done on the fly, as frequent as we are told to do so, as the session manager.678 /// This is done on the fly, as frequent as we are told to do so, as the session manager.
692 pub fn assemble_collators(679 pub fn assemble_collators(
693 candidates: BoundedVec<T::AccountId, T::MaxCandidates>,680 candidates: BoundedVec<T::AccountId, T::MaxCollators>,
694 ) -> Vec<T::AccountId> {681 ) -> Vec<T::AccountId> {
695 let mut collators = Self::invulnerables().to_vec();682 let mut collators = Self::invulnerables().to_vec();
696 collators.extend(candidates);683 collators.extend(candidates);
700 /// Kicks out candidates that did not produce a block in the kick threshold687 /// Kicks out candidates that did not produce a block in the kick threshold
701 /// and **confiscates** their deposits to the treasury.688 /// and **confiscates** their deposits to the treasury.
702 pub fn kick_stale_candidates(689 pub fn kick_stale_candidates(
703 candidates: BoundedVec<T::AccountId, T::MaxCandidates>, //LicenseInfo<T::AccountId, BalanceOf<T>>690 candidates: BoundedVec<T::AccountId, T::MaxCollators>, //LicenseInfo<T::AccountId, BalanceOf<T>>
704 ) -> BoundedVec<T::AccountId, T::MaxCandidates> {691 ) -> BoundedVec<T::AccountId, T::MaxCollators> {
705 let now = frame_system::Pallet::<T>::block_number();692 let now = frame_system::Pallet::<T>::block_number();
706 let kick_threshold = Self::kick_threshold();693 let kick_threshold = Self::kick_threshold();
707 candidates694 candidates
708 .into_iter()695 .into_iter()
709 .filter_map(|c| {696 .filter_map(|c| {
710 let last_block = <LastAuthoredBlock<T>>::get(c.clone());697 let last_block = <LastAuthoredBlock<T>>::get(c.clone());
711 let since_last = now.saturating_sub(last_block);698 let since_last = now.saturating_sub(last_block);
712 if since_last < kick_threshold ||699 if since_last < kick_threshold {
713 Self::candidates().len() as u32 <= T::MinCandidates::get()
714 {
715 Some(c)700 Some(c)
716 } else {701 } else {
717 let outcome = Self::try_remove_candidate(&c);
718 if let Err(why) = outcome {
719 log::warn!("Failed to remove candidate {:?}", why);
720 debug_assert!(false, "failed to remove candidate {:?}", why);
721 return None;
722 }
723 let outcome = Self::try_release_license(&c, true);702 let outcome = Self::try_remove_candidate_and_release_license(&c, true, false);
724 if let Err(why) = outcome {703 if let Err(why) = outcome {
725 log::warn!("Failed to release license {:?}", why);704 log::warn!("Failed to kick collator and release license {:?}", why);
726 debug_assert!(false, "failed to release license {:?}", why);705 debug_assert!(false, "failed to kick collator and release license {why:?}");
727 }706 }
728 None707 None
729 }708 }
modifiedpallets/collator-selection/src/mock.rsdiffbeforeafterboth
--- a/pallets/collator-selection/src/mock.rs
+++ b/pallets/collator-selection/src/mock.rs
@@ -206,9 +206,7 @@
 
 parameter_types! {
 	pub const PotId: PalletId = PalletId(*b"PotStake");
-	pub const MaxCandidates: u32 = 20;
-	pub const MaxInvulnerables: u32 = 20;
-	pub const MinCandidates: u32 = 1;
+	pub const MaxCollators: u32 = 20;
 	pub const MaxAuthorities: u32 = 100_000;
 	pub const SlashRatio: Perbill = Perbill::one();
 }
@@ -230,9 +228,7 @@
 	type Currency = Balances;
 	type UpdateOrigin = EnsureSignedBy<RootAccount, u64>;
 	type PotId = PotId;
-	type MaxCandidates = MaxCandidates;
-	type MinCandidates = MinCandidates;
-	type MaxInvulnerables = MaxInvulnerables;
+	type MaxCollators = MaxCollators;
 	// type KickThreshold = Period;
 	type SlashRatio = SlashRatio;
 	type TreasuryAccountId = ();
@@ -263,9 +259,9 @@
 		})
 		.collect::<Vec<_>>();
 	let collator_selection = collator_selection::GenesisConfig::<Test> {
-		desired_candidates: 2,
+		desired_collators: 5,
 		license_bond: 10,
-		kick_threshold: 1,
+		kick_threshold: 10,
 		invulnerables,
 	};
 	let session = pallet_session::GenesisConfig::<Test> { keys };
modifiedpallets/collator-selection/src/tests.rsdiffbeforeafterboth
--- a/pallets/collator-selection/src/tests.rs
+++ b/pallets/collator-selection/src/tests.rs
@@ -31,7 +31,7 @@
 // limitations under the License.
 
 use crate as collator_selection;
-use crate::{mock::*, LicenseInfo, Error};
+use crate::{mock::*, Error};
 use frame_support::{
 	assert_noop, assert_ok,
 	traits::{Currency, GenesisBuild, OnInitialize},
@@ -39,10 +39,19 @@
 use pallet_balances::Error as BalancesError;
 use sp_runtime::traits::BadOrigin;
 
+fn get_license_and_onboard(account_id: <Test as frame_system::Config>::AccountId) {
+	assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(
+		account_id
+	)));
+	assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed(
+		account_id
+	)));
+}
+
 #[test]
 fn basic_setup_works() {
 	new_test_ext().execute_with(|| {
-		assert_eq!(CollatorSelection::desired_candidates(), 2);
+		assert_eq!(CollatorSelection::desired_collators(), 5);
 		assert_eq!(CollatorSelection::license_bond(), 10);
 
 		assert!(CollatorSelection::candidates().is_empty());
@@ -51,6 +60,7 @@
 }
 
 // todo:collator add more tests later
+// invulnerable after onboard + invulnerables can bypass desired_candidates
 
 #[test]
 fn it_should_add_invulnerables() {
@@ -112,21 +122,21 @@
 }
 
 #[test]
-fn set_desired_candidates_works() {
+fn set_desired_collators_works() {
 	new_test_ext().execute_with(|| {
 		// given
-		assert_eq!(CollatorSelection::desired_candidates(), 2);
+		assert_eq!(CollatorSelection::desired_collators(), 5);
 
 		// can set
-		assert_ok!(CollatorSelection::set_desired_candidates(
+		assert_ok!(CollatorSelection::set_desired_collators(
 			RuntimeOrigin::signed(RootAccount::get()),
 			7
 		));
-		assert_eq!(CollatorSelection::desired_candidates(), 7);
+		assert_eq!(CollatorSelection::desired_collators(), 7);
 
 		// rejects bad origin
 		assert_noop!(
-			CollatorSelection::set_desired_candidates(RuntimeOrigin::signed(1), 8),
+			CollatorSelection::set_desired_collators(RuntimeOrigin::signed(1), 8),
 			BadOrigin
 		);
 	});
@@ -154,166 +164,246 @@
 }
 
 #[test]
-fn cannot_register_candidate_if_too_many() {
+fn cannot_onboard_candidate_with_no_license() {
 	new_test_ext().execute_with(|| {
-		// reset desired candidates:
-		<crate::DesiredCandidates<Test>>::put(0);
+		// can't onboard a candidate who did not get a license.
+		assert_noop!(
+			CollatorSelection::onboard(RuntimeOrigin::signed(3)),
+			Error::<Test>::NoLicense,
+		);
+
+		// but give it a license and welcome aboard.
+		assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));
+		assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed(3)));
+	})
+}
+
+#[test]
+fn cannot_onboard_candidate_if_too_many() {
+	new_test_ext().execute_with(|| {
+		// reset desired candidates
+		<crate::DesiredCollators<Test>>::put(0);
+
+		// can still get a license.
+		assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(4)));
 
 		// can't accept anyone anymore.
 		assert_noop!(
-			CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)),
+			CollatorSelection::onboard(RuntimeOrigin::signed(4)),
 			Error::<Test>::TooManyCandidates,
 		);
 
-		// reset desired candidates:
-		<crate::DesiredCandidates<Test>>::put(1);
-		assert_ok!(CollatorSelection::register_as_candidate(
-			RuntimeOrigin::signed(4)
-		));
+		// reset desired candidates to invulnerables + 1
+		<crate::DesiredCollators<Test>>::put(3);
+		assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed(4)));
 
-		// but no more
+		// but no more.
+		assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(5)));
 		assert_noop!(
-			CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)),
+			CollatorSelection::onboard(RuntimeOrigin::signed(5)),
 			Error::<Test>::TooManyCandidates,
 		);
 	})
 }
 
 #[test]
-fn cannot_unregister_candidate_if_too_few() {
+fn cannot_obtain_license_if_keys_not_registered() {
 	new_test_ext().execute_with(|| {
-		// reset desired candidates:
-		<crate::DesiredCandidates<Test>>::put(1);
-		assert_ok!(CollatorSelection::register_as_candidate(
-			RuntimeOrigin::signed(4)
-		));
-
-		// can not remove too few
+		// can't 7 because keys not registered.
 		assert_noop!(
-			CollatorSelection::leave_intent(RuntimeOrigin::signed(4)),
-			Error::<Test>::TooFewCandidates,
+			CollatorSelection::get_license(RuntimeOrigin::signed(7)),
+			Error::<Test>::ValidatorNotRegistered
 		);
 	})
 }
 
 #[test]
-fn cannot_register_as_candidate_if_invulnerable() {
+fn cannot_obtain_license_if_poor() {
 	new_test_ext().execute_with(|| {
-		assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
+		assert_eq!(Balances::free_balance(&3), 100);
+		assert_eq!(Balances::free_balance(&33), 0);
 
-		// can't 1 because it is invulnerable.
-		assert_noop!(
-			CollatorSelection::register_as_candidate(RuntimeOrigin::signed(1)),
-			Error::<Test>::AlreadyInvulnerable,
-		);
-	})
-}
+		// works
+		assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));
 
-#[test]
-fn cannot_register_as_candidate_if_keys_not_registered() {
-	new_test_ext().execute_with(|| {
-		// can't 7 because keys not registered.
+		// poor
 		assert_noop!(
-			CollatorSelection::register_as_candidate(RuntimeOrigin::signed(7)),
-			Error::<Test>::ValidatorNotRegistered
+			CollatorSelection::get_license(RuntimeOrigin::signed(33)),
+			BalancesError::<Test>::InsufficientBalance,
 		);
-	})
+	});
 }
 
 #[test]
-fn cannot_register_dupe_candidate() {
+fn cannot_onboard_dupe_candidate() {
 	new_test_ext().execute_with(|| {
 		// can add 3 as candidate
-		assert_ok!(CollatorSelection::register_as_candidate(
-			RuntimeOrigin::signed(3)
-		));
-		let addition = LicenseInfo {
-			who: 3,
-			deposit: 10,
-		};
-		assert_eq!(CollatorSelection::candidates(), vec![addition]);
+		get_license_and_onboard(3);
+		assert_eq!(CollatorSelection::licenses(3), 10);
+		assert_eq!(CollatorSelection::candidates(), vec![3]);
 		assert_eq!(CollatorSelection::last_authored_block(3), 10);
 		assert_eq!(Balances::free_balance(3), 90);
 
 		// but no more
 		assert_noop!(
-			CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)),
+			CollatorSelection::get_license(RuntimeOrigin::signed(3)),
+			Error::<Test>::AlreadyHoldingLicense,
+		);
+		assert_noop!(
+			CollatorSelection::onboard(RuntimeOrigin::signed(3)),
 			Error::<Test>::AlreadyCandidate,
 		);
 	})
 }
 
 #[test]
-fn cannot_register_as_candidate_if_poor() {
+fn becoming_candidate_works() {
 	new_test_ext().execute_with(|| {
+		// given
+		assert_eq!(CollatorSelection::desired_collators(), 5);
+		assert_eq!(CollatorSelection::license_bond(), 10);
+		assert_eq!(CollatorSelection::candidates(), Vec::new());
+		assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
+
+		// take two endowed, non-invulnerables accounts.
 		assert_eq!(Balances::free_balance(&3), 100);
-		assert_eq!(Balances::free_balance(&33), 0);
+		assert_eq!(Balances::free_balance(&4), 100);
 
-		// works
-		assert_ok!(CollatorSelection::register_as_candidate(
-			RuntimeOrigin::signed(3)
-		));
+		get_license_and_onboard(3);
+		get_license_and_onboard(4);
+
+		assert_eq!(Balances::free_balance(&3), 90);
+		assert_eq!(Balances::free_balance(&4), 90);
 
-		// poor
-		assert_noop!(
-			CollatorSelection::register_as_candidate(RuntimeOrigin::signed(33)),
-			BalancesError::<Test>::InsufficientBalance,
-		);
+		assert_eq!(CollatorSelection::candidates().len(), 2);
 	});
 }
 
 #[test]
-fn register_as_candidate_works() {
+fn cannot_become_candidate_if_invulnerable() {
 	new_test_ext().execute_with(|| {
-		// given
-		assert_eq!(CollatorSelection::desired_candidates(), 2);
-		assert_eq!(CollatorSelection::license_bond(), 10);
-		assert_eq!(CollatorSelection::candidates(), Vec::new());
 		assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
 
-		// take two endowed, non-invulnerables accounts.
-		assert_eq!(Balances::free_balance(&3), 100);
-		assert_eq!(Balances::free_balance(&4), 100);
+		// can obtain a license even if is invulnerable.
+		assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(1)));
+		// but cannot onboard
+		assert_noop!(
+			CollatorSelection::onboard(RuntimeOrigin::signed(1)),
+			Error::<Test>::AlreadyInvulnerable,
+		);
 
-		assert_ok!(CollatorSelection::register_as_candidate(
-			RuntimeOrigin::signed(3)
+		// get a license and then become invulnerable.
+		assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));
+		assert_ok!(CollatorSelection::add_invulnerable(
+			RuntimeOrigin::signed(RootAccount::get()),
+			3
 		));
-		assert_ok!(CollatorSelection::register_as_candidate(
-			RuntimeOrigin::signed(4)
+		assert_noop!(
+			CollatorSelection::onboard(RuntimeOrigin::signed(3)),
+			Error::<Test>::AlreadyInvulnerable,
+		);
+	})
+}
+
+#[test]
+fn can_become_invulnerable_if_candidate() {
+	new_test_ext().execute_with(|| {
+		// become a candidate and then become invulnerable.
+		get_license_and_onboard(3);
+		assert_eq!(CollatorSelection::candidates(), vec![3]);
+
+		assert_ok!(CollatorSelection::add_invulnerable(
+			RuntimeOrigin::signed(RootAccount::get()),
+			3
 		));
+		// should exclude from candidates, but not revoke the license
+		assert_eq!(CollatorSelection::candidates(), vec![]);
+		assert_eq!(CollatorSelection::licenses(3), 10);
+		assert_eq!(Balances::free_balance(3), 90);
+	});
+}
 
-		assert_eq!(Balances::free_balance(&3), 90);
-		assert_eq!(Balances::free_balance(&4), 90);
+#[test]
+fn offboard() {
+	new_test_ext().execute_with(|| {
+		// register a candidate.
+		get_license_and_onboard(3);
+		assert_eq!(Balances::free_balance(3), 90);
+
+		// cannot leave if holds license but not yet candidate.
+		assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(4)));
+		assert_noop!(
+			CollatorSelection::offboard(RuntimeOrigin::signed(4)),
+			Error::<Test>::NotCandidate
+		);
+		// cannot leave if does not hold license.
+		assert_noop!(
+			CollatorSelection::offboard(RuntimeOrigin::signed(5)),
+			Error::<Test>::NotCandidate
+		);
 
-		assert_eq!(CollatorSelection::candidates().len(), 2);
+		// bond is returned - only after releasing the license
+		assert_ok!(CollatorSelection::offboard(RuntimeOrigin::signed(3)));
+		assert_eq!(Balances::free_balance(3), 90);
+		assert_eq!(CollatorSelection::last_authored_block(3), 0);
+		assert_ok!(CollatorSelection::release_license(RuntimeOrigin::signed(3)));
+		assert_eq!(Balances::free_balance(3), 100);
 	});
 }
 
 #[test]
-fn leave_intent() {
+fn release_license() {
 	new_test_ext().execute_with(|| {
+		// obtain a license to collate and reserve the bond.
+		assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));
+		assert_eq!(Balances::free_balance(3), 90);
+
+		// release the license and get the bond back.
+		assert_ok!(CollatorSelection::release_license(RuntimeOrigin::signed(3)));
+		assert_eq!(Balances::free_balance(3), 100);
+
 		// register a candidate.
-		assert_ok!(CollatorSelection::register_as_candidate(
-			RuntimeOrigin::signed(3)
-		));
+		get_license_and_onboard(3);
 		assert_eq!(Balances::free_balance(3), 90);
 
-		// register too so can leave above min candidates
-		assert_ok!(CollatorSelection::register_as_candidate(
-			RuntimeOrigin::signed(5)
-		));
-		assert_eq!(Balances::free_balance(5), 90);
+		// can release license even if onboarded.
+		assert_ok!(CollatorSelection::release_license(RuntimeOrigin::signed(3)));
+		assert_eq!(Balances::free_balance(3), 100);
+		assert_eq!(CollatorSelection::candidates(), vec![]);
+	});
+}
+
+#[test]
+fn force_revoke_license() {
+	new_test_ext().execute_with(|| {
+		// obtain a license to collate and reserve the bond.
+		assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));
+		assert_eq!(Balances::free_balance(3), 90);
 
-		// cannot leave if not candidate.
+		// cannot execute the operation as non-root
 		assert_noop!(
-			CollatorSelection::leave_intent(RuntimeOrigin::signed(4)),
-			Error::<Test>::NotCandidate
+			CollatorSelection::force_revoke_license(RuntimeOrigin::signed(3), 3),
+			BadOrigin
 		);
 
-		// bond is returned
-		assert_ok!(CollatorSelection::leave_intent(RuntimeOrigin::signed(3)));
+		// release the license and get the bond back.
+		assert_ok!(CollatorSelection::force_revoke_license(
+			RuntimeOrigin::signed(RootAccount::get()),
+			3
+		));
+		assert_eq!(Balances::free_balance(3), 100);
+
+		// register a candidate.
+		get_license_and_onboard(3);
+		assert_eq!(Balances::free_balance(3), 90);
+
+		// can release license even if onboarded.
+		assert_ok!(CollatorSelection::force_revoke_license(
+			RuntimeOrigin::signed(RootAccount::get()),
+			3
+		));
 		assert_eq!(Balances::free_balance(3), 100);
-		assert_eq!(CollatorSelection::last_authored_block(3), 0);
+		assert_eq!(CollatorSelection::candidates(), vec![]);
 	});
 }
 
@@ -325,18 +415,11 @@
 
 		// 4 is the default author.
 		assert_eq!(Balances::free_balance(4), 100);
-		assert_ok!(CollatorSelection::register_as_candidate(
-			RuntimeOrigin::signed(4)
-		));
+		get_license_and_onboard(4);
 		// triggers `note_author`
 		Authorship::on_initialize(1);
 
-		let collator = LicenseInfo {
-			who: 4,
-			deposit: 10,
-		};
-
-		assert_eq!(CollatorSelection::candidates(), vec![collator]);
+		assert_eq!(CollatorSelection::candidates(), vec![4]);
 		assert_eq!(CollatorSelection::last_authored_block(4), 0);
 
 		// half of the pot goes to the collator who's the author (4 in tests).
@@ -355,18 +438,11 @@
 		Balances::make_free_balance_be(&CollatorSelection::account_id(), 5);
 		// 4 is the default author.
 		assert_eq!(Balances::free_balance(4), 100);
-		assert_ok!(CollatorSelection::register_as_candidate(
-			RuntimeOrigin::signed(4)
-		));
+		get_license_and_onboard(4);
 		// triggers `note_author`
 		Authorship::on_initialize(1);
 
-		let collator = LicenseInfo {
-			who: 4,
-			deposit: 10,
-		};
-
-		assert_eq!(CollatorSelection::candidates(), vec![collator]);
+		assert_eq!(CollatorSelection::candidates(), vec![4]);
 		assert_eq!(CollatorSelection::last_authored_block(4), 0);
 		// Nothing received
 		assert_eq!(Balances::free_balance(4), 90);
@@ -389,9 +465,7 @@
 		assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
 
 		// add a new collator
-		assert_ok!(CollatorSelection::register_as_candidate(
-			RuntimeOrigin::signed(3)
-		));
+		get_license_and_onboard(5);
 
 		// session won't see this.
 		assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
@@ -410,7 +484,7 @@
 		initialize_to_block(20);
 		assert_eq!(SessionChangeBlock::get(), 20);
 		// changed are now reflected to session handlers.
-		assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3]);
+		assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 5]);
 	});
 }
 
@@ -418,64 +492,28 @@
 fn kick_mechanism() {
 	new_test_ext().execute_with(|| {
 		// add a new collator
-		assert_ok!(CollatorSelection::register_as_candidate(
-			RuntimeOrigin::signed(3)
-		));
-		assert_ok!(CollatorSelection::register_as_candidate(
-			RuntimeOrigin::signed(4)
-		));
+		get_license_and_onboard(3);
+		get_license_and_onboard(4);
+
 		initialize_to_block(10);
 		assert_eq!(CollatorSelection::candidates().len(), 2);
+
 		initialize_to_block(20);
 		assert_eq!(SessionChangeBlock::get(), 20);
 		// 4 authored this block, gets to stay 3 was kicked
 		assert_eq!(CollatorSelection::candidates().len(), 1);
 		// 3 will be kicked after 1 session delay
 		assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 4]);
-		let collator = LicenseInfo {
-			who: 4,
-			deposit: 10,
-		};
-		assert_eq!(CollatorSelection::candidates(), vec![collator]);
-		assert_eq!(CollatorSelection::kick_threshold(), 1);
+
+		assert_eq!(CollatorSelection::candidates(), vec![4]);
+		assert_eq!(CollatorSelection::kick_threshold(), 10);
 		assert_eq!(CollatorSelection::last_authored_block(4), 20);
+
 		initialize_to_block(30);
 		// 3 gets kicked after 1 session delay
 		assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 4]);
-		// kicked collator gets funds back
-		assert_eq!(Balances::free_balance(3), 100);
-	});
-}
-
-#[test]
-fn should_not_kick_mechanism_too_few() {
-	new_test_ext().execute_with(|| {
-		// add a new collator
-		assert_ok!(CollatorSelection::register_as_candidate(
-			RuntimeOrigin::signed(3)
-		));
-		assert_ok!(CollatorSelection::register_as_candidate(
-			RuntimeOrigin::signed(5)
-		));
-		initialize_to_block(10);
-		assert_eq!(CollatorSelection::candidates().len(), 2);
-		initialize_to_block(20);
-		assert_eq!(SessionChangeBlock::get(), 20);
-		// 4 authored this block, 5 gets to stay too few 3 was kicked
-		assert_eq!(CollatorSelection::candidates().len(), 1);
-		// 3 will be kicked after 1 session delay
-		assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 5]);
-		let collator = LicenseInfo {
-			who: 5,
-			deposit: 10,
-		};
-		assert_eq!(CollatorSelection::candidates(), vec![collator]);
-		assert_eq!(CollatorSelection::last_authored_block(4), 20);
-		initialize_to_block(30);
-		// 3 gets kicked after 1 session delay
-		assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 5]);
-		// kicked collator gets funds back
-		assert_eq!(Balances::free_balance(3), 100);
+		// kicked collator gets their funds slashed, the deposit going to treasury
+		assert_eq!(Balances::free_balance(3), 90);
 	});
 }
 
@@ -489,9 +527,9 @@
 	let invulnerables = vec![1, 1];
 
 	let collator_selection = collator_selection::GenesisConfig::<Test> {
-		desired_candidates: 2,
+		desired_collators: 5,
 		license_bond: 10,
-		kick_threshold: 1,
+		kick_threshold: 10,
 		invulnerables,
 	};
 	// collator selection must be initialized before session.
modifiedpallets/collator-selection/src/weights.rsdiffbeforeafterboth
--- a/pallets/collator-selection/src/weights.rs
+++ b/pallets/collator-selection/src/weights.rs
@@ -45,7 +45,7 @@
 // The weight info trait for `pallet_collator_selection`.
 pub trait WeightInfo {
 	fn set_invulnerables(_b: u32) -> Weight;
-	fn set_desired_candidates() -> Weight;
+	fn set_desired_collators() -> Weight;
 	fn set_license_bond() -> Weight;
 	fn register_as_candidate(_c: u32) -> Weight;
 	fn leave_intent(_c: u32) -> Weight;
@@ -62,7 +62,7 @@
 			.saturating_add(Weight::from_ref_time(68_000 as u64).saturating_mul(b as u64))
 			.saturating_add(T::DbWeight::get().writes(1 as u64))
 	}
-	fn set_desired_candidates() -> Weight {
+	fn set_desired_collators() -> Weight {
 		Weight::from_ref_time(16_363_000 as u64).saturating_add(T::DbWeight::get().writes(1 as u64))
 	}
 	fn set_license_bond() -> Weight {
@@ -108,7 +108,7 @@
 			.saturating_add(Weight::from_ref_time(68_000 as u64).saturating_mul(b as u64))
 			.saturating_add(RocksDbWeight::get().writes(1 as u64))
 	}
-	fn set_desired_candidates() -> Weight {
+	fn set_desired_collators() -> Weight {
 		Weight::from_ref_time(16_363_000 as u64)
 			.saturating_add(RocksDbWeight::get().writes(1 as u64))
 	}
modifiedprimitives/common/src/constants.rsdiffbeforeafterboth
--- a/primitives/common/src/constants.rs
+++ b/primitives/common/src/constants.rs
@@ -45,9 +45,9 @@
 /// Minimum balance required to create or keep an account open.
 pub const EXISTENTIAL_DEPOSIT: u128 = 0;
 /// Amount of Balance reserved for candidate registration.
-pub const GENESIS_LICENSE_BOND: u128 = EXISTENTIAL_DEPOSIT;
+pub const GENESIS_LICENSE_BOND: u128 = 1_000_000_000_000 * UNIQUE;
 /// How long a periodic session lasts in blocks.
-pub const SESSION_LENGTH: BlockNumber = MINUTES;
+pub const SESSION_LENGTH: BlockNumber = HOURS;
 
 // Targeting 0.1 UNQ per transfer
 pub const WEIGHT_TO_FEE_COEFF: u32 = /*<weight2fee>*/175_199_920/*</weight2fee>*/;
modifiedruntime/common/config/pallets/collator_selection.rsdiffbeforeafterboth
--- a/runtime/common/config/pallets/collator_selection.rs
+++ b/runtime/common/config/pallets/collator_selection.rs
@@ -55,9 +55,7 @@
 
 parameter_types! {
 	pub const PotId: PalletId = PalletId(*b"PotStake");
-	pub const MaxCandidates: u32 = 30; // todo:collator 30 collator slots - 3 planned invulnerables
-	pub const MinCandidates: u32 = 1;
-	pub const MaxInvulnerables: u32 = 30;
+	pub const MaxCollators: u32 = 10;
 	pub const SlashRatio: Perbill = Perbill::from_percent(100);
 }
 
@@ -68,9 +66,7 @@
 	type UpdateOrigin = EnsureRoot<AccountId>;
 	type TreasuryAccountId = TreasuryAccountId;
 	type PotId = PotId;
-	type MaxCandidates = MaxCandidates;
-	type MinCandidates = MinCandidates;
-	type MaxInvulnerables = MaxInvulnerables;
+	type MaxCollators = MaxCollators;
 	// todo:collator kick threshold should be in storage and configured only by root -- or rather UpdateOrigin
 	type SlashRatio = SlashRatio;
 	type ValidatorId = <Self as frame_system::Config>::AccountId;
modifiedruntime/common/mod.rsdiffbeforeafterboth
--- a/runtime/common/mod.rs
+++ b/runtime/common/mod.rs
@@ -192,7 +192,7 @@
 			};
 			use pallet_session::SessionManager;
 			use up_common::constants::GENESIS_LICENSE_BOND;
-			use crate::config::pallets::collator_selection::MaxInvulnerables;
+			use crate::config::pallets::collator_selection::MaxCollators;
 
 			let mut weight = <Runtime as frame_system::Config>::DbWeight::get().reads(1);
 
@@ -231,17 +231,17 @@
 					})
 					.collect::<Vec<_>>();
 
-				let bounded_invulnerables = BoundedVec::<_, MaxInvulnerables>::try_from(
+				let bounded_invulnerables = BoundedVec::<_, MaxCollators>::try_from(
 					invulnerables
 						.iter()
 						.cloned()
 						.map(|(acc, _)| acc)
 						.collect::<Vec<_>>(),
 				)
-				.expect("Existing collators/invulnerables are more than MaxInvulnerables");
+				.expect("Existing collators/invulnerables are more than MaxCollators");
 
 				<pallet_collator_selection::Invulnerables<Runtime>>::put(bounded_invulnerables);
-				<pallet_collator_selection::DesiredCandidates<Runtime>>::put(0);
+				<pallet_collator_selection::DesiredCollators<Runtime>>::put(MaxCollators::get());
 				<pallet_collator_selection::LicenseBond<Runtime>>::put(GENESIS_LICENSE_BOND);
 
 				let keys = invulnerables
modifiedruntime/common/tests/mod.rsdiffbeforeafterboth
--- a/runtime/common/tests/mod.rs
+++ b/runtime/common/tests/mod.rs
@@ -18,7 +18,7 @@
 use sp_core::{Public, Pair};
 use sp_std::vec;
 use up_common::types::AuraId;
-use crate::{GenesisConfig, ParachainInfoConfig, AuraConfig};
+use crate::{GenesisConfig, ParachainInfoConfig};
 
 pub mod xcm;
 
@@ -28,7 +28,61 @@
 		.public()
 }
 
+#[cfg(feature = "collator-selection")]
+fn new_test_ext(para_id: u32) -> sp_io::TestExternalities {
+	use sp_core::{sr25519};
+	use sp_runtime::traits::{IdentifyAccount, Verify};
+	use crate::{AccountId, Signature, SessionKeys, CollatorSelectionConfig, SessionConfig};
+
+	type AccountPublic = <Signature as Verify>::Signer;
+
+	fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId
+	where
+		AccountPublic: From<<TPublic::Pair as Pair>::Public>,
+	{
+		AccountPublic::from(get_from_seed::<TPublic>(seed)).into_account()
+	}
+
+	let accounts = vec!["Alice", "Bob"];
+	let keys = accounts
+		.iter()
+		.map(|&acc| {
+			let account_id = get_account_id_from_seed::<sr25519::Public>(acc);
+			(
+				account_id.clone(),
+				account_id,
+				SessionKeys {
+					aura: get_from_seed::<AuraId>(acc),
+				},
+			)
+		})
+		.collect::<Vec<_>>();
+	let invulnerables = accounts
+		.iter()
+		.map(|acc| get_account_id_from_seed::<sr25519::Public>(acc))
+		.collect::<Vec<_>>();
+
+	let cfg = GenesisConfig {
+		collator_selection: CollatorSelectionConfig {
+			desired_collators: 2,
+			license_bond: 10,
+			kick_threshold: 10,
+			invulnerables,
+		},
+		session: SessionConfig { keys },
+		parachain_info: ParachainInfoConfig {
+			parachain_id: para_id.into(),
+		},
+		..GenesisConfig::default()
+	};
+
+	cfg.build_storage().unwrap().into()
+}
+
+#[cfg(not(feature = "collator-selection"))]
 fn new_test_ext(para_id: u32) -> sp_io::TestExternalities {
+	use crate::AuraConfig;
+
 	let cfg = GenesisConfig {
 		aura: AuraConfig {
 			authorities: vec![