slots.rs 31.8 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
24
//! 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.

use sp_std::prelude::*;
25
use sp_runtime::traits::{CheckedSub, Zero, CheckedConversion, Saturating};
26
use frame_support::{
27
28
	decl_module, decl_storage, decl_event, decl_error, dispatch::DispatchResult,
	traits::{Currency, ReservableCurrency, Get}, weights::Weight,
29
};
30
use primitives::v1::Id as ParaId;
31
use frame_system::{ensure_signed, ensure_root};
32
use crate::traits::{Leaser, LeaseError, Registrar};
Gavin Wood's avatar
Gavin Wood committed
33

34
type BalanceOf<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
35
36
37
38
39
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;
40
41
	fn clear_all_leases() -> Weight;
	fn trigger_onboard() -> Weight;
42
43
44
45
46
47
}

pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
	fn force_lease() -> Weight { 0 }
	fn manage_lease_period_start(_c: u32, _t:u32) -> Weight { 0 }
48
49
	fn clear_all_leases() -> Weight { 0 }
	fn trigger_onboard() -> Weight { 0 }
50
}
Gavin Wood's avatar
Gavin Wood committed
51
52

/// The module's configuration trait.
53
pub trait Config: frame_system::Config {
Gavin Wood's avatar
Gavin Wood committed
54
	/// The overarching event type.
55
	type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;
Gavin Wood's avatar
Gavin Wood committed
56
57
58
59
60

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

	/// The parachain registrar type.
61
	type Registrar: Registrar<AccountId=Self::AccountId>;
Gavin Wood's avatar
Gavin Wood committed
62
63
64

	/// The number of blocks over which a single period lasts.
	type LeasePeriod: Get<Self::BlockNumber>;
65

66
67
	/// Weight Information for the Extrinsics in the Pallet
	type WeightInfo: WeightInfo;
Gavin Wood's avatar
Gavin Wood committed
68
69
}

70
// This module's storage items.
Gavin Wood's avatar
Gavin Wood committed
71
decl_storage! {
72
	trait Store for Module<T: Config> as Slots {
73
		/// Amounts held on deposit for each (possibly future) leased parachain.
Gavin Wood's avatar
Gavin Wood committed
74
		///
75
76
77
78
		/// 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
Gavin Wood's avatar
Gavin Wood committed
79
80
81
82
83
84
		/// 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 module is concerned.
		///
		/// If a parachain doesn't exist *yet* but is scheduled to exist in the future, then it
85
		/// will be left-padded with one or more `None`s to denote the fact that nothing is held on
Gavin Wood's avatar
Gavin Wood committed
86
87
		/// deposit for the non-existent chain currently, but is held at some point in the future.
		///
88
89
		/// It is illegal for a `None` value to trail in the list.
		pub Leases get(fn lease): map hasher(twox_64_concat) ParaId => Vec<Option<(T::AccountId, BalanceOf<T>)>>;
Gavin Wood's avatar
Gavin Wood committed
90
91
92
93
94
	}
}

decl_event!(
	pub enum Event<T> where
95
		AccountId = <T as frame_system::Config>::AccountId,
Gavin Wood's avatar
Gavin Wood committed
96
		LeasePeriod = LeasePeriodOf<T>,
97
		ParaId = ParaId,
Gavin Wood's avatar
Gavin Wood committed
98
99
		Balance = BalanceOf<T>,
	{
100
		/// A new [lease_period] is beginning.
Gavin Wood's avatar
Gavin Wood committed
101
		NewLeasePeriod(LeasePeriod),
102
103
104
105
		/// 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.
		/// \[parachain_id, leaser, period_begin, period_count, extra_reserved, total_amount\]
106
		Leased(ParaId, AccountId, LeasePeriod, LeasePeriod, Balance, Balance),
Gavin Wood's avatar
Gavin Wood committed
107
108
109
	}
);

110
decl_error! {
111
	pub enum Error for Module<T: Config> {
112
113
		/// The parachain ID is not onboarding.
		ParaNotOnboarding,
114
115
		/// There was an error with the lease.
		LeaseError,
116
117
118
	}
}

Gavin Wood's avatar
Gavin Wood committed
119
decl_module! {
120
	pub struct Module<T: Config> for enum Call where origin: T::Origin {
121
122
		type Error = Error<T>;

123
124
		const LeasePeriod: T::BlockNumber = T::LeasePeriod::get();

thiolliere's avatar
thiolliere committed
125
		fn deposit_event() = default;
Gavin Wood's avatar
Gavin Wood committed
126

127
		fn on_initialize(n: T::BlockNumber) -> Weight {
128
			// If we're beginning a new lease period then handle that.
Gavin Wood's avatar
Gavin Wood committed
129
130
			let lease_period = T::LeasePeriod::get();
			if (n % lease_period).is_zero() {
131
132
133
134
				let lease_period_index = n / lease_period;
				Self::manage_lease_period_start(lease_period_index)
			} else {
				0
Gavin Wood's avatar
Gavin Wood committed
135
136
137
			}
		}

138
139
		/// Just a hotwire into the `lease_out` call, in case Root wants to force some lease to happen
		/// independently of any other on-chain mechanism to use it.
140
141
		///
		/// Can only be called by the Root origin.
142
		#[weight = T::WeightInfo::force_lease()]
143
		pub fn force_lease(origin,
144
145
146
147
148
149
			para: ParaId,
			leaser: T::AccountId,
			amount: BalanceOf<T>,
			period_begin: LeasePeriodOf<T>,
			period_count: LeasePeriodOf<T>,
		) -> DispatchResult {
150
			ensure_root(origin)?;
151
152
153
			Self::lease_out(para, &leaser, amount, period_begin, period_count)
				.map_err(|_| Error::<T>::LeaseError)?;
			Ok(())
Gavin Wood's avatar
Gavin Wood committed
154
		}
155
156
157
158

		/// Clear all leases for a Para Id, refunding any deposits back to the original owners.
		///
		/// Can only be called by the Root origin.
159
		#[weight = T::WeightInfo::clear_all_leases()]
160
		pub fn clear_all_leases(origin, para: ParaId) -> DispatchResult {
161
162
163
164
165
166
167
168
169
170
171
172
			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(())
		}
173
174
175
176
177
178
179
180
181

		/// 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.
		#[weight = T::WeightInfo::trigger_onboard()]
182
		pub fn trigger_onboard(origin, para: ParaId) -> DispatchResult {
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
			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.
				Some(Some(_lease_info)) => {
					T::Registrar::make_parachain(para)?
				},
				// Otherwise, it does not have a lease.
				Some(None) | None => {
					return Err(Error::<T>::ParaNotOnboarding.into());
				}
			};
			Ok(())
		}
198
199
	}
}
Gavin Wood's avatar
Gavin Wood committed
200

201
202
203
204
205
206
207
impl<T: Config> Module<T> {
	/// 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 {
		Self::deposit_event(RawEvent::NewLeasePeriod(lease_period_index));
Gavin Wood's avatar
Gavin Wood committed
208

209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
		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() {
			if lease_periods.is_empty() { continue }
			// ^^ 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
226

227
228
				// Remove the now-empty lease list.
				Leases::<T>::remove(para);
Gavin Wood's avatar
Gavin Wood committed
229
			} else {
230
				// The parachain entry has leased future periods.
231

232
233
234
				// We need to pop the first deposit entry, which corresponds to the now-
				// ended lease period.
				let maybe_ended_lease = lease_periods.remove(0);
235

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

238
239
240
241
242
				// 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
243

244
245
246
					// 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) {
247
						T::Currency::unreserve(&ended_lease.0, rebate);
248
249
					}
				}
Gavin Wood's avatar
Gavin Wood committed
250

251
252
253
				// 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
254
255
256
				}
			}
		}
257
		parachains.sort();
Gavin Wood's avatar
Gavin Wood committed
258

259
260
261
262
263
		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
264
265
266
			}
		}

267
268
269
270
271
		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
272
273
274
			}
		}

275
276
277
278
279
		T::WeightInfo::manage_lease_period_start(
			old_parachains.len() as u32,
			parachains.len() as u32,
		)
	}
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307

	// 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();
		Leases::<T>::get(para)
			.into_iter()
			.for_each(|lease| {
				match lease {
					Some((who, amount)) => {
						match tracker.get(&who) {
							Some(prev_amount) => {
								if amount > *prev_amount {
									tracker.insert(who, amount);
								}
							},
							None => {
								tracker.insert(who, amount);
							}
						}
					},
					None => {},
				}
			});

		tracker.into_iter().collect()
	}
308
}
Gavin Wood's avatar
Gavin Wood committed
309

310
311
312
313
314
315
316
impl<T: Config> crate::traits::OnSwap for Module<T> {
	fn on_swap(one: ParaId, other: ParaId) {
		Leases::<T>::mutate(one, |x|
			Leases::<T>::mutate(other, |y|
				sp_std::mem::swap(x, y)
			)
		)
Gavin Wood's avatar
Gavin Wood committed
317
	}
318
}
Gavin Wood's avatar
Gavin Wood committed
319

320
321
322
323
324
325
326
327
328
329
330
331
impl<T: Config> Leaser for Module<T> {
	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> {
332
		let current_lease_period = Self::lease_period_index();
333
334
335
		// 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
336
			.checked_sub(&current_lease_period)
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
			.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 {
				d.resize_with(offset, || { None });
			}
			let period_count_usize = period_count.checked_into::<usize>()
				.ok_or(LeaseError::AlreadyEnded)?;
			// Then place the deposit values for as long as the chain should exist.
			for i in offset .. (offset + period_count_usize) {
				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
361
					} else {
362
363
364
365
366
						// 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.
						return Err(LeaseError::AlreadyLeased);
Gavin Wood's avatar
Gavin Wood committed
367
					}
368
369
370
				} else if d.len() == i {
					// Doesn't exist. This is usual.
					d.push(Some((leaser.clone(), amount)));
Gavin Wood's avatar
Gavin Wood committed
371
				} else {
372
373
					// 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
374
				}
375
			}
Gavin Wood's avatar
Gavin Wood committed
376

377
378
379
380
381
382
			// 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
383
384
			}

385
			let reserved = maybe_additional.unwrap_or_default();
386
387
388
389
390
391
392
393

			// 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);
			}

394
395
			Self::deposit_event(
				RawEvent::Leased(para, leaser.clone(), period_begin, period_count, reserved, amount)
Gavin Wood's avatar
Gavin Wood committed
396
397
			);

398
399
400
			Ok(())
		})
	}
Gavin Wood's avatar
Gavin Wood committed
401

402
403
404
405
406
407
408
409
410
	fn deposit_held(para: ParaId, leaser: &Self::AccountId) -> <Self::Currency as Currency<Self::AccountId>>::Balance {
		Leases::<T>::get(para)
			.into_iter()
			.map(|lease| {
				match lease {
					Some((who, amount)) => {
						if &who == leaser { amount } else { Zero::zero() }
					},
					None => Zero::zero(),
Gavin Wood's avatar
Gavin Wood committed
411
				}
412
413
414
			})
			.max()
			.unwrap_or_else(Zero::zero)
Gavin Wood's avatar
Gavin Wood committed
415
416
	}

417
418
419
420
421
	fn lease_period() -> Self::LeasePeriod {
		T::LeasePeriod::get()
	}

	fn lease_period_index() -> Self::LeasePeriod {
422
		<frame_system::Pallet<T>>::block_number() / T::LeasePeriod::get()
Gavin Wood's avatar
Gavin Wood committed
423
	}
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458

	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);
		for slot in offset ..= offset + period_count {
			if let Some(Some(_)) = leases.get(slot) {
				// If there exists any lease period, we exit early and return true.
				return true
			}
		}

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

461

Gavin Wood's avatar
Gavin Wood committed
462
463
464
465
466
/// tests for this module
#[cfg(test)]
mod tests {
	use super::*;

467
	use sp_core::H256;
468
	use sp_runtime::traits::{BlakeTwo256, IdentityLookup};
469
	use frame_support::{
470
		parameter_types, assert_ok, assert_noop,
471
		traits::{OnInitialize, OnFinalize}
Gavin Wood's avatar
Gavin Wood committed
472
	};
473
	use pallet_balances;
474
475
	use primitives::v1::{BlockNumber, Header};
	use crate::{slots, mock::TestRegistrar};
476
477
478
479
480
481
482
483
484
485

	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,
		{
486
487
488
			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>},
489
490
		}
	);
Gavin Wood's avatar
Gavin Wood committed
491

492
	parameter_types! {
493
		pub const BlockHashCount: u32 = 250;
494
	}
495
	impl frame_system::Config for Test {
496
		type BaseCallFilter = ();
497
498
		type BlockWeights = ();
		type BlockLength = ();
Gavin Wood's avatar
Gavin Wood committed
499
		type Origin = Origin;
500
		type Call = Call;
Gavin Wood's avatar
Gavin Wood committed
501
		type Index = u64;
502
		type BlockNumber = BlockNumber;
Gavin Wood's avatar
Gavin Wood committed
503
504
505
506
507
		type Hash = H256;
		type Hashing = BlakeTwo256;
		type AccountId = u64;
		type Lookup = IdentityLookup<Self::AccountId>;
		type Header = Header;
508
		type Event = Event;
509
		type BlockHashCount = BlockHashCount;
510
		type DbWeight = ();
511
		type Version = ();
512
		type PalletInfo = PalletInfo;
513
		type AccountData = pallet_balances::AccountData<u64>;
Gavin Wood's avatar
Gavin Wood committed
514
		type OnNewAccount = ();
515
		type OnKilledAccount = ();
516
		type SystemWeightInfo = ();
517
		type SS58Prefix = ();
518
		type OnSetCode = ();
Gavin Wood's avatar
Gavin Wood committed
519
520
	}

Gavin Wood's avatar
Gavin Wood committed
521
	parameter_types! {
Shawn Tabrizi's avatar
Shawn Tabrizi committed
522
		pub const ExistentialDeposit: u64 = 1;
Gavin Wood's avatar
Gavin Wood committed
523
524
	}

525
	impl pallet_balances::Config for Test {
Gavin Wood's avatar
Gavin Wood committed
526
		type Balance = u64;
527
		type Event = Event;
Gavin Wood's avatar
Gavin Wood committed
528
		type DustRemoval = ();
Gavin Wood's avatar
Gavin Wood committed
529
		type ExistentialDeposit = ExistentialDeposit;
530
		type AccountStore = System;
531
		type WeightInfo = ();
532
		type MaxLocks = ();
Gavin Wood's avatar
Gavin Wood committed
533
534
		type MaxReserves = ();
		type ReserveIdentifier = [u8; 8];
Gavin Wood's avatar
Gavin Wood committed
535
536
	}

537
	parameter_types! {
538
		pub const LeasePeriod: BlockNumber = 10;
539
		pub const ParaDeposit: u64 = 1;
Gavin Wood's avatar
Gavin Wood committed
540
541
	}

542
	impl Config for Test {
543
		type Event = Event;
Gavin Wood's avatar
Gavin Wood committed
544
		type Currency = Balances;
545
		type Registrar = TestRegistrar<Test>;
Gavin Wood's avatar
Gavin Wood committed
546
		type LeasePeriod = LeasePeriod;
547
		type WeightInfo = crate::slots::TestWeightInfo;
Gavin Wood's avatar
Gavin Wood committed
548
549
550
551
	}

	// This function basically just builds a genesis storage key/value store according to
	// our desired mock up.
552
	pub fn new_test_ext() -> sp_io::TestExternalities {
553
		let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
554
		pallet_balances::GenesisConfig::<Test> {
Gavin Wood's avatar
Gavin Wood committed
555
			balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)],
556
		}.assimilate_storage(&mut t).unwrap();
Gavin Wood's avatar
Gavin Wood committed
557
558
559
		t.into()
	}

560
	fn run_to_block(n: BlockNumber) {
Gavin Wood's avatar
Gavin Wood committed
561
562
563
564
565
566
567
568
569
570
571
572
573
		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() {
574
		new_test_ext().execute_with(|| {
Gavin Wood's avatar
Gavin Wood committed
575
			run_to_block(1);
576
577
578
			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
579

580
581
			run_to_block(10);
			assert_eq!(Slots::lease_period_index(), 1);
Gavin Wood's avatar
Gavin Wood committed
582
583
584
585
		});
	}

	#[test]
586
	fn lease_lifecycle_works() {
587
		new_test_ext().execute_with(|| {
Gavin Wood's avatar
Gavin Wood committed
588
589
			run_to_block(1);

590
			assert_ok!(TestRegistrar::<Test>::register(1, ParaId::from(1), Default::default(), Default::default()));
Gavin Wood's avatar
Gavin Wood committed
591

592
			assert_ok!(Slots::lease_out(1.into(), &1, 1, 1, 1));
593
594
			assert_eq!(Slots::deposit_held(1.into(), &1), 1);
			assert_eq!(Balances::reserved_balance(1), 1);
Gavin Wood's avatar
Gavin Wood committed
595

596
597
			run_to_block(19);
			assert_eq!(Slots::deposit_held(1.into(), &1), 1);
Gavin Wood's avatar
Gavin Wood committed
598
			assert_eq!(Balances::reserved_balance(1), 1);
Gavin Wood's avatar
Gavin Wood committed
599

600
601
			run_to_block(20);
			assert_eq!(Slots::deposit_held(1.into(), &1), 0);
Gavin Wood's avatar
Gavin Wood committed
602
			assert_eq!(Balances::reserved_balance(1), 0);
603

604
605
606
607
			assert_eq!(TestRegistrar::<Test>::operations(), vec![
				(1.into(), 10, true),
				(1.into(), 20, false),
			]);
608
609
610
611
		});
	}

	#[test]
612
	fn lease_interrupted_lifecycle_works() {
613
		new_test_ext().execute_with(|| {
Gavin Wood's avatar
Gavin Wood committed
614
615
			run_to_block(1);

616
			assert_ok!(TestRegistrar::<Test>::register(1, ParaId::from(1), Default::default(), Default::default()));
Gavin Wood's avatar
Gavin Wood committed
617

618
619
			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
620

621
622
623
			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
624

625
626
627
			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
628

629
630
631
			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
632

633
634
635
			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
636

637
638
639
640
641
642
			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
643
644
645
646
		});
	}

	#[test]
647
	fn lease_relayed_lifecycle_works() {
648
		new_test_ext().execute_with(|| {
Gavin Wood's avatar
Gavin Wood committed
649
650
			run_to_block(1);

651
			assert_ok!(TestRegistrar::<Test>::register(1, ParaId::from(1), Default::default(), Default::default()));
Gavin Wood's avatar
Gavin Wood committed
652

653
654
655
656
657
658
			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
659

660
661
662
663
664
			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
665
666

			run_to_block(20);
667
668
669
670
			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
671

672
673
674
675
676
			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
677
678

			run_to_block(30);
679
680
681
682
			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
683

684
685
686
687
			assert_eq!(TestRegistrar::<Test>::operations(), vec![
				(1.into(), 10, true),
				(1.into(), 30, false),
			]);
Gavin Wood's avatar
Gavin Wood committed
688
689
690
691
		});
	}

	#[test]
692
	fn lease_deposit_increase_works() {
693
		new_test_ext().execute_with(|| {
Gavin Wood's avatar
Gavin Wood committed
694
695
			run_to_block(1);

696
			assert_ok!(TestRegistrar::<Test>::register(1, ParaId::from(1), Default::default(), Default::default()));
Gavin Wood's avatar
Gavin Wood committed
697

698
699
700
			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
701

702
703
704
			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
705

706
707
708
			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
709
710

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

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

	#[test]
722
	fn lease_deposit_decrease_works() {
723
		new_test_ext().execute_with(|| {
Gavin Wood's avatar
Gavin Wood committed
724
725
			run_to_block(1);

726
			assert_ok!(TestRegistrar::<Test>::register(1, ParaId::from(1), Default::default(), Default::default()));
Gavin Wood's avatar
Gavin Wood committed
727

728
729
730
			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
731

732
733
734
			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
735

736
737
738
			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
739

740
741
742
			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
743

744
745
746
			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
747

748
749
750
			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
751

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
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788

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

			assert_ok!(TestRegistrar::<Test>::register(1, ParaId::from(1), Default::default(), Default::default()));

			let max_num = 5u32;

			// max_num different people are reserved for leases to Para ID 1
			for i in 1u32 ..= max_num {
				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
			for i in 1u32 ..= max_num {
				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());
		});
	}
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844

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

			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()));

			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));

			assert_eq!(TestRegistrar::<Test>::operations(), vec![
				(1.into(), 20, true),
			]);
		});
	}

	#[test]
	fn trigger_onboard_works() {
		new_test_ext().execute_with(|| {
			run_to_block(1);
			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()));

			// 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
			assert_noop!(Slots::trigger_onboard(Origin::signed(1), 1.into()), Error::<Test>::ParaNotOnboarding);

			// 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
			assert_noop!(Slots::trigger_onboard(Origin::signed(1), 3.into()), Error::<Test>::ParaNotOnboarding);

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

			assert_eq!(TestRegistrar::<Test>::operations(), vec![
				(2.into(), 1, true),
			]);
		});
	}
845
}
846

847
848
849
850
851
852
853
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking {
	use super::*;
	use frame_system::RawOrigin;
	use frame_support::assert_ok;
	use sp_runtime::traits::Bounded;

854
	use frame_benchmarking::{benchmarks, account, whitelisted_caller, impl_benchmark_test_suite};
855
856
857
858

	use crate::slots::Module as Slots;

	fn assert_last_event<T: Config>(generic_event: <T as Config>::Event) {
859
		let events = frame_system::Pallet::<T>::events();
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
		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();

		assert_ok!(T::Registrar::register(leaser.clone(), para, worst_head_data, worst_validation_code));
		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();
885
			let period_begin = 69u32.into();
886
887
888
889
890
			let period_count = 3u32.into();
		}: _(RawOrigin::Root, para, leaser.clone(), amount, period_begin, period_count)
		verify {
			assert_last_event::<T>(RawEvent::Leased(para, leaser, period_begin, period_count, amount, amount).into());
		}
891

892
893
894
895
896
		// 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;
897

898
899
900
901
902
903
904
905
906
			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();
907

908
			// T parathread are upgrading to parachains
909
			for (para, leaser) in paras_info {
910
				let amount = T::Currency::minimum_balance();
911

912
913
				Slots::<T>::force_lease(RawOrigin::Root.into(), para, leaser, amount, period_begin, period_count)?;
			}
914

915
			T::Registrar::execute_pending_transitions();
916

917
918
919
920
921
			// 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)?;
			}
922

923
			T::Registrar::execute_pending_transitions();
924

925
926
927
			for i in 0 .. t {
				assert!(T::Registrar::is_parathread(ParaId::from(i)));
			}
928

929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
			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)));
			}
		}
944

945
946
947
948
949
		// 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);
950

951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
			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());
			}
973
		}
974

975
976
977
978
979
980
981
982
983
984
		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));
985
		}
986
	}
987
988
989
990
991
992

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