Skip to main content

polkadot_runtime_common/slots/
mod.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Parathread and parachains leasing system. Allows para IDs to be claimed, the code and data to be
18//! initialized and parachain slots (i.e. continuous scheduling) to be leased. Also allows for
19//! parachains and parathreads to be swapped.
20//!
21//! This doesn't handle the mechanics of determining which para ID actually ends up with a parachain
22//! lease. This must handled by a separately, through the trait interface that this pallet provides
23//! or the root dispatchables.
24
25pub 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		/// The overarching event type.
77		#[allow(deprecated)]
78		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
79
80		/// The currency type used for bidding.
81		type Currency: ReservableCurrency<Self::AccountId>;
82
83		/// The parachain registrar type.
84		type Registrar: Registrar<AccountId = Self::AccountId>;
85
86		/// The number of blocks over which a single period lasts.
87		#[pallet::constant]
88		type LeasePeriod: Get<BlockNumberFor<Self>>;
89
90		/// The number of blocks to offset each lease period by.
91		#[pallet::constant]
92		type LeaseOffset: Get<BlockNumberFor<Self>>;
93
94		/// The origin which may forcibly create or clear leases. Root can always do this.
95		type ForceOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;
96
97		/// Weight Information for the Extrinsics in the Pallet
98		type WeightInfo: WeightInfo;
99	}
100
101	/// Amounts held on deposit for each (possibly future) leased parachain.
102	///
103	/// The actual amount locked on its behalf by any account at any time is the maximum of the
104	/// second values of the items in this list whose first value is the account.
105	///
106	/// The first item in the list is the amount locked for the current Lease Period. Following
107	/// items are for the subsequent lease periods.
108	///
109	/// The default value (an empty list) implies that the parachain no longer exists (or never
110	/// existed) as far as this pallet is concerned.
111	///
112	/// If a parachain doesn't exist *yet* but is scheduled to exist in the future, then it
113	/// will be left-padded with one or more `None`s to denote the fact that nothing is held on
114	/// deposit for the non-existent chain currently, but is held at some point in the future.
115	///
116	/// It is illegal for a `None` value to trail in the list.
117	#[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		/// A new `[lease_period]` is beginning.
125		NewLeasePeriod { lease_period: LeasePeriodOf<T> },
126		/// A para has won the right to a continuous set of lease periods as a parachain.
127		/// First balance is any extra amount reserved on top of the para's existing deposit.
128		/// Second balance is the total amount reserved.
129		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		/// The parachain ID is not onboarding.
142		ParaNotOnboarding,
143		/// There was an error with the lease.
144		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 we're beginning a new lease period then handle that.
152				if first_block {
153					return Self::manage_lease_period_start(lease_period);
154				}
155			}
156
157			// We didn't return early above, so we didn't do anything.
158			Weight::zero()
159		}
160	}
161
162	#[pallet::call]
163	impl<T: Config> Pallet<T> {
164		/// Just a connect into the `lease_out` call, in case Root wants to force some lease to
165		/// happen independently of any other on-chain mechanism to use it.
166		///
167		/// The dispatch origin for this call must match `T::ForceOrigin`.
168		#[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		/// Clear all leases for a Para Id, refunding any deposits back to the original owners.
185		///
186		/// The dispatch origin for this call must match `T::ForceOrigin`.
187		#[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			// Refund any deposits for these leases
194			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		/// Try to onboard a parachain that has a lease for the current lease period.
204		///
205		/// This function can be useful if there was some state issue with a para that should
206		/// have onboarded, but was unable to. As long as they have a lease period, we can
207		/// let them onboard from here.
208		///
209		/// Origin must be signed, but can be called by anyone.
210		#[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				// If the first element in leases is present, then it has a lease!
217				// We can try to onboard it.
218				Some(Some(_lease_info)) => T::Registrar::make_parachain(para)?,
219				// Otherwise, it does not have a lease.
220				Some(None) | None => return Err(Error::<T>::ParaNotOnboarding.into()),
221			};
222			Ok(())
223		}
224	}
225}
226
227impl<T: Config> Pallet<T> {
228	/// A new lease period is beginning. We're at the start of the first block of it.
229	///
230	/// We need to on-board and off-board parachains as needed. We should also handle reducing/
231	/// returning deposits.
232	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		// Figure out what chains need bringing on.
238		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			// ^^ should never be empty since we would have deleted the entry otherwise.
244
245			if lease_periods.len() == 1 {
246				// Just one entry, which corresponds to the now-ended lease period.
247				//
248				// `para` is now just an on-demand parachain.
249				//
250				// Unreserve whatever is left.
251				if let Some((who, value)) = &lease_periods[0] {
252					T::Currency::unreserve(&who, *value);
253				}
254
255				// Remove the now-empty lease list.
256				Leases::<T>::remove(para);
257			} else {
258				// The parachain entry has leased future periods.
259
260				// We need to pop the first deposit entry, which corresponds to the now-
261				// ended lease period.
262				let maybe_ended_lease = lease_periods.remove(0);
263
264				Leases::<T>::insert(para, &lease_periods);
265
266				// If we *were* active in the last period and so have ended a lease...
267				if let Some(ended_lease) = maybe_ended_lease {
268					// Then we need to get the new amount that should continue to be held on
269					// deposit for the parachain.
270					let now_held = Self::deposit_held(para, &ended_lease.0);
271
272					// If this is less than what we were holding for this leaser's now-ended lease,
273					// then unreserve it.
274					if let Some(rebate) = ended_lease.1.checked_sub(&now_held) {
275						T::Currency::unreserve(&ended_lease.0, rebate);
276					}
277				}
278
279				// If we have an active lease in the new period, then add to the current parachains
280				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				// incoming.
290				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				// outgoing.
298				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	// Return a vector of (user, balance) for all deposits for a parachain.
310	// Useful when trying to clean up a parachain leases, as this would tell
311	// you all the balances you need to unreserve.
312	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		// Finally, we update the deposit held so it is `amount` for the new lease period
354		// indices that were won in the auction.
355		let offset = period_begin
356			.checked_sub(&current_lease_period)
357			.and_then(|x| x.checked_into::<usize>())
358			.ok_or(LeaseError::AlreadyEnded)?;
359
360		// offset is the amount into the `Deposits` items list that our lease begins. `period_count`
361		// is the number of items that it lasts for.
362
363		// The lease period index range (begin, end) that newly belongs to this parachain
364		// ID. We need to ensure that it features in `Deposits` to prevent it from being
365		// reaped too early (any managed parachain whose `Deposits` set runs low will be
366		// removed).
367		Leases::<T>::try_mutate(para, |d| {
368			// Left-pad with `None`s as necessary.
369			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			// Then place the deposit values for as long as the chain should exist.
375			for i in offset..(offset + period_count_usize) {
376				if d.len() > i {
377					// Already exists but it's `None`. That means a later slot was already leased.
378					// No problem.
379					if d[i] == None {
380						d[i] = Some((leaser.clone(), amount));
381					} else {
382						// The chain tried to lease the same period twice. This might be a griefing
383						// attempt.
384						//
385						// We bail, not giving any lease and leave it for governance to sort out.
386						return Err(LeaseError::AlreadyLeased);
387					}
388				} else if d.len() == i {
389					// Doesn't exist. This is usual.
390					d.push(Some((leaser.clone(), amount)));
391				} else {
392					// earlier resize means it must be >= i; qed
393					// defensive code though since we really don't want to panic here.
394				}
395			}
396
397			// Figure out whether we already have some funds of `leaser` held in reserve for
398			// `para_id`.  If so, then we can deduct those from the amount that we need to reserve.
399			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			// Check if current lease period is same as period begin, and onboard them directly.
408			// This will allow us to support onboarding new parachains in the middle of a lease
409			// period.
410			if current_lease_period == period_begin {
411				// Best effort. Not much we can do if this fails.
412				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		// Note that blocks before `LeaseOffset` do not count as any lease period.
455		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		// Can't look in the past, so we pick whichever is the biggest.
474		let start_period = first_period.max(current_lease_period);
475		// Find the offset to look into the lease period list.
476		// Subtraction is safe because of max above.
477		let offset = match (start_period - current_lease_period).checked_into::<usize>() {
478			Some(offset) => offset,
479			None => return true,
480		};
481
482		// This calculates how deep we should look in the vec for a potential lease.
483		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		// Get the leases, and check each item in the vec which is part of the range we are
489		// checking.
490		let leases = Leases::<T>::get(para_id);
491		for slot in offset..=offset + period_count {
492			if let Some(Some(_)) = leases.get(slot) {
493				// If there exists any lease period, we exit early and return true.
494				return true;
495			}
496		}
497
498		// If we got here, then we did not find any overlapping leases.
499		false
500	}
501}
502
503/// tests for this pallet
504#[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	// This function basically just builds a genesis storage key/value store according to
579	// our desired mock up.
580	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			// max_num different people are reserved for leases to Para ID 1
823			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			// Balances cleaned up correctly
833			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			// Leases is empty.
840			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			// Can't lease from the past
866			assert!(Slots::lease_out(1.into(), &1, 1, 1, 1).is_err());
867			// Lease in the current period triggers onboarding
868			assert_ok!(Slots::lease_out(1.into(), &1, 1, 2, 1));
869			// Lease in the future doesn't
870			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			// We will directly manipulate leases to emulate some kind of failure in the system.
900			// Para 1 will have no leases
901			// Para 2 will have a lease period in the current index
902			Leases::<Test>::insert(ParaId::from(2_u32), vec![Some((0, 0))]);
903			// Para 3 will have a lease period in a future index
904			Leases::<Test>::insert(ParaId::from(3_u32), vec![None, None, Some((0, 0))]);
905
906			// Para 1 should fail cause they don't have any leases
907			assert_noop!(
908				Slots::trigger_onboard(RuntimeOrigin::signed(1), 1.into()),
909				Error::<Test>::ParaNotOnboarding
910			);
911
912			// Para 2 should succeed
913			assert_ok!(Slots::trigger_onboard(RuntimeOrigin::signed(1), 2.into()));
914
915			// Para 3 should fail cause their lease is in the future
916			assert_noop!(
917				Slots::trigger_onboard(RuntimeOrigin::signed(1), 3.into()),
918				Error::<Test>::ParaNotOnboarding
919			);
920
921			// Trying Para 2 again should fail cause they are not currently an on-demand parachain
922			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			// Lease period is 10, and we add an offset of 5.
943			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		// compare to the last event record
976		let frame_system::EventRecord { event, .. } = &events[events.len() - 1];
977		assert_eq!(event, &system_event);
978	}
979
980	// Registers a parathread (on-demand parachain)
981	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			// If there is an offset, we need to be on that block to be able to do lease things.
1015			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		// Worst case scenario, T on-demand parachains onboard, and C lease holding parachains
1044		// offboard. Assume reasonable maximum of 100 paras at any time
1045		#[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			// If there is an offset, we need to be on that block to be able to do lease things.
1054			frame_system::Pallet::<T>::set_block_number(T::LeaseOffset::get() + One::one());
1055
1056			// Make T parathreads (on-demand parachains)
1057			let paras_info = (0..t).map(|i| register_a_parathread::<T>(i)).collect::<Vec<_>>();
1058
1059			T::Registrar::execute_pending_transitions();
1060
1061			// T on-demand parachains are upgrading to lease holding parachains
1062			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			// C lease holding parachains are downgrading to on-demand parachains
1072			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			// All paras should have switched.
1092			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		// Assume that at most 8 people have deposits for leases on a parachain.
1104		// This would cover at least 4 years of leases in the worst case scenario.
1105		#[benchmark]
1106		fn clear_all_leases() -> Result<(), BenchmarkError> {
1107			let max_people = 8;
1108			let (para, _) = register_a_parathread::<T>(1);
1109
1110			// If there is an offset, we need to be on that block to be able to do lease things.
1111			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				// Average slot has 4 lease periods.
1119				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			// get a parachain into a bad state where they did not onboard
1148			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}