slots.rs 32.2 KB
Newer Older
Shawn Tabrizi's avatar
Shawn Tabrizi committed
1
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
Gavin Wood's avatar
Gavin Wood committed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// This file is part of Polkadot.

// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.

17
18
19
20
21
22
23
//! Parathread and parachains leasing system. Allows para IDs to be claimed, the code and data to be initialized and
//! parachain slots (i.e. continuous scheduling) to be leased. Also allows for parachains and parathreads to be
//! swapped.
//!
//! This doesn't handle the mechanics of determining which para ID actually ends up with a parachain lease. This
//! must handled by a separately, through the trait interface that this pallet provides or the root dispatchables.

24
use crate::traits::{LeaseError, Leaser, Registrar};
25
use frame_support::{
26
27
28
	pallet_prelude::*,
	traits::{Currency, ReservableCurrency},
	weights::Weight,
29
};
30
31
use frame_system::pallet_prelude::*;
pub use pallet::*;
32
use primitives::v1::Id as ParaId;
33
34
use sp_runtime::traits::{CheckedConversion, CheckedSub, Saturating, Zero};
use sp_std::prelude::*;
Gavin Wood's avatar
Gavin Wood committed
35

36
37
type BalanceOf<T> =
	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
38
39
40
41
42
type LeasePeriodOf<T> = <T as frame_system::Config>::BlockNumber;

pub trait WeightInfo {
	fn force_lease() -> Weight;
	fn manage_lease_period_start(c: u32, t: u32) -> Weight;
43
44
	fn clear_all_leases() -> Weight;
	fn trigger_onboard() -> Weight;
45
46
47
48
}

pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
49
50
51
52
53
54
55
56
57
58
59
60
	fn force_lease() -> Weight {
		0
	}
	fn manage_lease_period_start(_c: u32, _t: u32) -> Weight {
		0
	}
	fn clear_all_leases() -> Weight {
		0
	}
	fn trigger_onboard() -> Weight {
		0
	}
61
}
Gavin Wood's avatar
Gavin Wood committed
62

63
64
65
#[frame_support::pallet]
pub mod pallet {
	use super::*;
Gavin Wood's avatar
Gavin Wood committed
66

67
68
69
	#[pallet::pallet]
	#[pallet::generate_store(pub(super) trait Store)]
	pub struct Pallet<T>(_);
Gavin Wood's avatar
Gavin Wood committed
70

71
72
73
74
	#[pallet::config]
	pub trait Config: frame_system::Config {
		/// The overarching event type.
		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
Gavin Wood's avatar
Gavin Wood committed
75

76
77
		/// The currency type used for bidding.
		type Currency: ReservableCurrency<Self::AccountId>;
78

79
80
		/// The parachain registrar type.
		type Registrar: Registrar<AccountId = Self::AccountId>;
Gavin Wood's avatar
Gavin Wood committed
81

82
83
84
85
86
87
		/// The number of blocks over which a single period lasts.
		#[pallet::constant]
		type LeasePeriod: Get<Self::BlockNumber>;

		/// Weight Information for the Extrinsics in the Pallet
		type WeightInfo: WeightInfo;
Gavin Wood's avatar
Gavin Wood committed
88
89
	}

90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
	/// Amounts held on deposit for each (possibly future) leased parachain.
	///
	/// The actual amount locked on its behalf by any account at any time is the maximum of the second values
	/// of the items in this list whose first value is the account.
	///
	/// The first item in the list is the amount locked for the current Lease Period. Following
	/// items are for the subsequent lease periods.
	///
	/// The default value (an empty list) implies that the parachain no longer exists (or never
	/// existed) as far as this pallet is concerned.
	///
	/// If a parachain doesn't exist *yet* but is scheduled to exist in the future, then it
	/// will be left-padded with one or more `None`s to denote the fact that nothing is held on
	/// deposit for the non-existent chain currently, but is held at some point in the future.
	///
	/// It is illegal for a `None` value to trail in the list.
	#[pallet::storage]
	#[pallet::getter(fn lease)]
	pub type Leases<T: Config> =
		StorageMap<_, Twox64Concat, ParaId, Vec<Option<(T::AccountId, BalanceOf<T>)>>, ValueQuery>;

	#[pallet::event]
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
	#[pallet::metadata(
		T::AccountId = "AccountId",
		LeasePeriodOf<T> = "LeasePeriod",
		BalanceOf<T> = "Balance",
	)]
	pub enum Event<T: Config> {
Denis_P's avatar
Denis_P committed
119
		/// A new `[lease_period]` is beginning.
120
		NewLeasePeriod(LeasePeriodOf<T>),
121
122
123
		/// A para has won the right to a continuous set of lease periods as a parachain.
		/// First balance is any extra amount reserved on top of the para's existing deposit.
		/// Second balance is the total amount reserved.
Denis_P's avatar
Denis_P committed
124
		/// `[parachain_id, leaser, period_begin, period_count, extra_reserved, total_amount]`
125
126
127
128
129
130
131
132
		Leased(
			ParaId,
			T::AccountId,
			LeasePeriodOf<T>,
			LeasePeriodOf<T>,
			BalanceOf<T>,
			BalanceOf<T>,
		),
Gavin Wood's avatar
Gavin Wood committed
133
134
	}

135
136
	#[pallet::error]
	pub enum Error<T> {
137
138
		/// The parachain ID is not onboarding.
		ParaNotOnboarding,
139
140
		/// There was an error with the lease.
		LeaseError,
141
	}
Gavin Wood's avatar
Gavin Wood committed
142

143
144
	#[pallet::hooks]
	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
145
		fn on_initialize(n: T::BlockNumber) -> Weight {
146
			// If we're beginning a new lease period then handle that.
Gavin Wood's avatar
Gavin Wood committed
147
148
			let lease_period = T::LeasePeriod::get();
			if (n % lease_period).is_zero() {
149
150
151
152
				let lease_period_index = n / lease_period;
				Self::manage_lease_period_start(lease_period_index)
			} else {
				0
Gavin Wood's avatar
Gavin Wood committed
153
154
			}
		}
155
	}
Gavin Wood's avatar
Gavin Wood committed
156

157
158
159
	#[pallet::call]
	impl<T: Config> Pallet<T> {
		/// Just a connect into the `lease_out` call, in case Root wants to force some lease to happen
160
		/// independently of any other on-chain mechanism to use it.
161
162
		///
		/// Can only be called by the Root origin.
163
164
165
		#[pallet::weight(T::WeightInfo::force_lease())]
		pub fn force_lease(
			origin: OriginFor<T>,
166
167
168
169
170
171
			para: ParaId,
			leaser: T::AccountId,
			amount: BalanceOf<T>,
			period_begin: LeasePeriodOf<T>,
			period_count: LeasePeriodOf<T>,
		) -> DispatchResult {
172
			ensure_root(origin)?;
173
174
175
			Self::lease_out(para, &leaser, amount, period_begin, period_count)
				.map_err(|_| Error::<T>::LeaseError)?;
			Ok(())
Gavin Wood's avatar
Gavin Wood committed
176
		}
177
178
179
180

		/// Clear all leases for a Para Id, refunding any deposits back to the original owners.
		///
		/// Can only be called by the Root origin.
181
182
		#[pallet::weight(T::WeightInfo::clear_all_leases())]
		pub fn clear_all_leases(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
183
184
185
186
187
188
189
190
191
192
193
194
			ensure_root(origin)?;
			let deposits = Self::all_deposits_held(para);

			// Refund any deposits for these leases
			for (who, deposit) in deposits {
				let err_amount = T::Currency::unreserve(&who, deposit);
				debug_assert!(err_amount.is_zero());
			}

			Leases::<T>::remove(para);
			Ok(())
		}
195
196
197
198
199
200
201
202

		/// Try to onboard a parachain that has a lease for the current lease period.
		///
		/// This function can be useful if there was some state issue with a para that should
		/// have onboarded, but was unable to. As long as they have a lease period, we can
		/// let them onboard from here.
		///
		/// Origin must be signed, but can be called by anyone.
203
204
		#[pallet::weight(T::WeightInfo::trigger_onboard())]
		pub fn trigger_onboard(origin: OriginFor<T>, para: ParaId) -> DispatchResult {
205
206
207
208
209
			let _ = ensure_signed(origin)?;
			let leases = Leases::<T>::get(para);
			match leases.first() {
				// If the first element in leases is present, then it has a lease!
				// We can try to onboard it.
210
				Some(Some(_lease_info)) => T::Registrar::make_parachain(para)?,
211
				// Otherwise, it does not have a lease.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
212
				Some(None) | None => return Err(Error::<T>::ParaNotOnboarding.into()),
213
214
215
			};
			Ok(())
		}
216
217
	}
}
Gavin Wood's avatar
Gavin Wood committed
218

219
impl<T: Config> Pallet<T> {
220
221
222
223
224
	/// A new lease period is beginning. We're at the start of the first block of it.
	///
	/// We need to on-board and off-board parachains as needed. We should also handle reducing/
	/// returning deposits.
	fn manage_lease_period_start(lease_period_index: LeasePeriodOf<T>) -> Weight {
225
		Self::deposit_event(Event::<T>::NewLeasePeriod(lease_period_index));
Gavin Wood's avatar
Gavin Wood committed
226

227
228
229
230
231
		let old_parachains = T::Registrar::parachains();

		// Figure out what chains need bringing on.
		let mut parachains = Vec::new();
		for (para, mut lease_periods) in Leases::<T>::iter() {
232
			if lease_periods.is_empty() {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
233
				continue
234
			}
235
236
237
238
239
240
241
242
243
244
245
			// ^^ should never be empty since we would have deleted the entry otherwise.

			if lease_periods.len() == 1 {
				// Just one entry, which corresponds to the now-ended lease period.
				//
				// `para` is now just a parathread.
				//
				// Unreserve whatever is left.
				if let Some((who, value)) = &lease_periods[0] {
					T::Currency::unreserve(&who, *value);
				}
Gavin Wood's avatar
Gavin Wood committed
246

247
248
				// Remove the now-empty lease list.
				Leases::<T>::remove(para);
Gavin Wood's avatar
Gavin Wood committed
249
			} else {
250
				// The parachain entry has leased future periods.
251

252
253
254
				// We need to pop the first deposit entry, which corresponds to the now-
				// ended lease period.
				let maybe_ended_lease = lease_periods.remove(0);
255

256
				Leases::<T>::insert(para, &lease_periods);
Gavin Wood's avatar
Gavin Wood committed
257

258
259
260
261
262
				// If we *were* active in the last period and so have ended a lease...
				if let Some(ended_lease) = maybe_ended_lease {
					// Then we need to get the new amount that should continue to be held on
					// deposit for the parachain.
					let now_held = Self::deposit_held(para, &ended_lease.0);
Gavin Wood's avatar
Gavin Wood committed
263

264
265
266
					// If this is less than what we were holding for this leaser's now-ended lease, then
					// unreserve it.
					if let Some(rebate) = ended_lease.1.checked_sub(&now_held) {
267
						T::Currency::unreserve(&ended_lease.0, rebate);
268
269
					}
				}
Gavin Wood's avatar
Gavin Wood committed
270

271
272
273
				// If we have an active lease in the new period, then add to the current parachains
				if lease_periods[0].is_some() {
					parachains.push(para);
Gavin Wood's avatar
Gavin Wood committed
274
275
276
				}
			}
		}
277
		parachains.sort();
Gavin Wood's avatar
Gavin Wood committed
278

279
280
281
282
283
		for para in parachains.iter() {
			if old_parachains.binary_search(para).is_err() {
				// incoming.
				let res = T::Registrar::make_parachain(*para);
				debug_assert!(res.is_ok());
Gavin Wood's avatar
Gavin Wood committed
284
285
286
			}
		}

287
288
289
290
291
		for para in old_parachains.iter() {
			if parachains.binary_search(para).is_err() {
				// outgoing.
				let res = T::Registrar::make_parathread(*para);
				debug_assert!(res.is_ok());
Gavin Wood's avatar
Gavin Wood committed
292
293
294
			}
		}

295
296
297
298
299
		T::WeightInfo::manage_lease_period_start(
			old_parachains.len() as u32,
			parachains.len() as u32,
		)
	}
300
301
302
303
304
305

	// Return a vector of (user, balance) for all deposits for a parachain.
	// Useful when trying to clean up a parachain leases, as this would tell
	// you all the balances you need to unreserve.
	fn all_deposits_held(para: ParaId) -> Vec<(T::AccountId, BalanceOf<T>)> {
		let mut tracker = sp_std::collections::btree_map::BTreeMap::new();
306
307
		Leases::<T>::get(para).into_iter().for_each(|lease| match lease {
			Some((who, amount)) => match tracker.get(&who) {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
308
				Some(prev_amount) =>
309
310
					if amount > *prev_amount {
						tracker.insert(who, amount);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
311
					},
312
313
				None => {
					tracker.insert(who, amount);
Shawn Tabrizi's avatar
Shawn Tabrizi committed
314
				},
315
			},
Shawn Tabrizi's avatar
Shawn Tabrizi committed
316
			None => {},
317
		});
318
319
320

		tracker.into_iter().collect()
	}
321
}
Gavin Wood's avatar
Gavin Wood committed
322

323
impl<T: Config> crate::traits::OnSwap for Pallet<T> {
324
	fn on_swap(one: ParaId, other: ParaId) {
325
		Leases::<T>::mutate(one, |x| Leases::<T>::mutate(other, |y| sp_std::mem::swap(x, y)))
Gavin Wood's avatar
Gavin Wood committed
326
	}
327
}
Gavin Wood's avatar
Gavin Wood committed
328

329
impl<T: Config> Leaser for Pallet<T> {
330
331
332
333
334
335
336
337
338
339
340
	type AccountId = T::AccountId;
	type LeasePeriod = T::BlockNumber;
	type Currency = T::Currency;

	fn lease_out(
		para: ParaId,
		leaser: &Self::AccountId,
		amount: <Self::Currency as Currency<Self::AccountId>>::Balance,
		period_begin: Self::LeasePeriod,
		period_count: Self::LeasePeriod,
	) -> Result<(), LeaseError> {
341
		let current_lease_period = Self::lease_period_index();
342
343
344
		// Finally, we update the deposit held so it is `amount` for the new lease period
		// indices that were won in the auction.
		let offset = period_begin
345
			.checked_sub(&current_lease_period)
346
347
348
349
350
351
352
353
354
355
356
357
358
			.and_then(|x| x.checked_into::<usize>())
			.ok_or(LeaseError::AlreadyEnded)?;

		// offset is the amount into the `Deposits` items list that our lease begins. `period_count`
		// is the number of items that it lasts for.

		// The lease period index range (begin, end) that newly belongs to this parachain
		// ID. We need to ensure that it features in `Deposits` to prevent it from being
		// reaped too early (any managed parachain whose `Deposits` set runs low will be
		// removed).
		Leases::<T>::try_mutate(para, |d| {
			// Left-pad with `None`s as necessary.
			if d.len() < offset {
359
				d.resize_with(offset, || None);
360
			}
361
362
			let period_count_usize =
				period_count.checked_into::<usize>().ok_or(LeaseError::AlreadyEnded)?;
363
			// Then place the deposit values for as long as the chain should exist.
364
			for i in offset..(offset + period_count_usize) {
365
366
367
368
369
				if d.len() > i {
					// Already exists but it's `None`. That means a later slot was already leased.
					// No problem.
					if d[i] == None {
						d[i] = Some((leaser.clone(), amount));
Gavin Wood's avatar
Gavin Wood committed
370
					} else {
371
372
373
374
						// The chain tried to lease the same period twice. This might be a griefing
						// attempt.
						//
						// We bail, not giving any lease and leave it for governance to sort out.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
375
						return Err(LeaseError::AlreadyLeased)
Gavin Wood's avatar
Gavin Wood committed
376
					}
377
378
379
				} else if d.len() == i {
					// Doesn't exist. This is usual.
					d.push(Some((leaser.clone(), amount)));
Gavin Wood's avatar
Gavin Wood committed
380
				} else {
381
382
					// earlier resize means it must be >= i; qed
					// defensive code though since we really don't want to panic here.
Gavin Wood's avatar
Gavin Wood committed
383
				}
384
			}
Gavin Wood's avatar
Gavin Wood committed
385

386
387
388
389
390
391
			// Figure out whether we already have some funds of `leaser` held in reserve for `para_id`.
			//  If so, then we can deduct those from the amount that we need to reserve.
			let maybe_additional = amount.checked_sub(&Self::deposit_held(para, &leaser));
			if let Some(ref additional) = maybe_additional {
				T::Currency::reserve(&leaser, *additional)
					.map_err(|_| LeaseError::ReserveFailed)?;
Gavin Wood's avatar
Gavin Wood committed
392
393
			}

394
			let reserved = maybe_additional.unwrap_or_default();
395
396
397
398
399
400
401
402

			// Check if current lease period is same as period begin, and onboard them directly.
			// This will allow us to support onboarding new parachains in the middle of a lease period.
			if current_lease_period == period_begin {
				// Best effort. Not much we can do if this fails.
				let _ = T::Registrar::make_parachain(para);
			}

403
404
405
406
407
408
409
410
			Self::deposit_event(Event::<T>::Leased(
				para,
				leaser.clone(),
				period_begin,
				period_count,
				reserved,
				amount,
			));
Gavin Wood's avatar
Gavin Wood committed
411

412
413
414
			Ok(())
		})
	}
Gavin Wood's avatar
Gavin Wood committed
415

416
417
418
419
	fn deposit_held(
		para: ParaId,
		leaser: &Self::AccountId,
	) -> <Self::Currency as Currency<Self::AccountId>>::Balance {
420
421
		Leases::<T>::get(para)
			.into_iter()
422
			.map(|lease| match lease {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
423
				Some((who, amount)) =>
424
425
426
427
					if &who == leaser {
						amount
					} else {
						Zero::zero()
Shawn Tabrizi's avatar
Shawn Tabrizi committed
428
					},
429
				None => Zero::zero(),
430
431
432
			})
			.max()
			.unwrap_or_else(Zero::zero)
Gavin Wood's avatar
Gavin Wood committed
433
434
	}

435
436
437
438
439
	fn lease_period() -> Self::LeasePeriod {
		T::LeasePeriod::get()
	}

	fn lease_period_index() -> Self::LeasePeriod {
440
		<frame_system::Pallet<T>>::block_number() / T::LeasePeriod::get()
Gavin Wood's avatar
Gavin Wood committed
441
	}
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466

	fn already_leased(
		para_id: ParaId,
		first_period: Self::LeasePeriod,
		last_period: Self::LeasePeriod,
	) -> bool {
		let current_lease_period = Self::lease_period_index();

		// Can't look in the past, so we pick whichever is the biggest.
		let start_period = first_period.max(current_lease_period);
		// Find the offset to look into the lease period list.
		// Subtraction is safe because of max above.
		let offset = match (start_period - current_lease_period).checked_into::<usize>() {
			Some(offset) => offset,
			None => return true,
		};

		// This calculates how deep we should look in the vec for a potential lease.
		let period_count = match last_period.saturating_sub(start_period).checked_into::<usize>() {
			Some(period_count) => period_count,
			None => return true,
		};

		// Get the leases, and check each item in the vec which is part of the range we are checking.
		let leases = Leases::<T>::get(para_id);
467
		for slot in offset..=offset + period_count {
468
469
			if let Some(Some(_)) = leases.get(slot) {
				// If there exists any lease period, we exit early and return true.
Shawn Tabrizi's avatar
Shawn Tabrizi committed
470
				return true
471
472
473
474
475
476
			}
		}

		// If we got here, then we did not find any overlapping leases.
		false
	}
Gavin Wood's avatar
Gavin Wood committed
477
478
}

479
/// tests for this pallet
Gavin Wood's avatar
Gavin Wood committed
480
481
482
483
#[cfg(test)]
mod tests {
	use super::*;

484
485
	use crate::{mock::TestRegistrar, slots};
	use frame_support::{assert_noop, assert_ok, parameter_types};
486
	use pallet_balances;
487
	use primitives::v1::{BlockNumber, Header};
488
489
	use sp_core::H256;
	use sp_runtime::traits::{BlakeTwo256, IdentityLookup};
490
491
492
493
494
495
496
497
498
499

	type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
	type Block = frame_system::mocking::MockBlock<Test>;

	frame_support::construct_runtime!(
		pub enum Test where
			Block = Block,
			NodeBlock = Block,
			UncheckedExtrinsic = UncheckedExtrinsic,
		{
500
501
502
			System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
			Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
			Slots: slots::{Pallet, Call, Storage, Event<T>},
503
504
		}
	);
Gavin Wood's avatar
Gavin Wood committed
505

506
	parameter_types! {
507
		pub const BlockHashCount: u32 = 250;
508
	}
509
	impl frame_system::Config for Test {
510
		type BaseCallFilter = frame_support::traits::Everything;
511
512
		type BlockWeights = ();
		type BlockLength = ();
Gavin Wood's avatar
Gavin Wood committed
513
		type Origin = Origin;
514
		type Call = Call;
Gavin Wood's avatar
Gavin Wood committed
515
		type Index = u64;
516
		type BlockNumber = BlockNumber;
Gavin Wood's avatar
Gavin Wood committed
517
518
519
520
521
		type Hash = H256;
		type Hashing = BlakeTwo256;
		type AccountId = u64;
		type Lookup = IdentityLookup<Self::AccountId>;
		type Header = Header;
522
		type Event = Event;
523
		type BlockHashCount = BlockHashCount;
524
		type DbWeight = ();
525
		type Version = ();
526
		type PalletInfo = PalletInfo;
527
		type AccountData = pallet_balances::AccountData<u64>;
Gavin Wood's avatar
Gavin Wood committed
528
		type OnNewAccount = ();
529
		type OnKilledAccount = ();
530
		type SystemWeightInfo = ();
531
		type SS58Prefix = ();
532
		type OnSetCode = ();
Gavin Wood's avatar
Gavin Wood committed
533
534
	}

Gavin Wood's avatar
Gavin Wood committed
535
	parameter_types! {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
536
		pub const ExistentialDeposit: u64 = 1;
Gavin Wood's avatar
Gavin Wood committed
537
538
	}

539
	impl pallet_balances::Config for Test {
Gavin Wood's avatar
Gavin Wood committed
540
		type Balance = u64;
541
		type Event = Event;
Gavin Wood's avatar
Gavin Wood committed
542
		type DustRemoval = ();
Gavin Wood's avatar
Gavin Wood committed
543
		type ExistentialDeposit = ExistentialDeposit;
544
		type AccountStore = System;
545
		type WeightInfo = ();
546
		type MaxLocks = ();
Gavin Wood's avatar
Gavin Wood committed
547
548
		type MaxReserves = ();
		type ReserveIdentifier = [u8; 8];
Gavin Wood's avatar
Gavin Wood committed
549
550
	}

551
	parameter_types! {
552
		pub const LeasePeriod: BlockNumber = 10;
553
		pub const ParaDeposit: u64 = 1;
Gavin Wood's avatar
Gavin Wood committed
554
555
	}

556
	impl Config for Test {
557
		type Event = Event;
Gavin Wood's avatar
Gavin Wood committed
558
		type Currency = Balances;
559
		type Registrar = TestRegistrar<Test>;
Gavin Wood's avatar
Gavin Wood committed
560
		type LeasePeriod = LeasePeriod;
561
		type WeightInfo = crate::slots::TestWeightInfo;
Gavin Wood's avatar
Gavin Wood committed
562
563
564
565
	}

	// This function basically just builds a genesis storage key/value store according to
	// our desired mock up.
566
	pub fn new_test_ext() -> sp_io::TestExternalities {
567
		let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
568
		pallet_balances::GenesisConfig::<Test> {
Gavin Wood's avatar
Gavin Wood committed
569
			balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)],
570
571
572
		}
		.assimilate_storage(&mut t)
		.unwrap();
Gavin Wood's avatar
Gavin Wood committed
573
574
575
		t.into()
	}

576
	fn run_to_block(n: BlockNumber) {
Gavin Wood's avatar
Gavin Wood committed
577
578
579
580
581
582
583
584
585
586
587
588
589
		while System::block_number() < n {
			Slots::on_finalize(System::block_number());
			Balances::on_finalize(System::block_number());
			System::on_finalize(System::block_number());
			System::set_block_number(System::block_number() + 1);
			System::on_initialize(System::block_number());
			Balances::on_initialize(System::block_number());
			Slots::on_initialize(System::block_number());
		}
	}

	#[test]
	fn basic_setup_works() {
590
		new_test_ext().execute_with(|| {
Gavin Wood's avatar
Gavin Wood committed
591
			run_to_block(1);
592
593
594
			assert_eq!(Slots::lease_period(), 10);
			assert_eq!(Slots::lease_period_index(), 0);
			assert_eq!(Slots::deposit_held(1.into(), &1), 0);
Gavin Wood's avatar
Gavin Wood committed
595

596
597
			run_to_block(10);
			assert_eq!(Slots::lease_period_index(), 1);
Gavin Wood's avatar
Gavin Wood committed
598
599
600
601
		});
	}

	#[test]
602
	fn lease_lifecycle_works() {
603
		new_test_ext().execute_with(|| {
Gavin Wood's avatar
Gavin Wood committed
604
605
			run_to_block(1);

606
607
608
609
610
611
			assert_ok!(TestRegistrar::<Test>::register(
				1,
				ParaId::from(1),
				Default::default(),
				Default::default()
			));
Gavin Wood's avatar
Gavin Wood committed
612

613
			assert_ok!(Slots::lease_out(1.into(), &1, 1, 1, 1));
614
615
			assert_eq!(Slots::deposit_held(1.into(), &1), 1);
			assert_eq!(Balances::reserved_balance(1), 1);
Gavin Wood's avatar
Gavin Wood committed
616

617
618
			run_to_block(19);
			assert_eq!(Slots::deposit_held(1.into(), &1), 1);
Gavin Wood's avatar
Gavin Wood committed
619
			assert_eq!(Balances::reserved_balance(1), 1);
Gavin Wood's avatar
Gavin Wood committed
620

621
622
			run_to_block(20);
			assert_eq!(Slots::deposit_held(1.into(), &1), 0);
Gavin Wood's avatar
Gavin Wood committed
623
			assert_eq!(Balances::reserved_balance(1), 0);
624

625
626
627
628
			assert_eq!(
				TestRegistrar::<Test>::operations(),
				vec![(1.into(), 10, true), (1.into(), 20, false),]
			);
629
630
631
632
		});
	}

	#[test]
633
	fn lease_interrupted_lifecycle_works() {
634
		new_test_ext().execute_with(|| {
Gavin Wood's avatar
Gavin Wood committed
635
636
			run_to_block(1);

637
638
639
640
641
642
			assert_ok!(TestRegistrar::<Test>::register(
				1,
				ParaId::from(1),
				Default::default(),
				Default::default()
			));
Gavin Wood's avatar
Gavin Wood committed
643

644
645
			assert_ok!(Slots::lease_out(1.into(), &1, 6, 1, 1));
			assert_ok!(Slots::lease_out(1.into(), &1, 4, 3, 1));
Gavin Wood's avatar
Gavin Wood committed
646

647
648
649
			run_to_block(19);
			assert_eq!(Slots::deposit_held(1.into(), &1), 6);
			assert_eq!(Balances::reserved_balance(1), 6);
Gavin Wood's avatar
Gavin Wood committed
650

651
652
653
			run_to_block(20);
			assert_eq!(Slots::deposit_held(1.into(), &1), 4);
			assert_eq!(Balances::reserved_balance(1), 4);
Gavin Wood's avatar
Gavin Wood committed
654

655
656
657
			run_to_block(39);
			assert_eq!(Slots::deposit_held(1.into(), &1), 4);
			assert_eq!(Balances::reserved_balance(1), 4);
Gavin Wood's avatar
Gavin Wood committed
658

659
660
661
			run_to_block(40);
			assert_eq!(Slots::deposit_held(1.into(), &1), 0);
			assert_eq!(Balances::reserved_balance(1), 0);
Gavin Wood's avatar
Gavin Wood committed
662

663
664
665
666
667
668
669
670
671
			assert_eq!(
				TestRegistrar::<Test>::operations(),
				vec![
					(1.into(), 10, true),
					(1.into(), 20, false),
					(1.into(), 30, true),
					(1.into(), 40, false),
				]
			);
Gavin Wood's avatar
Gavin Wood committed
672
673
674
675
		});
	}

	#[test]
676
	fn lease_relayed_lifecycle_works() {
677
		new_test_ext().execute_with(|| {
Gavin Wood's avatar
Gavin Wood committed
678
679
			run_to_block(1);

680
681
682
683
684
685
			assert_ok!(TestRegistrar::<Test>::register(
				1,
				ParaId::from(1),
				Default::default(),
				Default::default()
			));
Gavin Wood's avatar
Gavin Wood committed
686

687
688
689
690
691
692
			assert!(Slots::lease_out(1.into(), &1, 6, 1, 1).is_ok());
			assert!(Slots::lease_out(1.into(), &2, 4, 2, 1).is_ok());
			assert_eq!(Slots::deposit_held(1.into(), &1), 6);
			assert_eq!(Balances::reserved_balance(1), 6);
			assert_eq!(Slots::deposit_held(1.into(), &2), 4);
			assert_eq!(Balances::reserved_balance(2), 4);
Gavin Wood's avatar
Gavin Wood committed
693

694
695
696
697
698
			run_to_block(19);
			assert_eq!(Slots::deposit_held(1.into(), &1), 6);
			assert_eq!(Balances::reserved_balance(1), 6);
			assert_eq!(Slots::deposit_held(1.into(), &2), 4);
			assert_eq!(Balances::reserved_balance(2), 4);
Gavin Wood's avatar
Gavin Wood committed
699
700

			run_to_block(20);
701
702
703
704
			assert_eq!(Slots::deposit_held(1.into(), &1), 0);
			assert_eq!(Balances::reserved_balance(1), 0);
			assert_eq!(Slots::deposit_held(1.into(), &2), 4);
			assert_eq!(Balances::reserved_balance(2), 4);
Gavin Wood's avatar
Gavin Wood committed
705

706
707
708
709
710
			run_to_block(29);
			assert_eq!(Slots::deposit_held(1.into(), &1), 0);
			assert_eq!(Balances::reserved_balance(1), 0);
			assert_eq!(Slots::deposit_held(1.into(), &2), 4);
			assert_eq!(Balances::reserved_balance(2), 4);
Gavin Wood's avatar
Gavin Wood committed
711
712

			run_to_block(30);
713
714
715
716
			assert_eq!(Slots::deposit_held(1.into(), &1), 0);
			assert_eq!(Balances::reserved_balance(1), 0);
			assert_eq!(Slots::deposit_held(1.into(), &2), 0);
			assert_eq!(Balances::reserved_balance(2), 0);
Gavin Wood's avatar
Gavin Wood committed
717

718
719
720
721
			assert_eq!(
				TestRegistrar::<Test>::operations(),
				vec![(1.into(), 10, true), (1.into(), 30, false),]
			);
Gavin Wood's avatar
Gavin Wood committed
722
723
724
725
		});
	}

	#[test]
726
	fn lease_deposit_increase_works() {
727
		new_test_ext().execute_with(|| {
Gavin Wood's avatar
Gavin Wood committed
728
729
			run_to_block(1);

730
731
732
733
734
735
			assert_ok!(TestRegistrar::<Test>::register(
				1,
				ParaId::from(1),
				Default::default(),
				Default::default()
			));
Gavin Wood's avatar
Gavin Wood committed
736

737
738
739
			assert!(Slots::lease_out(1.into(), &1, 4, 1, 1).is_ok());
			assert_eq!(Slots::deposit_held(1.into(), &1), 4);
			assert_eq!(Balances::reserved_balance(1), 4);
Gavin Wood's avatar
Gavin Wood committed
740

741
742
743
			assert!(Slots::lease_out(1.into(), &1, 6, 2, 1).is_ok());
			assert_eq!(Slots::deposit_held(1.into(), &1), 6);
			assert_eq!(Balances::reserved_balance(1), 6);
Gavin Wood's avatar
Gavin Wood committed
744

745
746
747
			run_to_block(29);
			assert_eq!(Slots::deposit_held(1.into(), &1), 6);
			assert_eq!(Balances::reserved_balance(1), 6);
Gavin Wood's avatar
Gavin Wood committed
748
749

			run_to_block(30);
750
751
			assert_eq!(Slots::deposit_held(1.into(), &1), 0);
			assert_eq!(Balances::reserved_balance(1), 0);
Gavin Wood's avatar
Gavin Wood committed
752

753
754
755
756
			assert_eq!(
				TestRegistrar::<Test>::operations(),
				vec![(1.into(), 10, true), (1.into(), 30, false),]
			);
Gavin Wood's avatar
Gavin Wood committed
757
758
759
760
		});
	}

	#[test]
761
	fn lease_deposit_decrease_works() {
762
		new_test_ext().execute_with(|| {
Gavin Wood's avatar
Gavin Wood committed
763
764
			run_to_block(1);

765
766
767
768
769
770
			assert_ok!(TestRegistrar::<Test>::register(
				1,
				ParaId::from(1),
				Default::default(),
				Default::default()
			));
Gavin Wood's avatar
Gavin Wood committed
771

772
773
774
			assert!(Slots::lease_out(1.into(), &1, 6, 1, 1).is_ok());
			assert_eq!(Slots::deposit_held(1.into(), &1), 6);
			assert_eq!(Balances::reserved_balance(1), 6);
Gavin Wood's avatar
Gavin Wood committed
775

776
777
778
			assert!(Slots::lease_out(1.into(), &1, 4, 2, 1).is_ok());
			assert_eq!(Slots::deposit_held(1.into(), &1), 6);
			assert_eq!(Balances::reserved_balance(1), 6);
Gavin Wood's avatar
Gavin Wood committed
779

780
781
782
			run_to_block(19);
			assert_eq!(Slots::deposit_held(1.into(), &1), 6);
			assert_eq!(Balances::reserved_balance(1), 6);
Gavin Wood's avatar
Gavin Wood committed
783

784
785
786
			run_to_block(20);
			assert_eq!(Slots::deposit_held(1.into(), &1), 4);
			assert_eq!(Balances::reserved_balance(1), 4);
Gavin Wood's avatar
Gavin Wood committed
787

788
789
790
			run_to_block(29);
			assert_eq!(Slots::deposit_held(1.into(), &1), 4);
			assert_eq!(Balances::reserved_balance(1), 4);
Gavin Wood's avatar
Gavin Wood committed
791

792
793
794
			run_to_block(30);
			assert_eq!(Slots::deposit_held(1.into(), &1), 0);
			assert_eq!(Balances::reserved_balance(1), 0);
Gavin Wood's avatar
Gavin Wood committed
795

796
797
798
799
			assert_eq!(
				TestRegistrar::<Test>::operations(),
				vec![(1.into(), 10, true), (1.into(), 30, false),]
			);
800
		});
Gavin Wood's avatar
Gavin Wood committed
801
	}
802
803
804
805
806
807

	#[test]
	fn clear_all_leases_works() {
		new_test_ext().execute_with(|| {
			run_to_block(1);

808
809
810
811
812
813
			assert_ok!(TestRegistrar::<Test>::register(
				1,
				ParaId::from(1),
				Default::default(),
				Default::default()
			));
814
815
816
817

			let max_num = 5u32;

			// max_num different people are reserved for leases to Para ID 1
818
			for i in 1u32..=max_num {
819
820
821
822
823
824
825
826
827
				let j: u64 = i.into();
				assert_ok!(Slots::lease_out(1.into(), &j, j * 10, i * i, i));
				assert_eq!(Slots::deposit_held(1.into(), &j), j * 10);
				assert_eq!(Balances::reserved_balance(j), j * 10);
			}

			assert_ok!(Slots::clear_all_leases(Origin::root(), 1.into()));

			// Balances cleaned up correctly
828
			for i in 1u32..=max_num {
829
830
831
832
833
834
835
836
837
				let j: u64 = i.into();
				assert_eq!(Slots::deposit_held(1.into(), &j), 0);
				assert_eq!(Balances::reserved_balance(j), 0);
			}

			// Leases is empty.
			assert!(Leases::<Test>::get(ParaId::from(1)).is_empty());
		});
	}
838
839
840
841
842
843

	#[test]
	fn lease_out_current_lease_period() {
		new_test_ext().execute_with(|| {
			run_to_block(1);

844
845
846
847
848
849
850
851
852
853
854
855
			assert_ok!(TestRegistrar::<Test>::register(
				1,
				ParaId::from(1),
				Default::default(),
				Default::default()
			));
			assert_ok!(TestRegistrar::<Test>::register(
				1,
				ParaId::from(2),
				Default::default(),
				Default::default()
			));
856
857
858
859
860
861
862
863
864
865

			run_to_block(20);
			assert_eq!(Slots::lease_period_index(), 2);
			// Can't lease from the past
			assert!(Slots::lease_out(1.into(), &1, 1, 1, 1).is_err());
			// Lease in the current period triggers onboarding
			assert_ok!(Slots::lease_out(1.into(), &1, 1, 2, 1));
			// Lease in the future doesn't
			assert_ok!(Slots::lease_out(2.into(), &1, 1, 3, 1));

866
			assert_eq!(TestRegistrar::<Test>::operations(), vec![(1.into(), 20, true),]);
867
868
869
870
871
872
873
		});
	}

	#[test]
	fn trigger_onboard_works() {
		new_test_ext().execute_with(|| {
			run_to_block(1);
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
			assert_ok!(TestRegistrar::<Test>::register(
				1,
				ParaId::from(1),
				Default::default(),
				Default::default()
			));
			assert_ok!(TestRegistrar::<Test>::register(
				1,
				ParaId::from(2),
				Default::default(),
				Default::default()
			));
			assert_ok!(TestRegistrar::<Test>::register(
				1,
				ParaId::from(3),
				Default::default(),
				Default::default()
			));
892
893
894
895
896
897
898
899
900

			// We will directly manipulate leases to emulate some kind of failure in the system.
			// Para 1 will have no leases
			// Para 2 will have a lease period in the current index
			Leases::<Test>::insert(ParaId::from(2), vec![Some((0, 0))]);
			// Para 3 will have a lease period in a future index
			Leases::<Test>::insert(ParaId::from(3), vec![None, None, Some((0, 0))]);

			// Para 1 should fail cause they don't have any leases
901
902
903
904
			assert_noop!(
				Slots::trigger_onboard(Origin::signed(1), 1.into()),
				Error::<Test>::ParaNotOnboarding
			);
905
906
907
908
909

			// Para 2 should succeed
			assert_ok!(Slots::trigger_onboard(Origin::signed(1), 2.into()));

			// Para 3 should fail cause their lease is in the future
910
911
912
913
			assert_noop!(
				Slots::trigger_onboard(Origin::signed(1), 3.into()),
				Error::<Test>::ParaNotOnboarding
			);
914
915
916
917

			// Trying Para 2 again should fail cause they are not currently a parathread
			assert!(Slots::trigger_onboard(Origin::signed(1), 2.into()).is_err());

918
			assert_eq!(TestRegistrar::<Test>::operations(), vec![(2.into(), 1, true),]);
919
920
		});
	}
921
}
922

923
924
925
926
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking {
	use super::*;
	use frame_support::assert_ok;
927
	use frame_system::RawOrigin;
928
929
	use sp_runtime::traits::Bounded;

930
	use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller};
931

932
	use crate::slots::Pallet as Slots;
933
934

	fn assert_last_event<T: Config>(generic_event: <T as Config>::Event) {
935
		let events = frame_system::Pallet::<T>::events();
936
937
938
939
940
941
942
943
944
945
946
947
948
		let system_event: <T as frame_system::Config>::Event = generic_event.into();
		// compare to the last event record
		let frame_system::EventRecord { event, .. } = &events[events.len() - 1];
		assert_eq!(event, &system_event);
	}

	fn register_a_parathread<T: Config>(i: u32) -> (ParaId, T::AccountId) {
		let para = ParaId::from(i);
		let leaser: T::AccountId = account("leaser", i, 0);
		T::Currency::make_free_balance_be(&leaser, BalanceOf::<T>::max_value());
		let worst_head_data = T::Registrar::worst_head_data();
		let worst_validation_code = T::Registrar::worst_validation_code();

949
950
951
952
953
954
		assert_ok!(T::Registrar::register(
			leaser.clone(),
			para,
			worst_head_data,
			worst_validation_code
		));
955
956
957
958
959
960
961
962
963
964
965
		T::Registrar::execute_pending_transitions();

		(para, leaser)
	}

	benchmarks! {
		force_lease {
			let para = ParaId::from(1337);
			let leaser: T::AccountId = account("leaser", 0, 0);
			T::Currency::make_free_balance_be(&leaser, BalanceOf::<T>::max_value());
			let amount = T::Currency::minimum_balance();
966
			let period_begin = 69u32.into();
967
968
969
			let period_count = 3u32.into();
		}: _(RawOrigin::Root, para, leaser.clone(), amount, period_begin, period_count)
		verify {
970
			assert_last_event::<T>(Event::<T>::Leased(para, leaser, period_begin, period_count, amount, amount).into());
971
		}
972

973
974
975
976
977
		// Worst case scenario, T parathreads onboard, and C parachains offboard.
		manage_lease_period_start {
			// Assume reasonable maximum of 100 paras at any time
			let c in 1 .. 100;
			let t in 1 .. 100;
978

979
980
981
982
983
984
985
986
987
			let period_begin = 1u32.into();
			let period_count = 4u32.into();

			// Make T parathreads
			let paras_info = (0..t).map(|i| {
				register_a_parathread::<T>(i)
			}).collect::<Vec<_>>();

			T::Registrar::execute_pending_transitions();
988

989
			// T parathread are upgrading to parachains
990
			for (para, leaser) in paras_info {
991
				let amount = T::Currency::minimum_balance();
992

993
994
				Slots::<T>::force_lease(RawOrigin::Root.into(), para, leaser, amount, period_begin, period_count)?;
			}
995

996
			T::Registrar::execute_pending_transitions();
997

998
999
1000
1001
1002
			// C parachains are downgrading to parathreads
			for i in 200 .. 200 + c {
				let (para, leaser) = register_a_parathread::<T>(i);
				T::Registrar::make_parachain(para)?;
			}
1003

1004
			T::Registrar::execute_pending_transitions();
1005

1006
1007
1008
			for i in 0 .. t {
				assert!(T::Registrar::is_parathread(ParaId::from(i)));
			}
1009

1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
			for i in 200 .. 200 + c {
				assert!(T::Registrar::is_parachain(ParaId::from(i)));
			}
		}: {
				Slots::<T>::manage_lease_period_start(period_begin);
		} verify {
			// All paras should have switched.
			T::Registrar::execute_pending_transitions();
			for i in 0 .. t {
				assert!(T::Registrar::is_parachain(ParaId::from(i)));
			}
			for i in 200 .. 200 + c {
				assert!(T::Registrar::is_parathread(ParaId::from(i)));
			}
		}
1025

1026
1027
1028
1029
1030
		// Assume that at most 8 people have deposits for leases on a parachain.
		// This would cover at least 4 years of leases in the worst case scenario.
		clear_all_leases {
			let max_people = 8;
			let (para, _) = register_a_parathread::<T>(1);
1031

1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
			for i in 0 .. max_people {
				let leaser = account("lease_deposit", i, 0);
				let amount = T::Currency::minimum_balance();
				T::Currency::make_free_balance_be(&leaser, BalanceOf::<T>::max_value());

				// Average slot has 4 lease periods.
				let period_count: LeasePeriodOf<T> = 4u32.into();
				let period_begin = period_count * i.into();
				Slots::<T>::force_lease(RawOrigin::Root.into(), para, leaser, amount, period_begin, period_count)?;
			}

			for i in 0 .. max_people {
				let leaser = account("lease_deposit", i, 0);
				assert_eq!(T::Currency::reserved_balance(&leaser), T::Currency::minimum_balance());
			}

		}: _(RawOrigin::Root, para)
		verify {
			for i in 0 .. max_people {
				let leaser = account("lease_deposit", i, 0);
				assert_eq!(T::Currency::reserved_balance(&leaser), 0u32.into());
			}
1054
		}
1055

1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
		trigger_onboard {
			// get a parachain into a bad state where they did not onboard
			let (para, _) = register_a_parathread::<T>(1);
			Leases::<T>::insert(para, vec![Some((T::AccountId::default(), BalanceOf::<T>::default()))]);
			assert!(T::Registrar::is_parathread(para));
			let caller = whitelisted_caller();
		}: _(RawOrigin::Signed(caller), para)
		verify {
			T::Registrar::execute_pending_transitions();
			assert!(T::Registrar::is_parachain(para));
1066
		}
1067
	}
1068
1069
1070
1071
1072
1073

	impl_benchmark_test_suite!(
		Slots,
		crate::integration_tests::new_test_ext(),
		crate::integration_tests::Test,
	);
Gavin Wood's avatar
Gavin Wood committed
1074
}