1pub mod migration;
26
27use crate::traits::{LeaseError, Leaser, Registrar};
28use alloc::vec::Vec;
29use frame_support::{
30 pallet_prelude::*,
31 traits::{Currency, ReservableCurrency},
32 weights::Weight,
33};
34use frame_system::pallet_prelude::*;
35pub use pallet::*;
36use polkadot_primitives::Id as ParaId;
37use sp_runtime::traits::{CheckedConversion, CheckedSub, Saturating, Zero};
38
39type BalanceOf<T> =
40 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
41type LeasePeriodOf<T> = BlockNumberFor<T>;
42
43pub trait WeightInfo {
44 fn force_lease() -> Weight;
45 fn manage_lease_period_start(c: u32, t: u32) -> Weight;
46 fn clear_all_leases() -> Weight;
47 fn trigger_onboard() -> Weight;
48}
49
50pub struct TestWeightInfo;
51impl WeightInfo for TestWeightInfo {
52 fn force_lease() -> Weight {
53 Weight::zero()
54 }
55 fn manage_lease_period_start(_c: u32, _t: u32) -> Weight {
56 Weight::zero()
57 }
58 fn clear_all_leases() -> Weight {
59 Weight::zero()
60 }
61 fn trigger_onboard() -> Weight {
62 Weight::zero()
63 }
64}
65
66#[frame_support::pallet]
67pub mod pallet {
68 use super::*;
69
70 #[pallet::pallet]
71 #[pallet::without_storage_info]
72 pub struct Pallet<T>(_);
73
74 #[pallet::config]
75 pub trait Config: frame_system::Config {
76 #[allow(deprecated)]
78 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
79
80 type Currency: ReservableCurrency<Self::AccountId>;
82
83 type Registrar: Registrar<AccountId = Self::AccountId>;
85
86 #[pallet::constant]
88 type LeasePeriod: Get<BlockNumberFor<Self>>;
89
90 #[pallet::constant]
92 type LeaseOffset: Get<BlockNumberFor<Self>>;
93
94 type ForceOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;
96
97 type WeightInfo: WeightInfo;
99 }
100
101 #[pallet::storage]
118 pub type Leases<T: Config> =
119 StorageMap<_, Twox64Concat, ParaId, Vec<Option<(T::AccountId, BalanceOf<T>)>>, ValueQuery>;
120
121 #[pallet::event]
122 #[pallet::generate_deposit(pub(super) fn deposit_event)]
123 pub enum Event<T: Config> {
124 NewLeasePeriod { lease_period: LeasePeriodOf<T> },
126 Leased {
130 para_id: ParaId,
131 leaser: T::AccountId,
132 period_begin: LeasePeriodOf<T>,
133 period_count: LeasePeriodOf<T>,
134 extra_reserved: BalanceOf<T>,
135 total_amount: BalanceOf<T>,
136 },
137 }
138
139 #[pallet::error]
140 pub enum Error<T> {
141 ParaNotOnboarding,
143 LeaseError,
145 }
146
147 #[pallet::hooks]
148 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
149 fn on_initialize(n: BlockNumberFor<T>) -> Weight {
150 if let Some((lease_period, first_block)) = Self::lease_period_index(n) {
151 if first_block {
153 return Self::manage_lease_period_start(lease_period);
154 }
155 }
156
157 Weight::zero()
159 }
160 }
161
162 #[pallet::call]
163 impl<T: Config> Pallet<T> {
164 #[pallet::call_index(0)]
169 #[pallet::weight(T::WeightInfo::force_lease())]
170 pub fn force_lease(
171 origin: OriginFor<T>,
172 para: ParaId,
173 leaser: T::AccountId,
174 amount: BalanceOf<T>,
175 period_begin: LeasePeriodOf<T>,
176 period_count: LeasePeriodOf<T>,
177 ) -> DispatchResult {
178 T::ForceOrigin::ensure_origin(origin)?;
179 Self::lease_out(para, &leaser, amount, period_begin, period_count)
180 .map_err(|_| Error::<T>::LeaseError)?;
181 Ok(())
182 }
183
184 #[pallet::call_index(1)]
188 #[pallet::weight(T::WeightInfo::clear_all_leases())]
189 pub fn clear_all_leases(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
190 T::ForceOrigin::ensure_origin(origin)?;
191 let deposits = Self::all_deposits_held(para);
192
193 for (who, deposit) in deposits {
195 let err_amount = T::Currency::unreserve(&who, deposit);
196 debug_assert!(err_amount.is_zero());
197 }
198
199 Leases::<T>::remove(para);
200 Ok(())
201 }
202
203 #[pallet::call_index(2)]
211 #[pallet::weight(T::WeightInfo::trigger_onboard())]
212 pub fn trigger_onboard(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
213 ensure_signed(origin)?;
214 let leases = Leases::<T>::get(para);
215 match leases.first() {
216 Some(Some(_lease_info)) => T::Registrar::make_parachain(para)?,
219 Some(None) | None => return Err(Error::<T>::ParaNotOnboarding.into()),
221 };
222 Ok(())
223 }
224 }
225}
226
227impl<T: Config> Pallet<T> {
228 fn manage_lease_period_start(lease_period_index: LeasePeriodOf<T>) -> Weight {
233 Self::deposit_event(Event::<T>::NewLeasePeriod { lease_period: lease_period_index });
234
235 let old_parachains = T::Registrar::parachains();
236
237 let mut parachains = Vec::new();
239 for (para, mut lease_periods) in Leases::<T>::iter() {
240 if lease_periods.is_empty() {
241 continue;
242 }
243 if lease_periods.len() == 1 {
246 if let Some((who, value)) = &lease_periods[0] {
252 T::Currency::unreserve(&who, *value);
253 }
254
255 Leases::<T>::remove(para);
257 } else {
258 let maybe_ended_lease = lease_periods.remove(0);
263
264 Leases::<T>::insert(para, &lease_periods);
265
266 if let Some(ended_lease) = maybe_ended_lease {
268 let now_held = Self::deposit_held(para, &ended_lease.0);
271
272 if let Some(rebate) = ended_lease.1.checked_sub(&now_held) {
275 T::Currency::unreserve(&ended_lease.0, rebate);
276 }
277 }
278
279 if lease_periods[0].is_some() {
281 parachains.push(para);
282 }
283 }
284 }
285 parachains.sort();
286
287 for para in parachains.iter() {
288 if old_parachains.binary_search(para).is_err() {
289 let res = T::Registrar::make_parachain(*para);
291 debug_assert!(res.is_ok());
292 }
293 }
294
295 for para in old_parachains.iter() {
296 if parachains.binary_search(para).is_err() {
297 let res = T::Registrar::make_parathread(*para);
299 debug_assert!(res.is_ok());
300 }
301 }
302
303 T::WeightInfo::manage_lease_period_start(
304 old_parachains.len() as u32,
305 parachains.len() as u32,
306 )
307 }
308
309 fn all_deposits_held(para: ParaId) -> Vec<(T::AccountId, BalanceOf<T>)> {
313 let mut tracker = alloc::collections::btree_map::BTreeMap::new();
314 Leases::<T>::get(para).into_iter().for_each(|lease| match lease {
315 Some((who, amount)) => match tracker.get(&who) {
316 Some(prev_amount) => {
317 if amount > *prev_amount {
318 tracker.insert(who, amount);
319 }
320 },
321 None => {
322 tracker.insert(who, amount);
323 },
324 },
325 None => {},
326 });
327
328 tracker.into_iter().collect()
329 }
330}
331
332impl<T: Config> crate::traits::OnSwap for Pallet<T> {
333 fn on_swap(one: ParaId, other: ParaId) {
334 Leases::<T>::mutate(one, |x| Leases::<T>::mutate(other, |y| core::mem::swap(x, y)))
335 }
336}
337
338impl<T: Config> Leaser<BlockNumberFor<T>> for Pallet<T> {
339 type AccountId = T::AccountId;
340 type LeasePeriod = BlockNumberFor<T>;
341 type Currency = T::Currency;
342
343 fn lease_out(
344 para: ParaId,
345 leaser: &Self::AccountId,
346 amount: <Self::Currency as Currency<Self::AccountId>>::Balance,
347 period_begin: Self::LeasePeriod,
348 period_count: Self::LeasePeriod,
349 ) -> Result<(), LeaseError> {
350 let now = frame_system::Pallet::<T>::block_number();
351 let (current_lease_period, _) =
352 Self::lease_period_index(now).ok_or(LeaseError::NoLeasePeriod)?;
353 let offset = period_begin
356 .checked_sub(¤t_lease_period)
357 .and_then(|x| x.checked_into::<usize>())
358 .ok_or(LeaseError::AlreadyEnded)?;
359
360 Leases::<T>::try_mutate(para, |d| {
368 if d.len() < offset {
370 d.resize_with(offset, || None);
371 }
372 let period_count_usize =
373 period_count.checked_into::<usize>().ok_or(LeaseError::AlreadyEnded)?;
374 for i in offset..(offset + period_count_usize) {
376 if d.len() > i {
377 if d[i] == None {
380 d[i] = Some((leaser.clone(), amount));
381 } else {
382 return Err(LeaseError::AlreadyLeased);
387 }
388 } else if d.len() == i {
389 d.push(Some((leaser.clone(), amount)));
391 } else {
392 }
395 }
396
397 let maybe_additional = amount.checked_sub(&Self::deposit_held(para, &leaser));
400 if let Some(ref additional) = maybe_additional {
401 T::Currency::reserve(&leaser, *additional)
402 .map_err(|_| LeaseError::ReserveFailed)?;
403 }
404
405 let reserved = maybe_additional.unwrap_or_default();
406
407 if current_lease_period == period_begin {
411 let _ = T::Registrar::make_parachain(para);
413 }
414
415 Self::deposit_event(Event::<T>::Leased {
416 para_id: para,
417 leaser: leaser.clone(),
418 period_begin,
419 period_count,
420 extra_reserved: reserved,
421 total_amount: amount,
422 });
423
424 Ok(())
425 })
426 }
427
428 fn deposit_held(
429 para: ParaId,
430 leaser: &Self::AccountId,
431 ) -> <Self::Currency as Currency<Self::AccountId>>::Balance {
432 Leases::<T>::get(para)
433 .into_iter()
434 .map(|lease| match lease {
435 Some((who, amount)) => {
436 if &who == leaser {
437 amount
438 } else {
439 Zero::zero()
440 }
441 },
442 None => Zero::zero(),
443 })
444 .max()
445 .unwrap_or_else(Zero::zero)
446 }
447
448 #[cfg(any(feature = "runtime-benchmarks", test))]
449 fn lease_period_length() -> (BlockNumberFor<T>, BlockNumberFor<T>) {
450 (T::LeasePeriod::get(), T::LeaseOffset::get())
451 }
452
453 fn lease_period_index(b: BlockNumberFor<T>) -> Option<(Self::LeasePeriod, bool)> {
454 let offset_block_now = b.checked_sub(&T::LeaseOffset::get())?;
456 let lease_period = offset_block_now / T::LeasePeriod::get();
457 let at_begin = (offset_block_now % T::LeasePeriod::get()).is_zero();
458
459 Some((lease_period, at_begin))
460 }
461
462 fn already_leased(
463 para_id: ParaId,
464 first_period: Self::LeasePeriod,
465 last_period: Self::LeasePeriod,
466 ) -> bool {
467 let now = frame_system::Pallet::<T>::block_number();
468 let (current_lease_period, _) = match Self::lease_period_index(now) {
469 Some(clp) => clp,
470 None => return true,
471 };
472
473 let start_period = first_period.max(current_lease_period);
475 let offset = match (start_period - current_lease_period).checked_into::<usize>() {
478 Some(offset) => offset,
479 None => return true,
480 };
481
482 let period_count = match last_period.saturating_sub(start_period).checked_into::<usize>() {
484 Some(period_count) => period_count,
485 None => return true,
486 };
487
488 let leases = Leases::<T>::get(para_id);
491 for slot in offset..=offset + period_count {
492 if let Some(Some(_)) = leases.get(slot) {
493 return true;
495 }
496 }
497
498 false
500 }
501}
502
503#[cfg(test)]
505mod tests {
506 use super::*;
507
508 use crate::{mock::TestRegistrar, slots};
509 use frame_support::{assert_noop, assert_ok, derive_impl, parameter_types};
510 use frame_system::EnsureRoot;
511 use pallet_balances;
512 use polkadot_primitives::BlockNumber;
513 use polkadot_primitives_test_helpers::{dummy_head_data, dummy_validation_code};
514 use sp_core::H256;
515 use sp_runtime::{
516 traits::{BlakeTwo256, IdentityLookup},
517 BuildStorage,
518 };
519
520 type Block = frame_system::mocking::MockBlockU32<Test>;
521
522 frame_support::construct_runtime!(
523 pub enum Test
524 {
525 System: frame_system,
526 Balances: pallet_balances,
527 Slots: slots,
528 }
529 );
530
531 #[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
532 impl frame_system::Config for Test {
533 type BaseCallFilter = frame_support::traits::Everything;
534 type BlockWeights = ();
535 type BlockLength = ();
536 type RuntimeOrigin = RuntimeOrigin;
537 type RuntimeCall = RuntimeCall;
538 type Nonce = u64;
539 type Hash = H256;
540 type Hashing = BlakeTwo256;
541 type AccountId = u64;
542 type Lookup = IdentityLookup<Self::AccountId>;
543 type Block = Block;
544 type RuntimeEvent = RuntimeEvent;
545 type DbWeight = ();
546 type Version = ();
547 type PalletInfo = PalletInfo;
548 type AccountData = pallet_balances::AccountData<u64>;
549 type OnNewAccount = ();
550 type OnKilledAccount = ();
551 type SystemWeightInfo = ();
552 type SS58Prefix = ();
553 type OnSetCode = ();
554 type MaxConsumers = frame_support::traits::ConstU32<16>;
555 }
556
557 #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
558 impl pallet_balances::Config for Test {
559 type AccountStore = System;
560 }
561
562 parameter_types! {
563 pub const LeasePeriod: BlockNumber = 10;
564 pub static LeaseOffset: BlockNumber = 0;
565 pub const ParaDeposit: u64 = 1;
566 }
567
568 impl Config for Test {
569 type RuntimeEvent = RuntimeEvent;
570 type Currency = Balances;
571 type Registrar = TestRegistrar<Test>;
572 type LeasePeriod = LeasePeriod;
573 type LeaseOffset = LeaseOffset;
574 type ForceOrigin = EnsureRoot<Self::AccountId>;
575 type WeightInfo = crate::slots::TestWeightInfo;
576 }
577
578 pub fn new_test_ext() -> sp_io::TestExternalities {
581 let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
582 pallet_balances::GenesisConfig::<Test> {
583 balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)],
584 ..Default::default()
585 }
586 .assimilate_storage(&mut t)
587 .unwrap();
588 t.into()
589 }
590
591 #[test]
592 fn basic_setup_works() {
593 new_test_ext().execute_with(|| {
594 System::run_to_block::<AllPalletsWithSystem>(1);
595 assert_eq!(Slots::lease_period_length(), (10, 0));
596 let now = System::block_number();
597 assert_eq!(Slots::lease_period_index(now).unwrap().0, 0);
598 assert_eq!(Slots::deposit_held(1.into(), &1), 0);
599
600 System::run_to_block::<AllPalletsWithSystem>(10);
601 let now = System::block_number();
602 assert_eq!(Slots::lease_period_index(now).unwrap().0, 1);
603 });
604 }
605
606 #[test]
607 fn lease_lifecycle_works() {
608 new_test_ext().execute_with(|| {
609 System::run_to_block::<AllPalletsWithSystem>(1);
610
611 assert_ok!(TestRegistrar::<Test>::register(
612 1,
613 ParaId::from(1_u32),
614 dummy_head_data(),
615 dummy_validation_code()
616 ));
617
618 assert_ok!(Slots::lease_out(1.into(), &1, 1, 1, 1));
619 assert_eq!(Slots::deposit_held(1.into(), &1), 1);
620 assert_eq!(Balances::reserved_balance(1), 1);
621
622 System::run_to_block::<AllPalletsWithSystem>(19);
623 assert_eq!(Slots::deposit_held(1.into(), &1), 1);
624 assert_eq!(Balances::reserved_balance(1), 1);
625
626 System::run_to_block::<AllPalletsWithSystem>(20);
627 assert_eq!(Slots::deposit_held(1.into(), &1), 0);
628 assert_eq!(Balances::reserved_balance(1), 0);
629
630 assert_eq!(
631 TestRegistrar::<Test>::operations(),
632 vec![(1.into(), 10, true), (1.into(), 20, false),]
633 );
634 });
635 }
636
637 #[test]
638 fn lease_interrupted_lifecycle_works() {
639 new_test_ext().execute_with(|| {
640 System::run_to_block::<AllPalletsWithSystem>(1);
641
642 assert_ok!(TestRegistrar::<Test>::register(
643 1,
644 ParaId::from(1_u32),
645 dummy_head_data(),
646 dummy_validation_code()
647 ));
648
649 assert_ok!(Slots::lease_out(1.into(), &1, 6, 1, 1));
650 assert_ok!(Slots::lease_out(1.into(), &1, 4, 3, 1));
651
652 System::run_to_block::<AllPalletsWithSystem>(19);
653 assert_eq!(Slots::deposit_held(1.into(), &1), 6);
654 assert_eq!(Balances::reserved_balance(1), 6);
655
656 System::run_to_block::<AllPalletsWithSystem>(20);
657 assert_eq!(Slots::deposit_held(1.into(), &1), 4);
658 assert_eq!(Balances::reserved_balance(1), 4);
659
660 System::run_to_block::<AllPalletsWithSystem>(39);
661 assert_eq!(Slots::deposit_held(1.into(), &1), 4);
662 assert_eq!(Balances::reserved_balance(1), 4);
663
664 System::run_to_block::<AllPalletsWithSystem>(40);
665 assert_eq!(Slots::deposit_held(1.into(), &1), 0);
666 assert_eq!(Balances::reserved_balance(1), 0);
667
668 assert_eq!(
669 TestRegistrar::<Test>::operations(),
670 vec![
671 (1.into(), 10, true),
672 (1.into(), 20, false),
673 (1.into(), 30, true),
674 (1.into(), 40, false),
675 ]
676 );
677 });
678 }
679
680 #[test]
681 fn lease_relayed_lifecycle_works() {
682 new_test_ext().execute_with(|| {
683 System::run_to_block::<AllPalletsWithSystem>(1);
684
685 assert_ok!(TestRegistrar::<Test>::register(
686 1,
687 ParaId::from(1_u32),
688 dummy_head_data(),
689 dummy_validation_code()
690 ));
691
692 assert!(Slots::lease_out(1.into(), &1, 6, 1, 1).is_ok());
693 assert!(Slots::lease_out(1.into(), &2, 4, 2, 1).is_ok());
694 assert_eq!(Slots::deposit_held(1.into(), &1), 6);
695 assert_eq!(Balances::reserved_balance(1), 6);
696 assert_eq!(Slots::deposit_held(1.into(), &2), 4);
697 assert_eq!(Balances::reserved_balance(2), 4);
698
699 System::run_to_block::<AllPalletsWithSystem>(19);
700 assert_eq!(Slots::deposit_held(1.into(), &1), 6);
701 assert_eq!(Balances::reserved_balance(1), 6);
702 assert_eq!(Slots::deposit_held(1.into(), &2), 4);
703 assert_eq!(Balances::reserved_balance(2), 4);
704
705 System::run_to_block::<AllPalletsWithSystem>(20);
706 assert_eq!(Slots::deposit_held(1.into(), &1), 0);
707 assert_eq!(Balances::reserved_balance(1), 0);
708 assert_eq!(Slots::deposit_held(1.into(), &2), 4);
709 assert_eq!(Balances::reserved_balance(2), 4);
710
711 System::run_to_block::<AllPalletsWithSystem>(29);
712 assert_eq!(Slots::deposit_held(1.into(), &1), 0);
713 assert_eq!(Balances::reserved_balance(1), 0);
714 assert_eq!(Slots::deposit_held(1.into(), &2), 4);
715 assert_eq!(Balances::reserved_balance(2), 4);
716
717 System::run_to_block::<AllPalletsWithSystem>(30);
718 assert_eq!(Slots::deposit_held(1.into(), &1), 0);
719 assert_eq!(Balances::reserved_balance(1), 0);
720 assert_eq!(Slots::deposit_held(1.into(), &2), 0);
721 assert_eq!(Balances::reserved_balance(2), 0);
722
723 assert_eq!(
724 TestRegistrar::<Test>::operations(),
725 vec![(1.into(), 10, true), (1.into(), 30, false),]
726 );
727 });
728 }
729
730 #[test]
731 fn lease_deposit_increase_works() {
732 new_test_ext().execute_with(|| {
733 System::run_to_block::<AllPalletsWithSystem>(1);
734
735 assert_ok!(TestRegistrar::<Test>::register(
736 1,
737 ParaId::from(1_u32),
738 dummy_head_data(),
739 dummy_validation_code()
740 ));
741
742 assert!(Slots::lease_out(1.into(), &1, 4, 1, 1).is_ok());
743 assert_eq!(Slots::deposit_held(1.into(), &1), 4);
744 assert_eq!(Balances::reserved_balance(1), 4);
745
746 assert!(Slots::lease_out(1.into(), &1, 6, 2, 1).is_ok());
747 assert_eq!(Slots::deposit_held(1.into(), &1), 6);
748 assert_eq!(Balances::reserved_balance(1), 6);
749
750 System::run_to_block::<AllPalletsWithSystem>(29);
751 assert_eq!(Slots::deposit_held(1.into(), &1), 6);
752 assert_eq!(Balances::reserved_balance(1), 6);
753
754 System::run_to_block::<AllPalletsWithSystem>(30);
755 assert_eq!(Slots::deposit_held(1.into(), &1), 0);
756 assert_eq!(Balances::reserved_balance(1), 0);
757
758 assert_eq!(
759 TestRegistrar::<Test>::operations(),
760 vec![(1.into(), 10, true), (1.into(), 30, false),]
761 );
762 });
763 }
764
765 #[test]
766 fn lease_deposit_decrease_works() {
767 new_test_ext().execute_with(|| {
768 System::run_to_block::<AllPalletsWithSystem>(1);
769
770 assert_ok!(TestRegistrar::<Test>::register(
771 1,
772 ParaId::from(1_u32),
773 dummy_head_data(),
774 dummy_validation_code()
775 ));
776
777 assert!(Slots::lease_out(1.into(), &1, 6, 1, 1).is_ok());
778 assert_eq!(Slots::deposit_held(1.into(), &1), 6);
779 assert_eq!(Balances::reserved_balance(1), 6);
780
781 assert!(Slots::lease_out(1.into(), &1, 4, 2, 1).is_ok());
782 assert_eq!(Slots::deposit_held(1.into(), &1), 6);
783 assert_eq!(Balances::reserved_balance(1), 6);
784
785 System::run_to_block::<AllPalletsWithSystem>(19);
786 assert_eq!(Slots::deposit_held(1.into(), &1), 6);
787 assert_eq!(Balances::reserved_balance(1), 6);
788
789 System::run_to_block::<AllPalletsWithSystem>(20);
790 assert_eq!(Slots::deposit_held(1.into(), &1), 4);
791 assert_eq!(Balances::reserved_balance(1), 4);
792
793 System::run_to_block::<AllPalletsWithSystem>(29);
794 assert_eq!(Slots::deposit_held(1.into(), &1), 4);
795 assert_eq!(Balances::reserved_balance(1), 4);
796
797 System::run_to_block::<AllPalletsWithSystem>(30);
798 assert_eq!(Slots::deposit_held(1.into(), &1), 0);
799 assert_eq!(Balances::reserved_balance(1), 0);
800
801 assert_eq!(
802 TestRegistrar::<Test>::operations(),
803 vec![(1.into(), 10, true), (1.into(), 30, false),]
804 );
805 });
806 }
807
808 #[test]
809 fn clear_all_leases_works() {
810 new_test_ext().execute_with(|| {
811 System::run_to_block::<AllPalletsWithSystem>(1);
812
813 assert_ok!(TestRegistrar::<Test>::register(
814 1,
815 ParaId::from(1_u32),
816 dummy_head_data(),
817 dummy_validation_code()
818 ));
819
820 let max_num = 5u32;
821
822 for i in 1u32..=max_num {
824 let j: u64 = i.into();
825 assert_ok!(Slots::lease_out(1.into(), &j, j * 10 - 1, i * i, i));
826 assert_eq!(Slots::deposit_held(1.into(), &j), j * 10 - 1);
827 assert_eq!(Balances::reserved_balance(j), j * 10 - 1);
828 }
829
830 assert_ok!(Slots::clear_all_leases(RuntimeOrigin::root(), 1.into()));
831
832 for i in 1u32..=max_num {
834 let j: u64 = i.into();
835 assert_eq!(Slots::deposit_held(1.into(), &j), 0);
836 assert_eq!(Balances::reserved_balance(j), 0);
837 }
838
839 assert!(Leases::<Test>::get(ParaId::from(1_u32)).is_empty());
841 });
842 }
843
844 #[test]
845 fn lease_out_current_lease_period() {
846 new_test_ext().execute_with(|| {
847 System::run_to_block::<AllPalletsWithSystem>(1);
848
849 assert_ok!(TestRegistrar::<Test>::register(
850 1,
851 ParaId::from(1_u32),
852 dummy_head_data(),
853 dummy_validation_code()
854 ));
855 assert_ok!(TestRegistrar::<Test>::register(
856 1,
857 ParaId::from(2_u32),
858 dummy_head_data(),
859 dummy_validation_code()
860 ));
861
862 System::run_to_block::<AllPalletsWithSystem>(20);
863 let now = System::block_number();
864 assert_eq!(Slots::lease_period_index(now).unwrap().0, 2);
865 assert!(Slots::lease_out(1.into(), &1, 1, 1, 1).is_err());
867 assert_ok!(Slots::lease_out(1.into(), &1, 1, 2, 1));
869 assert_ok!(Slots::lease_out(2.into(), &1, 1, 3, 1));
871
872 assert_eq!(TestRegistrar::<Test>::operations(), vec![(1.into(), 20, true),]);
873 });
874 }
875
876 #[test]
877 fn trigger_onboard_works() {
878 new_test_ext().execute_with(|| {
879 System::run_to_block::<AllPalletsWithSystem>(1);
880 assert_ok!(TestRegistrar::<Test>::register(
881 1,
882 ParaId::from(1_u32),
883 dummy_head_data(),
884 dummy_validation_code()
885 ));
886 assert_ok!(TestRegistrar::<Test>::register(
887 1,
888 ParaId::from(2_u32),
889 dummy_head_data(),
890 dummy_validation_code()
891 ));
892 assert_ok!(TestRegistrar::<Test>::register(
893 1,
894 ParaId::from(3_u32),
895 dummy_head_data(),
896 dummy_validation_code()
897 ));
898
899 Leases::<Test>::insert(ParaId::from(2_u32), vec![Some((0, 0))]);
903 Leases::<Test>::insert(ParaId::from(3_u32), vec![None, None, Some((0, 0))]);
905
906 assert_noop!(
908 Slots::trigger_onboard(RuntimeOrigin::signed(1), 1.into()),
909 Error::<Test>::ParaNotOnboarding
910 );
911
912 assert_ok!(Slots::trigger_onboard(RuntimeOrigin::signed(1), 2.into()));
914
915 assert_noop!(
917 Slots::trigger_onboard(RuntimeOrigin::signed(1), 3.into()),
918 Error::<Test>::ParaNotOnboarding
919 );
920
921 assert!(Slots::trigger_onboard(RuntimeOrigin::signed(1), 2.into()).is_err());
923
924 assert_eq!(TestRegistrar::<Test>::operations(), vec![(2.into(), 1, true),]);
925 });
926 }
927
928 #[test]
929 fn lease_period_offset_works() {
930 new_test_ext().execute_with(|| {
931 let (lpl, offset) = Slots::lease_period_length();
932 assert_eq!(offset, 0);
933 assert_eq!(Slots::lease_period_index(0), Some((0, true)));
934 assert_eq!(Slots::lease_period_index(1), Some((0, false)));
935 assert_eq!(Slots::lease_period_index(lpl - 1), Some((0, false)));
936 assert_eq!(Slots::lease_period_index(lpl), Some((1, true)));
937 assert_eq!(Slots::lease_period_index(lpl + 1), Some((1, false)));
938 assert_eq!(Slots::lease_period_index(2 * lpl - 1), Some((1, false)));
939 assert_eq!(Slots::lease_period_index(2 * lpl), Some((2, true)));
940 assert_eq!(Slots::lease_period_index(2 * lpl + 1), Some((2, false)));
941
942 LeaseOffset::set(5);
944 let (lpl, offset) = Slots::lease_period_length();
945 assert_eq!(offset, 5);
946 assert_eq!(Slots::lease_period_index(0), None);
947 assert_eq!(Slots::lease_period_index(1), None);
948 assert_eq!(Slots::lease_period_index(offset), Some((0, true)));
949 assert_eq!(Slots::lease_period_index(lpl), Some((0, false)));
950 assert_eq!(Slots::lease_period_index(lpl - 1 + offset), Some((0, false)));
951 assert_eq!(Slots::lease_period_index(lpl + offset), Some((1, true)));
952 assert_eq!(Slots::lease_period_index(lpl + offset + 1), Some((1, false)));
953 assert_eq!(Slots::lease_period_index(2 * lpl - 1 + offset), Some((1, false)));
954 assert_eq!(Slots::lease_period_index(2 * lpl + offset), Some((2, true)));
955 assert_eq!(Slots::lease_period_index(2 * lpl + offset + 1), Some((2, false)));
956 });
957 }
958}
959
960#[cfg(feature = "runtime-benchmarks")]
961mod benchmarking {
962 use super::*;
963 use frame_support::assert_ok;
964 use frame_system::RawOrigin;
965 use polkadot_runtime_parachains::paras;
966 use sp_runtime::traits::{Bounded, One};
967
968 use frame_benchmarking::v2::*;
969
970 use crate::slots::Pallet as Slots;
971
972 fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
973 let events = frame_system::Pallet::<T>::events();
974 let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into();
975 let frame_system::EventRecord { event, .. } = &events[events.len() - 1];
977 assert_eq!(event, &system_event);
978 }
979
980 fn register_a_parathread<T: Config + paras::Config>(i: u32) -> (ParaId, T::AccountId) {
982 let para = ParaId::from(i);
983 let leaser: T::AccountId = account("leaser", i, 0);
984 T::Currency::make_free_balance_be(&leaser, BalanceOf::<T>::max_value());
985 let worst_head_data = T::Registrar::worst_head_data();
986 let worst_validation_code = T::Registrar::worst_validation_code();
987
988 assert_ok!(T::Registrar::register(
989 leaser.clone(),
990 para,
991 worst_head_data,
992 worst_validation_code.clone(),
993 ));
994 assert_ok!(paras::Pallet::<T>::add_trusted_validation_code(
995 frame_system::Origin::<T>::Root.into(),
996 worst_validation_code,
997 ));
998
999 T::Registrar::execute_pending_transitions();
1000
1001 (para, leaser)
1002 }
1003
1004 #[benchmarks(
1005 where T: paras::Config,
1006 )]
1007
1008 mod benchmarks {
1009 use super::*;
1010 use alloc::vec;
1011
1012 #[benchmark]
1013 fn force_lease() -> Result<(), BenchmarkError> {
1014 frame_system::Pallet::<T>::set_block_number(T::LeaseOffset::get() + One::one());
1016 let para = ParaId::from(1337);
1017 let leaser: T::AccountId = account("leaser", 0, 0);
1018 T::Currency::make_free_balance_be(&leaser, BalanceOf::<T>::max_value());
1019 let amount = T::Currency::minimum_balance();
1020 let period_begin = 69u32.into();
1021 let period_count = 3u32.into();
1022 let origin =
1023 T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1024
1025 #[extrinsic_call]
1026 _(origin as T::RuntimeOrigin, para, leaser.clone(), amount, period_begin, period_count);
1027
1028 assert_last_event::<T>(
1029 Event::<T>::Leased {
1030 para_id: para,
1031 leaser,
1032 period_begin,
1033 period_count,
1034 extra_reserved: amount,
1035 total_amount: amount,
1036 }
1037 .into(),
1038 );
1039
1040 Ok(())
1041 }
1042
1043 #[benchmark]
1046 fn manage_lease_period_start(
1047 c: Linear<0, 100>,
1048 t: Linear<0, 100>,
1049 ) -> Result<(), BenchmarkError> {
1050 let period_begin = 1u32.into();
1051 let period_count = 4u32.into();
1052
1053 frame_system::Pallet::<T>::set_block_number(T::LeaseOffset::get() + One::one());
1055
1056 let paras_info = (0..t).map(|i| register_a_parathread::<T>(i)).collect::<Vec<_>>();
1058
1059 T::Registrar::execute_pending_transitions();
1060
1061 for (para, leaser) in paras_info {
1063 let amount = T::Currency::minimum_balance();
1064 let origin = T::ForceOrigin::try_successful_origin()
1065 .expect("ForceOrigin has no successful origin required for the benchmark");
1066 Slots::<T>::force_lease(origin, para, leaser, amount, period_begin, period_count)?;
1067 }
1068
1069 T::Registrar::execute_pending_transitions();
1070
1071 for i in 200..200 + c {
1073 let (para, _) = register_a_parathread::<T>(i);
1074 T::Registrar::make_parachain(para)?;
1075 }
1076
1077 T::Registrar::execute_pending_transitions();
1078
1079 for i in 0..t {
1080 assert!(T::Registrar::is_parathread(ParaId::from(i)));
1081 }
1082
1083 for i in 200..200 + c {
1084 assert!(T::Registrar::is_parachain(ParaId::from(i)));
1085 }
1086 #[block]
1087 {
1088 let _ = Slots::<T>::manage_lease_period_start(period_begin);
1089 }
1090
1091 T::Registrar::execute_pending_transitions();
1093 for i in 0..t {
1094 assert!(T::Registrar::is_parachain(ParaId::from(i)));
1095 }
1096 for i in 200..200 + c {
1097 assert!(T::Registrar::is_parathread(ParaId::from(i)));
1098 }
1099
1100 Ok(())
1101 }
1102
1103 #[benchmark]
1106 fn clear_all_leases() -> Result<(), BenchmarkError> {
1107 let max_people = 8;
1108 let (para, _) = register_a_parathread::<T>(1);
1109
1110 frame_system::Pallet::<T>::set_block_number(T::LeaseOffset::get() + One::one());
1112
1113 for i in 0..max_people {
1114 let leaser = account("lease_deposit", i, 0);
1115 let amount = T::Currency::minimum_balance();
1116 T::Currency::make_free_balance_be(&leaser, BalanceOf::<T>::max_value());
1117
1118 let period_count: LeasePeriodOf<T> = 4u32.into();
1120 let period_begin = period_count * i.into();
1121 let origin = T::ForceOrigin::try_successful_origin()
1122 .expect("ForceOrigin has no successful origin required for the benchmark");
1123 Slots::<T>::force_lease(origin, para, leaser, amount, period_begin, period_count)?;
1124 }
1125
1126 for i in 0..max_people {
1127 let leaser = account("lease_deposit", i, 0);
1128 assert_eq!(T::Currency::reserved_balance(&leaser), T::Currency::minimum_balance());
1129 }
1130
1131 let origin =
1132 T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1133
1134 #[extrinsic_call]
1135 _(origin as T::RuntimeOrigin, para);
1136
1137 for i in 0..max_people {
1138 let leaser = account("lease_deposit", i, 0);
1139 assert_eq!(T::Currency::reserved_balance(&leaser), 0u32.into());
1140 }
1141
1142 Ok(())
1143 }
1144
1145 #[benchmark]
1146 fn trigger_onboard() -> Result<(), BenchmarkError> {
1147 let (para, _) = register_a_parathread::<T>(1);
1149 Leases::<T>::insert(
1150 para,
1151 vec![Some((
1152 account::<T::AccountId>("lease_insert", 0, 0),
1153 BalanceOf::<T>::default(),
1154 ))],
1155 );
1156 assert!(T::Registrar::is_parathread(para));
1157 let caller = whitelisted_caller();
1158
1159 #[extrinsic_call]
1160 _(RawOrigin::Signed(caller), para);
1161
1162 T::Registrar::execute_pending_transitions();
1163 assert!(T::Registrar::is_parachain(para));
1164 Ok(())
1165 }
1166
1167 impl_benchmark_test_suite!(
1168 Slots,
1169 crate::integration_tests::new_test_ext(),
1170 crate::integration_tests::Test,
1171 );
1172 }
1173}