referrerpolicy=no-referrer-when-downgrade

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::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				None => {
321					tracker.insert(who, amount);
322				},
323			},
324			None => {},
325		});
326
327		tracker.into_iter().collect()
328	}
329}
330
331impl<T: Config> crate::traits::OnSwap for Pallet<T> {
332	fn on_swap(one: ParaId, other: ParaId) {
333		Leases::<T>::mutate(one, |x| Leases::<T>::mutate(other, |y| core::mem::swap(x, y)))
334	}
335}
336
337impl<T: Config> Leaser<BlockNumberFor<T>> for Pallet<T> {
338	type AccountId = T::AccountId;
339	type LeasePeriod = BlockNumberFor<T>;
340	type Currency = T::Currency;
341
342	fn lease_out(
343		para: ParaId,
344		leaser: &Self::AccountId,
345		amount: <Self::Currency as Currency<Self::AccountId>>::Balance,
346		period_begin: Self::LeasePeriod,
347		period_count: Self::LeasePeriod,
348	) -> Result<(), LeaseError> {
349		let now = frame_system::Pallet::<T>::block_number();
350		let (current_lease_period, _) =
351			Self::lease_period_index(now).ok_or(LeaseError::NoLeasePeriod)?;
352		// Finally, we update the deposit held so it is `amount` for the new lease period
353		// indices that were won in the auction.
354		let offset = period_begin
355			.checked_sub(&current_lease_period)
356			.and_then(|x| x.checked_into::<usize>())
357			.ok_or(LeaseError::AlreadyEnded)?;
358
359		// offset is the amount into the `Deposits` items list that our lease begins. `period_count`
360		// is the number of items that it lasts for.
361
362		// The lease period index range (begin, end) that newly belongs to this parachain
363		// ID. We need to ensure that it features in `Deposits` to prevent it from being
364		// reaped too early (any managed parachain whose `Deposits` set runs low will be
365		// removed).
366		Leases::<T>::try_mutate(para, |d| {
367			// Left-pad with `None`s as necessary.
368			if d.len() < offset {
369				d.resize_with(offset, || None);
370			}
371			let period_count_usize =
372				period_count.checked_into::<usize>().ok_or(LeaseError::AlreadyEnded)?;
373			// Then place the deposit values for as long as the chain should exist.
374			for i in offset..(offset + period_count_usize) {
375				if d.len() > i {
376					// Already exists but it's `None`. That means a later slot was already leased.
377					// No problem.
378					if d[i] == None {
379						d[i] = Some((leaser.clone(), amount));
380					} else {
381						// The chain tried to lease the same period twice. This might be a griefing
382						// attempt.
383						//
384						// We bail, not giving any lease and leave it for governance to sort out.
385						return Err(LeaseError::AlreadyLeased);
386					}
387				} else if d.len() == i {
388					// Doesn't exist. This is usual.
389					d.push(Some((leaser.clone(), amount)));
390				} else {
391					// earlier resize means it must be >= i; qed
392					// defensive code though since we really don't want to panic here.
393				}
394			}
395
396			// Figure out whether we already have some funds of `leaser` held in reserve for
397			// `para_id`.  If so, then we can deduct those from the amount that we need to reserve.
398			let maybe_additional = amount.checked_sub(&Self::deposit_held(para, &leaser));
399			if let Some(ref additional) = maybe_additional {
400				T::Currency::reserve(&leaser, *additional)
401					.map_err(|_| LeaseError::ReserveFailed)?;
402			}
403
404			let reserved = maybe_additional.unwrap_or_default();
405
406			// Check if current lease period is same as period begin, and onboard them directly.
407			// This will allow us to support onboarding new parachains in the middle of a lease
408			// period.
409			if current_lease_period == period_begin {
410				// Best effort. Not much we can do if this fails.
411				let _ = T::Registrar::make_parachain(para);
412			}
413
414			Self::deposit_event(Event::<T>::Leased {
415				para_id: para,
416				leaser: leaser.clone(),
417				period_begin,
418				period_count,
419				extra_reserved: reserved,
420				total_amount: amount,
421			});
422
423			Ok(())
424		})
425	}
426
427	fn deposit_held(
428		para: ParaId,
429		leaser: &Self::AccountId,
430	) -> <Self::Currency as Currency<Self::AccountId>>::Balance {
431		Leases::<T>::get(para)
432			.into_iter()
433			.map(|lease| match lease {
434				Some((who, amount)) =>
435					if &who == leaser {
436						amount
437					} else {
438						Zero::zero()
439					},
440				None => Zero::zero(),
441			})
442			.max()
443			.unwrap_or_else(Zero::zero)
444	}
445
446	#[cfg(any(feature = "runtime-benchmarks", test))]
447	fn lease_period_length() -> (BlockNumberFor<T>, BlockNumberFor<T>) {
448		(T::LeasePeriod::get(), T::LeaseOffset::get())
449	}
450
451	fn lease_period_index(b: BlockNumberFor<T>) -> Option<(Self::LeasePeriod, bool)> {
452		// Note that blocks before `LeaseOffset` do not count as any lease period.
453		let offset_block_now = b.checked_sub(&T::LeaseOffset::get())?;
454		let lease_period = offset_block_now / T::LeasePeriod::get();
455		let at_begin = (offset_block_now % T::LeasePeriod::get()).is_zero();
456
457		Some((lease_period, at_begin))
458	}
459
460	fn already_leased(
461		para_id: ParaId,
462		first_period: Self::LeasePeriod,
463		last_period: Self::LeasePeriod,
464	) -> bool {
465		let now = frame_system::Pallet::<T>::block_number();
466		let (current_lease_period, _) = match Self::lease_period_index(now) {
467			Some(clp) => clp,
468			None => return true,
469		};
470
471		// Can't look in the past, so we pick whichever is the biggest.
472		let start_period = first_period.max(current_lease_period);
473		// Find the offset to look into the lease period list.
474		// Subtraction is safe because of max above.
475		let offset = match (start_period - current_lease_period).checked_into::<usize>() {
476			Some(offset) => offset,
477			None => return true,
478		};
479
480		// This calculates how deep we should look in the vec for a potential lease.
481		let period_count = match last_period.saturating_sub(start_period).checked_into::<usize>() {
482			Some(period_count) => period_count,
483			None => return true,
484		};
485
486		// Get the leases, and check each item in the vec which is part of the range we are
487		// checking.
488		let leases = Leases::<T>::get(para_id);
489		for slot in offset..=offset + period_count {
490			if let Some(Some(_)) = leases.get(slot) {
491				// If there exists any lease period, we exit early and return true.
492				return true;
493			}
494		}
495
496		// If we got here, then we did not find any overlapping leases.
497		false
498	}
499}
500
501/// tests for this pallet
502#[cfg(test)]
503mod tests {
504	use super::*;
505
506	use crate::{mock::TestRegistrar, slots};
507	use frame_support::{assert_noop, assert_ok, derive_impl, parameter_types};
508	use frame_system::EnsureRoot;
509	use pallet_balances;
510	use polkadot_primitives::BlockNumber;
511	use polkadot_primitives_test_helpers::{dummy_head_data, dummy_validation_code};
512	use sp_core::H256;
513	use sp_runtime::{
514		traits::{BlakeTwo256, IdentityLookup},
515		BuildStorage,
516	};
517
518	type Block = frame_system::mocking::MockBlockU32<Test>;
519
520	frame_support::construct_runtime!(
521		pub enum Test
522		{
523			System: frame_system,
524			Balances: pallet_balances,
525			Slots: slots,
526		}
527	);
528
529	#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
530	impl frame_system::Config for Test {
531		type BaseCallFilter = frame_support::traits::Everything;
532		type BlockWeights = ();
533		type BlockLength = ();
534		type RuntimeOrigin = RuntimeOrigin;
535		type RuntimeCall = RuntimeCall;
536		type Nonce = u64;
537		type Hash = H256;
538		type Hashing = BlakeTwo256;
539		type AccountId = u64;
540		type Lookup = IdentityLookup<Self::AccountId>;
541		type Block = Block;
542		type RuntimeEvent = RuntimeEvent;
543		type DbWeight = ();
544		type Version = ();
545		type PalletInfo = PalletInfo;
546		type AccountData = pallet_balances::AccountData<u64>;
547		type OnNewAccount = ();
548		type OnKilledAccount = ();
549		type SystemWeightInfo = ();
550		type SS58Prefix = ();
551		type OnSetCode = ();
552		type MaxConsumers = frame_support::traits::ConstU32<16>;
553	}
554
555	#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
556	impl pallet_balances::Config for Test {
557		type AccountStore = System;
558	}
559
560	parameter_types! {
561		pub const LeasePeriod: BlockNumber = 10;
562		pub static LeaseOffset: BlockNumber = 0;
563		pub const ParaDeposit: u64 = 1;
564	}
565
566	impl Config for Test {
567		type RuntimeEvent = RuntimeEvent;
568		type Currency = Balances;
569		type Registrar = TestRegistrar<Test>;
570		type LeasePeriod = LeasePeriod;
571		type LeaseOffset = LeaseOffset;
572		type ForceOrigin = EnsureRoot<Self::AccountId>;
573		type WeightInfo = crate::slots::TestWeightInfo;
574	}
575
576	// This function basically just builds a genesis storage key/value store according to
577	// our desired mock up.
578	pub fn new_test_ext() -> sp_io::TestExternalities {
579		let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
580		pallet_balances::GenesisConfig::<Test> {
581			balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)],
582			..Default::default()
583		}
584		.assimilate_storage(&mut t)
585		.unwrap();
586		t.into()
587	}
588
589	#[test]
590	fn basic_setup_works() {
591		new_test_ext().execute_with(|| {
592			System::run_to_block::<AllPalletsWithSystem>(1);
593			assert_eq!(Slots::lease_period_length(), (10, 0));
594			let now = System::block_number();
595			assert_eq!(Slots::lease_period_index(now).unwrap().0, 0);
596			assert_eq!(Slots::deposit_held(1.into(), &1), 0);
597
598			System::run_to_block::<AllPalletsWithSystem>(10);
599			let now = System::block_number();
600			assert_eq!(Slots::lease_period_index(now).unwrap().0, 1);
601		});
602	}
603
604	#[test]
605	fn lease_lifecycle_works() {
606		new_test_ext().execute_with(|| {
607			System::run_to_block::<AllPalletsWithSystem>(1);
608
609			assert_ok!(TestRegistrar::<Test>::register(
610				1,
611				ParaId::from(1_u32),
612				dummy_head_data(),
613				dummy_validation_code()
614			));
615
616			assert_ok!(Slots::lease_out(1.into(), &1, 1, 1, 1));
617			assert_eq!(Slots::deposit_held(1.into(), &1), 1);
618			assert_eq!(Balances::reserved_balance(1), 1);
619
620			System::run_to_block::<AllPalletsWithSystem>(19);
621			assert_eq!(Slots::deposit_held(1.into(), &1), 1);
622			assert_eq!(Balances::reserved_balance(1), 1);
623
624			System::run_to_block::<AllPalletsWithSystem>(20);
625			assert_eq!(Slots::deposit_held(1.into(), &1), 0);
626			assert_eq!(Balances::reserved_balance(1), 0);
627
628			assert_eq!(
629				TestRegistrar::<Test>::operations(),
630				vec![(1.into(), 10, true), (1.into(), 20, false),]
631			);
632		});
633	}
634
635	#[test]
636	fn lease_interrupted_lifecycle_works() {
637		new_test_ext().execute_with(|| {
638			System::run_to_block::<AllPalletsWithSystem>(1);
639
640			assert_ok!(TestRegistrar::<Test>::register(
641				1,
642				ParaId::from(1_u32),
643				dummy_head_data(),
644				dummy_validation_code()
645			));
646
647			assert_ok!(Slots::lease_out(1.into(), &1, 6, 1, 1));
648			assert_ok!(Slots::lease_out(1.into(), &1, 4, 3, 1));
649
650			System::run_to_block::<AllPalletsWithSystem>(19);
651			assert_eq!(Slots::deposit_held(1.into(), &1), 6);
652			assert_eq!(Balances::reserved_balance(1), 6);
653
654			System::run_to_block::<AllPalletsWithSystem>(20);
655			assert_eq!(Slots::deposit_held(1.into(), &1), 4);
656			assert_eq!(Balances::reserved_balance(1), 4);
657
658			System::run_to_block::<AllPalletsWithSystem>(39);
659			assert_eq!(Slots::deposit_held(1.into(), &1), 4);
660			assert_eq!(Balances::reserved_balance(1), 4);
661
662			System::run_to_block::<AllPalletsWithSystem>(40);
663			assert_eq!(Slots::deposit_held(1.into(), &1), 0);
664			assert_eq!(Balances::reserved_balance(1), 0);
665
666			assert_eq!(
667				TestRegistrar::<Test>::operations(),
668				vec![
669					(1.into(), 10, true),
670					(1.into(), 20, false),
671					(1.into(), 30, true),
672					(1.into(), 40, false),
673				]
674			);
675		});
676	}
677
678	#[test]
679	fn lease_relayed_lifecycle_works() {
680		new_test_ext().execute_with(|| {
681			System::run_to_block::<AllPalletsWithSystem>(1);
682
683			assert_ok!(TestRegistrar::<Test>::register(
684				1,
685				ParaId::from(1_u32),
686				dummy_head_data(),
687				dummy_validation_code()
688			));
689
690			assert!(Slots::lease_out(1.into(), &1, 6, 1, 1).is_ok());
691			assert!(Slots::lease_out(1.into(), &2, 4, 2, 1).is_ok());
692			assert_eq!(Slots::deposit_held(1.into(), &1), 6);
693			assert_eq!(Balances::reserved_balance(1), 6);
694			assert_eq!(Slots::deposit_held(1.into(), &2), 4);
695			assert_eq!(Balances::reserved_balance(2), 4);
696
697			System::run_to_block::<AllPalletsWithSystem>(19);
698			assert_eq!(Slots::deposit_held(1.into(), &1), 6);
699			assert_eq!(Balances::reserved_balance(1), 6);
700			assert_eq!(Slots::deposit_held(1.into(), &2), 4);
701			assert_eq!(Balances::reserved_balance(2), 4);
702
703			System::run_to_block::<AllPalletsWithSystem>(20);
704			assert_eq!(Slots::deposit_held(1.into(), &1), 0);
705			assert_eq!(Balances::reserved_balance(1), 0);
706			assert_eq!(Slots::deposit_held(1.into(), &2), 4);
707			assert_eq!(Balances::reserved_balance(2), 4);
708
709			System::run_to_block::<AllPalletsWithSystem>(29);
710			assert_eq!(Slots::deposit_held(1.into(), &1), 0);
711			assert_eq!(Balances::reserved_balance(1), 0);
712			assert_eq!(Slots::deposit_held(1.into(), &2), 4);
713			assert_eq!(Balances::reserved_balance(2), 4);
714
715			System::run_to_block::<AllPalletsWithSystem>(30);
716			assert_eq!(Slots::deposit_held(1.into(), &1), 0);
717			assert_eq!(Balances::reserved_balance(1), 0);
718			assert_eq!(Slots::deposit_held(1.into(), &2), 0);
719			assert_eq!(Balances::reserved_balance(2), 0);
720
721			assert_eq!(
722				TestRegistrar::<Test>::operations(),
723				vec![(1.into(), 10, true), (1.into(), 30, false),]
724			);
725		});
726	}
727
728	#[test]
729	fn lease_deposit_increase_works() {
730		new_test_ext().execute_with(|| {
731			System::run_to_block::<AllPalletsWithSystem>(1);
732
733			assert_ok!(TestRegistrar::<Test>::register(
734				1,
735				ParaId::from(1_u32),
736				dummy_head_data(),
737				dummy_validation_code()
738			));
739
740			assert!(Slots::lease_out(1.into(), &1, 4, 1, 1).is_ok());
741			assert_eq!(Slots::deposit_held(1.into(), &1), 4);
742			assert_eq!(Balances::reserved_balance(1), 4);
743
744			assert!(Slots::lease_out(1.into(), &1, 6, 2, 1).is_ok());
745			assert_eq!(Slots::deposit_held(1.into(), &1), 6);
746			assert_eq!(Balances::reserved_balance(1), 6);
747
748			System::run_to_block::<AllPalletsWithSystem>(29);
749			assert_eq!(Slots::deposit_held(1.into(), &1), 6);
750			assert_eq!(Balances::reserved_balance(1), 6);
751
752			System::run_to_block::<AllPalletsWithSystem>(30);
753			assert_eq!(Slots::deposit_held(1.into(), &1), 0);
754			assert_eq!(Balances::reserved_balance(1), 0);
755
756			assert_eq!(
757				TestRegistrar::<Test>::operations(),
758				vec![(1.into(), 10, true), (1.into(), 30, false),]
759			);
760		});
761	}
762
763	#[test]
764	fn lease_deposit_decrease_works() {
765		new_test_ext().execute_with(|| {
766			System::run_to_block::<AllPalletsWithSystem>(1);
767
768			assert_ok!(TestRegistrar::<Test>::register(
769				1,
770				ParaId::from(1_u32),
771				dummy_head_data(),
772				dummy_validation_code()
773			));
774
775			assert!(Slots::lease_out(1.into(), &1, 6, 1, 1).is_ok());
776			assert_eq!(Slots::deposit_held(1.into(), &1), 6);
777			assert_eq!(Balances::reserved_balance(1), 6);
778
779			assert!(Slots::lease_out(1.into(), &1, 4, 2, 1).is_ok());
780			assert_eq!(Slots::deposit_held(1.into(), &1), 6);
781			assert_eq!(Balances::reserved_balance(1), 6);
782
783			System::run_to_block::<AllPalletsWithSystem>(19);
784			assert_eq!(Slots::deposit_held(1.into(), &1), 6);
785			assert_eq!(Balances::reserved_balance(1), 6);
786
787			System::run_to_block::<AllPalletsWithSystem>(20);
788			assert_eq!(Slots::deposit_held(1.into(), &1), 4);
789			assert_eq!(Balances::reserved_balance(1), 4);
790
791			System::run_to_block::<AllPalletsWithSystem>(29);
792			assert_eq!(Slots::deposit_held(1.into(), &1), 4);
793			assert_eq!(Balances::reserved_balance(1), 4);
794
795			System::run_to_block::<AllPalletsWithSystem>(30);
796			assert_eq!(Slots::deposit_held(1.into(), &1), 0);
797			assert_eq!(Balances::reserved_balance(1), 0);
798
799			assert_eq!(
800				TestRegistrar::<Test>::operations(),
801				vec![(1.into(), 10, true), (1.into(), 30, false),]
802			);
803		});
804	}
805
806	#[test]
807	fn clear_all_leases_works() {
808		new_test_ext().execute_with(|| {
809			System::run_to_block::<AllPalletsWithSystem>(1);
810
811			assert_ok!(TestRegistrar::<Test>::register(
812				1,
813				ParaId::from(1_u32),
814				dummy_head_data(),
815				dummy_validation_code()
816			));
817
818			let max_num = 5u32;
819
820			// max_num different people are reserved for leases to Para ID 1
821			for i in 1u32..=max_num {
822				let j: u64 = i.into();
823				assert_ok!(Slots::lease_out(1.into(), &j, j * 10 - 1, i * i, i));
824				assert_eq!(Slots::deposit_held(1.into(), &j), j * 10 - 1);
825				assert_eq!(Balances::reserved_balance(j), j * 10 - 1);
826			}
827
828			assert_ok!(Slots::clear_all_leases(RuntimeOrigin::root(), 1.into()));
829
830			// Balances cleaned up correctly
831			for i in 1u32..=max_num {
832				let j: u64 = i.into();
833				assert_eq!(Slots::deposit_held(1.into(), &j), 0);
834				assert_eq!(Balances::reserved_balance(j), 0);
835			}
836
837			// Leases is empty.
838			assert!(Leases::<Test>::get(ParaId::from(1_u32)).is_empty());
839		});
840	}
841
842	#[test]
843	fn lease_out_current_lease_period() {
844		new_test_ext().execute_with(|| {
845			System::run_to_block::<AllPalletsWithSystem>(1);
846
847			assert_ok!(TestRegistrar::<Test>::register(
848				1,
849				ParaId::from(1_u32),
850				dummy_head_data(),
851				dummy_validation_code()
852			));
853			assert_ok!(TestRegistrar::<Test>::register(
854				1,
855				ParaId::from(2_u32),
856				dummy_head_data(),
857				dummy_validation_code()
858			));
859
860			System::run_to_block::<AllPalletsWithSystem>(20);
861			let now = System::block_number();
862			assert_eq!(Slots::lease_period_index(now).unwrap().0, 2);
863			// Can't lease from the past
864			assert!(Slots::lease_out(1.into(), &1, 1, 1, 1).is_err());
865			// Lease in the current period triggers onboarding
866			assert_ok!(Slots::lease_out(1.into(), &1, 1, 2, 1));
867			// Lease in the future doesn't
868			assert_ok!(Slots::lease_out(2.into(), &1, 1, 3, 1));
869
870			assert_eq!(TestRegistrar::<Test>::operations(), vec![(1.into(), 20, true),]);
871		});
872	}
873
874	#[test]
875	fn trigger_onboard_works() {
876		new_test_ext().execute_with(|| {
877			System::run_to_block::<AllPalletsWithSystem>(1);
878			assert_ok!(TestRegistrar::<Test>::register(
879				1,
880				ParaId::from(1_u32),
881				dummy_head_data(),
882				dummy_validation_code()
883			));
884			assert_ok!(TestRegistrar::<Test>::register(
885				1,
886				ParaId::from(2_u32),
887				dummy_head_data(),
888				dummy_validation_code()
889			));
890			assert_ok!(TestRegistrar::<Test>::register(
891				1,
892				ParaId::from(3_u32),
893				dummy_head_data(),
894				dummy_validation_code()
895			));
896
897			// We will directly manipulate leases to emulate some kind of failure in the system.
898			// Para 1 will have no leases
899			// Para 2 will have a lease period in the current index
900			Leases::<Test>::insert(ParaId::from(2_u32), vec![Some((0, 0))]);
901			// Para 3 will have a lease period in a future index
902			Leases::<Test>::insert(ParaId::from(3_u32), vec![None, None, Some((0, 0))]);
903
904			// Para 1 should fail cause they don't have any leases
905			assert_noop!(
906				Slots::trigger_onboard(RuntimeOrigin::signed(1), 1.into()),
907				Error::<Test>::ParaNotOnboarding
908			);
909
910			// Para 2 should succeed
911			assert_ok!(Slots::trigger_onboard(RuntimeOrigin::signed(1), 2.into()));
912
913			// Para 3 should fail cause their lease is in the future
914			assert_noop!(
915				Slots::trigger_onboard(RuntimeOrigin::signed(1), 3.into()),
916				Error::<Test>::ParaNotOnboarding
917			);
918
919			// Trying Para 2 again should fail cause they are not currently an on-demand parachain
920			assert!(Slots::trigger_onboard(RuntimeOrigin::signed(1), 2.into()).is_err());
921
922			assert_eq!(TestRegistrar::<Test>::operations(), vec![(2.into(), 1, true),]);
923		});
924	}
925
926	#[test]
927	fn lease_period_offset_works() {
928		new_test_ext().execute_with(|| {
929			let (lpl, offset) = Slots::lease_period_length();
930			assert_eq!(offset, 0);
931			assert_eq!(Slots::lease_period_index(0), Some((0, true)));
932			assert_eq!(Slots::lease_period_index(1), Some((0, false)));
933			assert_eq!(Slots::lease_period_index(lpl - 1), Some((0, false)));
934			assert_eq!(Slots::lease_period_index(lpl), Some((1, true)));
935			assert_eq!(Slots::lease_period_index(lpl + 1), Some((1, false)));
936			assert_eq!(Slots::lease_period_index(2 * lpl - 1), Some((1, false)));
937			assert_eq!(Slots::lease_period_index(2 * lpl), Some((2, true)));
938			assert_eq!(Slots::lease_period_index(2 * lpl + 1), Some((2, false)));
939
940			// Lease period is 10, and we add an offset of 5.
941			LeaseOffset::set(5);
942			let (lpl, offset) = Slots::lease_period_length();
943			assert_eq!(offset, 5);
944			assert_eq!(Slots::lease_period_index(0), None);
945			assert_eq!(Slots::lease_period_index(1), None);
946			assert_eq!(Slots::lease_period_index(offset), Some((0, true)));
947			assert_eq!(Slots::lease_period_index(lpl), Some((0, false)));
948			assert_eq!(Slots::lease_period_index(lpl - 1 + offset), Some((0, false)));
949			assert_eq!(Slots::lease_period_index(lpl + offset), Some((1, true)));
950			assert_eq!(Slots::lease_period_index(lpl + offset + 1), Some((1, false)));
951			assert_eq!(Slots::lease_period_index(2 * lpl - 1 + offset), Some((1, false)));
952			assert_eq!(Slots::lease_period_index(2 * lpl + offset), Some((2, true)));
953			assert_eq!(Slots::lease_period_index(2 * lpl + offset + 1), Some((2, false)));
954		});
955	}
956}
957
958#[cfg(feature = "runtime-benchmarks")]
959mod benchmarking {
960	use super::*;
961	use frame_support::assert_ok;
962	use frame_system::RawOrigin;
963	use polkadot_runtime_parachains::paras;
964	use sp_runtime::traits::{Bounded, One};
965
966	use frame_benchmarking::v2::*;
967
968	use crate::slots::Pallet as Slots;
969
970	fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
971		let events = frame_system::Pallet::<T>::events();
972		let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into();
973		// compare to the last event record
974		let frame_system::EventRecord { event, .. } = &events[events.len() - 1];
975		assert_eq!(event, &system_event);
976	}
977
978	// Registers a parathread (on-demand parachain)
979	fn register_a_parathread<T: Config + paras::Config>(i: u32) -> (ParaId, T::AccountId) {
980		let para = ParaId::from(i);
981		let leaser: T::AccountId = account("leaser", i, 0);
982		T::Currency::make_free_balance_be(&leaser, BalanceOf::<T>::max_value());
983		let worst_head_data = T::Registrar::worst_head_data();
984		let worst_validation_code = T::Registrar::worst_validation_code();
985
986		assert_ok!(T::Registrar::register(
987			leaser.clone(),
988			para,
989			worst_head_data,
990			worst_validation_code.clone(),
991		));
992		assert_ok!(paras::Pallet::<T>::add_trusted_validation_code(
993			frame_system::Origin::<T>::Root.into(),
994			worst_validation_code,
995		));
996
997		T::Registrar::execute_pending_transitions();
998
999		(para, leaser)
1000	}
1001
1002	#[benchmarks(
1003		where T: paras::Config,
1004	)]
1005
1006	mod benchmarks {
1007		use super::*;
1008
1009		#[benchmark]
1010		fn force_lease() -> Result<(), BenchmarkError> {
1011			// If there is an offset, we need to be on that block to be able to do lease things.
1012			frame_system::Pallet::<T>::set_block_number(T::LeaseOffset::get() + One::one());
1013			let para = ParaId::from(1337);
1014			let leaser: T::AccountId = account("leaser", 0, 0);
1015			T::Currency::make_free_balance_be(&leaser, BalanceOf::<T>::max_value());
1016			let amount = T::Currency::minimum_balance();
1017			let period_begin = 69u32.into();
1018			let period_count = 3u32.into();
1019			let origin =
1020				T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1021
1022			#[extrinsic_call]
1023			_(origin as T::RuntimeOrigin, para, leaser.clone(), amount, period_begin, period_count);
1024
1025			assert_last_event::<T>(
1026				Event::<T>::Leased {
1027					para_id: para,
1028					leaser,
1029					period_begin,
1030					period_count,
1031					extra_reserved: amount,
1032					total_amount: amount,
1033				}
1034				.into(),
1035			);
1036
1037			Ok(())
1038		}
1039
1040		// Worst case scenario, T on-demand parachains onboard, and C lease holding parachains
1041		// offboard. Assume reasonable maximum of 100 paras at any time
1042		#[benchmark]
1043		fn manage_lease_period_start(
1044			c: Linear<0, 100>,
1045			t: Linear<0, 100>,
1046		) -> Result<(), BenchmarkError> {
1047			let period_begin = 1u32.into();
1048			let period_count = 4u32.into();
1049
1050			// If there is an offset, we need to be on that block to be able to do lease things.
1051			frame_system::Pallet::<T>::set_block_number(T::LeaseOffset::get() + One::one());
1052
1053			// Make T parathreads (on-demand parachains)
1054			let paras_info = (0..t).map(|i| register_a_parathread::<T>(i)).collect::<Vec<_>>();
1055
1056			T::Registrar::execute_pending_transitions();
1057
1058			// T on-demand parachains are upgrading to lease holding parachains
1059			for (para, leaser) in paras_info {
1060				let amount = T::Currency::minimum_balance();
1061				let origin = T::ForceOrigin::try_successful_origin()
1062					.expect("ForceOrigin has no successful origin required for the benchmark");
1063				Slots::<T>::force_lease(origin, para, leaser, amount, period_begin, period_count)?;
1064			}
1065
1066			T::Registrar::execute_pending_transitions();
1067
1068			// C lease holding parachains are downgrading to on-demand parachains
1069			for i in 200..200 + c {
1070				let (para, _) = register_a_parathread::<T>(i);
1071				T::Registrar::make_parachain(para)?;
1072			}
1073
1074			T::Registrar::execute_pending_transitions();
1075
1076			for i in 0..t {
1077				assert!(T::Registrar::is_parathread(ParaId::from(i)));
1078			}
1079
1080			for i in 200..200 + c {
1081				assert!(T::Registrar::is_parachain(ParaId::from(i)));
1082			}
1083			#[block]
1084			{
1085				let _ = Slots::<T>::manage_lease_period_start(period_begin);
1086			}
1087
1088			// All paras should have switched.
1089			T::Registrar::execute_pending_transitions();
1090			for i in 0..t {
1091				assert!(T::Registrar::is_parachain(ParaId::from(i)));
1092			}
1093			for i in 200..200 + c {
1094				assert!(T::Registrar::is_parathread(ParaId::from(i)));
1095			}
1096
1097			Ok(())
1098		}
1099
1100		// Assume that at most 8 people have deposits for leases on a parachain.
1101		// This would cover at least 4 years of leases in the worst case scenario.
1102		#[benchmark]
1103		fn clear_all_leases() -> Result<(), BenchmarkError> {
1104			let max_people = 8;
1105			let (para, _) = register_a_parathread::<T>(1);
1106
1107			// If there is an offset, we need to be on that block to be able to do lease things.
1108			frame_system::Pallet::<T>::set_block_number(T::LeaseOffset::get() + One::one());
1109
1110			for i in 0..max_people {
1111				let leaser = account("lease_deposit", i, 0);
1112				let amount = T::Currency::minimum_balance();
1113				T::Currency::make_free_balance_be(&leaser, BalanceOf::<T>::max_value());
1114
1115				// Average slot has 4 lease periods.
1116				let period_count: LeasePeriodOf<T> = 4u32.into();
1117				let period_begin = period_count * i.into();
1118				let origin = T::ForceOrigin::try_successful_origin()
1119					.expect("ForceOrigin has no successful origin required for the benchmark");
1120				Slots::<T>::force_lease(origin, para, leaser, amount, period_begin, period_count)?;
1121			}
1122
1123			for i in 0..max_people {
1124				let leaser = account("lease_deposit", i, 0);
1125				assert_eq!(T::Currency::reserved_balance(&leaser), T::Currency::minimum_balance());
1126			}
1127
1128			let origin =
1129				T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1130
1131			#[extrinsic_call]
1132			_(origin as T::RuntimeOrigin, para);
1133
1134			for i in 0..max_people {
1135				let leaser = account("lease_deposit", i, 0);
1136				assert_eq!(T::Currency::reserved_balance(&leaser), 0u32.into());
1137			}
1138
1139			Ok(())
1140		}
1141
1142		#[benchmark]
1143		fn trigger_onboard() -> Result<(), BenchmarkError> {
1144			// get a parachain into a bad state where they did not onboard
1145			let (para, _) = register_a_parathread::<T>(1);
1146			Leases::<T>::insert(
1147				para,
1148				vec![Some((
1149					account::<T::AccountId>("lease_insert", 0, 0),
1150					BalanceOf::<T>::default(),
1151				))],
1152			);
1153			assert!(T::Registrar::is_parathread(para));
1154			let caller = whitelisted_caller();
1155
1156			#[extrinsic_call]
1157			_(RawOrigin::Signed(caller), para);
1158
1159			T::Registrar::execute_pending_transitions();
1160			assert!(T::Registrar::is_parachain(para));
1161			Ok(())
1162		}
1163
1164		impl_benchmark_test_suite!(
1165			Slots,
1166			crate::integration_tests::new_test_ext(),
1167			crate::integration_tests::Test,
1168		);
1169	}
1170}