From 85693d6883b8366823d2217934e40d82ad872457 Mon Sep 17 00:00:00 2001
From: Shawn Tabrizi <shawntabrizi@gmail.com>
Date: Sun, 4 Apr 2021 13:11:37 +0200
Subject: [PATCH] Create Macro for Implementing `SlotRange` (#2788)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Macro for generating `SlotRange`

* remove test code

* refactor slots into crate

* add no_std flag

* finish macro

* make compile

* fix copyright date

* don't assume lease periods per slot

* Update runtime/common/Cargo.toml

* Apply suggestions from code review

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* fixes

* Update lib.rs

* tests

* Move consts into struct

* docs

* fix compile

* Apply suggestions from code review

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update Cargo.lock

* Update runtime/common/src/slot_range.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* fixes

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>
Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
---
 polkadot/Cargo.lock                           |  11 +
 polkadot/Cargo.toml                           |   1 +
 polkadot/runtime/common/Cargo.toml            |   2 +
 .../common/slot_range_helper/Cargo.toml       |  19 ++
 .../common/slot_range_helper/src/lib.rs       | 289 ++++++++++++++++++
 polkadot/runtime/common/src/auctions.rs       |  25 +-
 polkadot/runtime/common/src/crowdloan.rs      |   7 +-
 polkadot/runtime/common/src/lib.rs            |   2 +-
 polkadot/runtime/common/src/slot_range.rs     | 137 +--------
 9 files changed, 343 insertions(+), 150 deletions(-)
 create mode 100644 polkadot/runtime/common/slot_range_helper/Cargo.toml
 create mode 100644 polkadot/runtime/common/slot_range_helper/src/lib.rs

diff --git a/polkadot/Cargo.lock b/polkadot/Cargo.lock
index c71c4fa51ce..ffa8f21cec5 100644
--- a/polkadot/Cargo.lock
+++ b/polkadot/Cargo.lock
@@ -6261,6 +6261,7 @@ dependencies = [
  "serde",
  "serde_derive",
  "serde_json",
+ "slot-range-helper",
  "sp-api",
  "sp-application-crypto",
  "sp-core",
@@ -8732,6 +8733,16 @@ version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
 
+[[package]]
+name = "slot-range-helper"
+version = "0.8.30"
+dependencies = [
+ "parity-scale-codec",
+ "paste 1.0.4",
+ "sp-runtime",
+ "sp-std",
+]
+
 [[package]]
 name = "smallvec"
 version = "0.6.13"
diff --git a/polkadot/Cargo.toml b/polkadot/Cargo.toml
index 7d97405d09e..0f02019ca2d 100644
--- a/polkadot/Cargo.toml
+++ b/polkadot/Cargo.toml
@@ -34,6 +34,7 @@ members = [
 	"erasure-coding",
 	"primitives",
 	"runtime/common",
+	"runtime/common/slot_range_helper",
 	"runtime/parachains",
 	"runtime/polkadot",
 	"runtime/kusama",
diff --git a/polkadot/runtime/common/Cargo.toml b/polkadot/runtime/common/Cargo.toml
index e2e8e621f49..ec44dbc13ad 100644
--- a/polkadot/runtime/common/Cargo.toml
+++ b/polkadot/runtime/common/Cargo.toml
@@ -46,6 +46,7 @@ primitives = { package = "polkadot-primitives", path = "../../primitives", defau
 libsecp256k1 = { version = "0.3.5", default-features = false }
 runtime-parachains = { package = "polkadot-runtime-parachains", path = "../parachains", default-features = false }
 
+slot-range-helper = { path = "slot_range_helper", default-features = false }
 xcm = { path = "../../xcm", default-features = false }
 
 [dev-dependencies]
@@ -91,6 +92,7 @@ std = [
 	"pallet-vesting/std",
 	"pallet-transaction-payment/std",
 	"pallet-treasury/std",
+	"slot-range-helper/std",
 	"sp-runtime/std",
 	"sp-session/std",
 	"sp-staking/std",
diff --git a/polkadot/runtime/common/slot_range_helper/Cargo.toml b/polkadot/runtime/common/slot_range_helper/Cargo.toml
new file mode 100644
index 00000000000..eff28d19c91
--- /dev/null
+++ b/polkadot/runtime/common/slot_range_helper/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "slot-range-helper"
+version = "0.8.30"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2018"
+
+[dependencies]
+paste = "1.0"
+parity-scale-codec = { version = "2.0.0", default-features = false, features = ["derive"] }
+sp-std = { package = "sp-std", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
+sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
+
+[features]
+default = ["std"]
+std = [
+	"sp-std/std",
+	"parity-scale-codec/std",
+	"sp-runtime/std",
+]
diff --git a/polkadot/runtime/common/slot_range_helper/src/lib.rs b/polkadot/runtime/common/slot_range_helper/src/lib.rs
new file mode 100644
index 00000000000..ffad684b33a
--- /dev/null
+++ b/polkadot/runtime/common/slot_range_helper/src/lib.rs
@@ -0,0 +1,289 @@
+// Copyright 2021 Parity Technologies (UK) Ltd.
+// 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/>.
+
+//! A helper macro for generating SlotRange enum.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+pub use sp_std::{result, ops::Add, convert::TryInto};
+pub use sp_runtime::traits::CheckedSub;
+pub use parity_scale_codec::{Encode, Decode};
+pub use paste;
+
+/// This macro generates a `SlotRange` enum of arbitrary length for use in the Slot Auction
+/// mechanism on Polkadot.
+///
+/// Usage:
+/// ```
+/// slot_range_helper::generate_slot_range!(Zero(0), One(1), Two(2), Three(3));
+/// ```
+///
+/// To extend the usage, continue to add `Identifier(value)` items to the macro.
+///
+/// This will generate an enum `SlotRange` with the following properties:
+///
+/// * Enum variants will range from all consecutive combinations of inputs, i.e.
+///   `ZeroZero`, `ZeroOne`, `ZeroTwo`, `ZeroThree`, `OneOne`, `OneTwo`, `OneThree`...
+/// * A constant `LEASE_PERIODS_PER_SLOT` will count the number of lease periods.
+/// * A constant `SLOT_RANGE_COUNT` will count the total number of enum variants.
+/// * A function `as_pair` will return a tuple representation of the `SlotRange`.
+/// * A function `intersects` will tell you if two slot ranges intersect with one another.
+/// * A function `len` will tell you the length of occupying a `SlotRange`.
+/// * A function `new_bounded` will generate a `SlotRange` from an input of the current
+///   lease period, the starting lease period, and the final lease period.
+#[macro_export]
+macro_rules! generate_slot_range{
+	// Entry point
+	($( $x:ident ( $e:expr ) ),*) => {
+		$crate::generate_lease_period_per_slot!( $( $x )* );
+		$crate::generate_slot_range!(@inner
+			{ }
+			$( $x ( $e ) )*
+		);
+	};
+	// Does the magic...
+	(@inner
+		{ $( $parsed:ident ( $t1:expr, $t2:expr ) )* }
+		$current:ident ( $ce:expr )
+		$( $remaining:ident ( $re:expr ) )*
+	) => {
+		$crate::paste::paste! {
+			$crate::generate_slot_range!(@inner
+				{
+					$( $parsed ( $t1, $t2 ) )*
+					[< $current $current >] ( $ce, $ce )
+					$( [< $current $remaining >] ($ce, $re) )*
+				}
+				$( $remaining ( $re ) )*
+			);
+		}
+	};
+	(@inner
+		{ $( $parsed:ident ( $t1:expr, $t2:expr ) )* }
+	) => {
+		$crate::generate_slot_range_enum!(@inner $( $parsed )* );
+
+		$crate::generate_slot_range_count!( $( $parsed )* );
+
+		#[cfg(feature = "std")]
+		impl std::fmt::Debug for SlotRange {
+			fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+				let p = self.as_pair();
+				write!(fmt, "[{}..{}]", p.0, p.1)
+			}
+		}
+
+		impl SlotRange {
+			pub const LEASE_PERIODS_PER_SLOT: usize = LEASE_PERIODS_PER_SLOT;
+			pub const SLOT_RANGE_COUNT: usize = SLOT_RANGE_COUNT;
+
+			$crate::generate_slot_range_as_pair!(@inner $( $parsed ( $t1, $t2 ) )* );
+
+			$crate::generate_slot_range_len!(@inner $( $parsed ( $t1, $t2 ) )* );
+
+			$crate::generate_slot_range_new_bounded!(@inner $( $parsed ( $t1, $t2 ) )* );
+		}
+	};
+}
+
+#[macro_export]
+#[doc(hidden)]
+macro_rules! generate_slot_range_enum {
+	(@inner
+		$( $parsed:ident )*
+	) => {
+		/// A compactly represented sub-range from the series.
+		#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, $crate::Encode, $crate::Decode)]
+		#[repr(u8)]
+		pub enum SlotRange { $( $parsed ),* }
+	};
+}
+
+#[macro_export]
+#[doc(hidden)]
+macro_rules! generate_slot_range_as_pair {
+	(@inner
+		$( $parsed:ident ( $t1:expr, $t2:expr ) )*
+	) => {
+		/// Return true if two `SlotRange` intersect in their lease periods.
+		pub fn intersects(&self, other: SlotRange) -> bool {
+			let a = self.as_pair();
+			let b = other.as_pair();
+			b.0 <= a.1 && a.0 <= b.1
+			// == !(b.0 > a.1 || a.0 > b.1)
+		}
+
+		/// Return a tuple representation of the `SlotRange`.
+		///
+		/// Example:`SlotRange::OneTwo.as_pair() == (1, 2)`
+		pub fn as_pair(&self) -> (u8, u8) {
+			match self {
+				$( SlotRange::$parsed => { ($t1, $t2) } )*
+			}
+		}
+	};
+}
+
+#[macro_export]
+#[doc(hidden)]
+macro_rules! generate_slot_range_len {
+	// Use evaluated length in function.
+	(@inner
+		$( $parsed:ident ( $t1:expr, $t2:expr ) )*
+	) => {
+		/// Return the length of occupying a `SlotRange`.
+		///
+		/// Example:`SlotRange::OneTwo.len() == 2`
+		pub fn len(&self) -> usize {
+			match self {
+				// len (0, 2) = 2 - 0 + 1 = 3
+				$( SlotRange::$parsed => { ( $t2 - $t1 + 1) } )*
+			}
+		}
+	};
+}
+
+#[macro_export]
+#[doc(hidden)]
+macro_rules! generate_slot_range_new_bounded {
+	(@inner
+		$( $parsed:ident ( $t1:expr, $t2:expr ) )*
+	) => {
+		/// Construct a `SlotRange` from the current lease period, the first lease period of the range,
+		/// and the last lease period of the range.
+		///
+		/// For example: `SlotRange::new_bounded(1, 2, 3) == SlotRange::OneTwo`.
+		pub fn new_bounded<
+			Index: $crate::Add<Output=Index> + $crate::CheckedSub + Copy + Ord + From<u32> + $crate::TryInto<u32>
+		>(
+			current: Index,
+			first: Index,
+			last: Index
+		) -> $crate::result::Result<Self, &'static str> {
+			if first > last || first < current || last >= current + (LEASE_PERIODS_PER_SLOT as u32).into() {
+				return Err("Invalid range for this auction")
+			}
+			let count: u32 = last.checked_sub(&first)
+				.ok_or("range ends before it begins")?
+				.try_into()
+				.map_err(|_| "range too big")?;
+			let first: u32 = first.checked_sub(&current)
+				.ok_or("range begins too early")?
+				.try_into()
+				.map_err(|_| "start too far")?;
+			match (first, first + count) {
+				$( ($t1, $t2) => { Ok(SlotRange::$parsed) })*
+				_ => Err("bad range"),
+			}
+		}
+	};
+}
+
+#[macro_export]
+#[doc(hidden)]
+macro_rules! generate_slot_range_count {
+	(
+		$start:ident $( $rest:ident )*
+	) => {
+		$crate::generate_slot_range_count!(@inner 1; $( $rest )*);
+	};
+	(@inner
+		$count:expr;
+		$start:ident $( $rest:ident )*
+	) => {
+		$crate::generate_slot_range_count!(@inner $count + 1; $( $rest )*);
+	};
+	(@inner
+		$count:expr;
+	) => {
+		const SLOT_RANGE_COUNT: usize = $count;
+	};
+}
+
+#[macro_export]
+#[doc(hidden)]
+macro_rules! generate_lease_period_per_slot {
+	(
+		$start:ident $( $rest:ident )*
+	) => {
+		$crate::generate_lease_period_per_slot!(@inner 1; $( $rest )*);
+	};
+	(@inner
+		$count:expr;
+		$start:ident $( $rest:ident )*
+	) => {
+		$crate::generate_lease_period_per_slot!(@inner $count + 1; $( $rest )*);
+	};
+	(@inner
+		$count:expr;
+	) => {
+		const LEASE_PERIODS_PER_SLOT: usize = $count;
+	};
+}
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+
+	#[test]
+	fn slot_range_4_works() {
+		generate_slot_range!(Zero(0), One(1), Two(2), Three(3));
+
+		assert_eq!(SlotRange::LEASE_PERIODS_PER_SLOT, 4);
+		// Sum over n from 0 - 4
+		assert_eq!(SlotRange::SLOT_RANGE_COUNT, 10);
+		assert_eq!(SlotRange::new_bounded(0u32, 1u32, 2u32).unwrap(), SlotRange::OneTwo);
+		assert_eq!(SlotRange::new_bounded(5u32, 6u32, 7u32).unwrap(), SlotRange::OneTwo);
+		assert!(SlotRange::new_bounded(10u32, 6u32, 7u32).is_err());
+		assert!(SlotRange::new_bounded(10u32, 16u32, 17u32).is_err());
+		assert!(SlotRange::new_bounded(10u32, 11u32, 10u32).is_err());
+		assert_eq!(SlotRange::TwoTwo.len(), 1);
+		assert_eq!(SlotRange::OneTwo.len(), 2);
+		assert_eq!(SlotRange::ZeroThree.len(), 4);
+		assert!(SlotRange::ZeroOne.intersects(SlotRange::OneThree));
+		assert!(!SlotRange::ZeroOne.intersects(SlotRange::TwoThree));
+		assert_eq!(SlotRange::ZeroZero.as_pair(), (0, 0));
+		assert_eq!(SlotRange::OneThree.as_pair(), (1, 3));
+	}
+
+	#[test]
+	fn slot_range_8_works() {
+		generate_slot_range!(Zero(0), One(1), Two(2), Three(3), Four(4), Five(5), Six(6), Seven(7));
+
+		assert_eq!(SlotRange::LEASE_PERIODS_PER_SLOT, 8);
+		// Sum over n from 0 to 8
+		assert_eq!(SlotRange::SLOT_RANGE_COUNT, 36);
+		assert_eq!(SlotRange::new_bounded(0u32, 1u32, 2u32).unwrap(), SlotRange::OneTwo);
+		assert_eq!(SlotRange::new_bounded(5u32, 6u32, 7u32).unwrap(), SlotRange::OneTwo);
+		assert!(SlotRange::new_bounded(10u32, 6u32, 7u32).is_err());
+		// This one passes with slot range 8
+		assert_eq!(SlotRange::new_bounded(10u32, 16u32, 17u32).unwrap(), SlotRange::SixSeven);
+		assert!(SlotRange::new_bounded(10u32, 17u32, 18u32).is_err());
+		assert!(SlotRange::new_bounded(10u32, 20u32, 21u32).is_err());
+		assert!(SlotRange::new_bounded(10u32, 11u32, 10u32).is_err());
+		assert_eq!(SlotRange::TwoTwo.len(), 1);
+		assert_eq!(SlotRange::OneTwo.len(), 2);
+		assert_eq!(SlotRange::ZeroThree.len(), 4);
+		assert_eq!(SlotRange::ZeroSeven.len(), 8);
+		assert!(SlotRange::ZeroOne.intersects(SlotRange::OneThree));
+		assert!(!SlotRange::ZeroOne.intersects(SlotRange::TwoThree));
+		assert!(SlotRange::FiveSix.intersects(SlotRange::SixSeven));
+		assert!(!SlotRange::ThreeFive.intersects(SlotRange::SixSeven));
+		assert_eq!(SlotRange::ZeroZero.as_pair(), (0, 0));
+		assert_eq!(SlotRange::OneThree.as_pair(), (1, 3));
+		assert_eq!(SlotRange::SixSeven.as_pair(), (6, 7));
+	}
+}
diff --git a/polkadot/runtime/common/src/auctions.rs b/polkadot/runtime/common/src/auctions.rs
index ec88de800c1..a3abc5e11ce 100644
--- a/polkadot/runtime/common/src/auctions.rs
+++ b/polkadot/runtime/common/src/auctions.rs
@@ -27,12 +27,13 @@ use frame_support::{
 };
 use primitives::v1::Id as ParaId;
 use frame_system::{ensure_signed, ensure_root};
-use crate::slot_range::{SlotRange, SLOT_RANGE_COUNT};
+use crate::slot_range::SlotRange;
 use crate::traits::{Leaser, LeaseError, Auctioneer, Registrar};
 use parity_scale_codec::Decode;
 
 type CurrencyOf<T> = <<T as Config>::Leaser as Leaser>::Currency;
-type BalanceOf<T> = <<<T as Config>::Leaser as Leaser>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
+type BalanceOf<T> =
+	<<<T as Config>::Leaser as Leaser>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
 
 pub trait WeightInfo {
 	fn new_auction() -> Weight;
@@ -84,7 +85,7 @@ pub type AuctionIndex = u32;
 type LeasePeriodOf<T> = <<T as Config>::Leaser as Leaser>::LeasePeriod;
 // Winning data type. This encodes the top bidders of each range together with their bid.
 type WinningData<T> =
-	[Option<(<T as frame_system::Config>::AccountId, ParaId, BalanceOf<T>)>; SLOT_RANGE_COUNT];
+	[Option<(<T as frame_system::Config>::AccountId, ParaId, BalanceOf<T>)>; SlotRange::SLOT_RANGE_COUNT];
 // Winners data type. This encodes each of the final winners of a parachain auction, the parachain
 // index assigned to them, their winning bid and the range that they won.
 type WinnersData<T> = Vec<(<T as frame_system::Config>::AccountId, ParaId, BalanceOf<T>, SlotRange)>;
@@ -563,19 +564,19 @@ impl<T: Config> Module<T> {
 	) -> WinnersData<T> {
 		let winning_ranges = {
 			let mut best_winners_ending_at:
-				[(Vec<SlotRange>, BalanceOf<T>); 4] = Default::default();
+				[(Vec<SlotRange>, BalanceOf<T>); SlotRange::LEASE_PERIODS_PER_SLOT] = Default::default();
 			let best_bid = |range: SlotRange| {
 				winning[range as u8 as usize].as_ref()
 					.map(|(_, _, amount)| *amount * (range.len() as u32).into())
 			};
-			for i in 0..4 {
+			for i in 0..SlotRange::LEASE_PERIODS_PER_SLOT {
 				let r = SlotRange::new_bounded(0, 0, i as u32).expect("`i < 4`; qed");
 				if let Some(bid) = best_bid(r) {
 					best_winners_ending_at[i] = (vec![r], bid);
 				}
 				for j in 0..i {
 					let r = SlotRange::new_bounded(0, j as u32 + 1, i as u32)
-						.expect("`i < 4`; `j < i`; `j + 1 < 4`; qed");
+						.expect("`i < LPPS`; `j < i`; `j + 1 < LPPS`; qed");
 					if let Some(mut bid) = best_bid(r) {
 						bid += best_winners_ending_at[j].1;
 						if bid > best_winners_ending_at[i].1 {
@@ -590,8 +591,7 @@ impl<T: Config> Module<T> {
 					}
 				}
 			}
-			let [_, _, _, (winning_ranges, _)] = best_winners_ending_at;
-			winning_ranges
+			best_winners_ending_at[SlotRange::LEASE_PERIODS_PER_SLOT - 1].0.clone()
 		};
 
 		winning_ranges.into_iter().map(|range| {
@@ -722,7 +722,10 @@ mod tests {
 			})
 		}
 
-		fn deposit_held(para: ParaId, leaser: &Self::AccountId) -> <Self::Currency as Currency<Self::AccountId>>::Balance {
+		fn deposit_held(
+			para: ParaId,
+			leaser: &Self::AccountId
+		) -> <Self::Currency as Currency<Self::AccountId>>::Balance {
 			leases().iter()
 				.filter_map(|((id, _period), data)|
 					if id == &para && &data.leaser == leaser { Some(data.amount) } else { None }
@@ -1546,7 +1549,7 @@ mod benchmarking {
 		let auction_index = AuctionCounter::get();
 		let minimum_balance = CurrencyOf::<T>::minimum_balance();
 
-		for n in 1 ..= SLOT_RANGE_COUNT as u32 {
+		for n in 1 ..= SlotRange::SLOT_RANGE_COUNT as u32 {
 			let owner = account("owner", n, 0);
 			let worst_validation_code = T::Registrar::worst_validation_code();
 			let worst_head_data = T::Registrar::worst_head_data();
@@ -1562,7 +1565,7 @@ mod benchmarking {
 
 		T::Registrar::execute_pending_transitions();
 
-		for n in 1 ..= SLOT_RANGE_COUNT as u32 {
+		for n in 1 ..= SlotRange::SLOT_RANGE_COUNT as u32 {
 			let bidder = account("bidder", n, 0);
 			CurrencyOf::<T>::make_free_balance_be(&bidder, BalanceOf::<T>::max_value());
 
diff --git a/polkadot/runtime/common/src/crowdloan.rs b/polkadot/runtime/common/src/crowdloan.rs
index 3af668c43c3..adf699c9162 100644
--- a/polkadot/runtime/common/src/crowdloan.rs
+++ b/polkadot/runtime/common/src/crowdloan.rs
@@ -73,6 +73,7 @@ use sp_runtime::{
 	},
 };
 use crate::traits::{Registrar, Auctioneer};
+use crate::slot_range::SlotRange;
 use parity_scale_codec::{Encode, Decode};
 use sp_std::vec::Vec;
 use primitives::v1::Id as ParaId;
@@ -318,7 +319,9 @@ decl_module! {
 			let depositor = ensure_signed(origin)?;
 
 			ensure!(first_period <= last_period, Error::<T>::LastPeriodBeforeFirstPeriod);
-			let last_period_limit = first_period.checked_add(&3u32.into()).ok_or(Error::<T>::FirstPeriodTooFarInFuture)?;
+			let last_period_limit = first_period
+				.checked_add(&((SlotRange::LEASE_PERIODS_PER_SLOT as u32) - 1).into())
+				.ok_or(Error::<T>::FirstPeriodTooFarInFuture)?;
 			ensure!(last_period <= last_period_limit, Error::<T>::LastPeriodTooFarInFuture);
 			ensure!(end > <frame_system::Pallet<T>>::block_number(), Error::<T>::CannotEndInPast);
 			let last_possible_win_date = (first_period.saturating_add(One::one())).saturating_mul(T::Auctioneer::lease_period());
@@ -1475,7 +1478,7 @@ mod benchmarking {
 		let cap = BalanceOf::<T>::max_value();
 		let lease_period_index = T::Auctioneer::lease_period_index();
 		let first_period = lease_period_index;
-		let last_period = lease_period_index + 3u32.into();
+		let last_period = lease_period_index + ((SlotRange::LEASE_PERIODS_PER_SLOT as u32) - 1).into();
 		let para_id = id.into();
 
 		let caller = account("fund_creator", id, 0);
diff --git a/polkadot/runtime/common/src/lib.rs b/polkadot/runtime/common/src/lib.rs
index 8cdab773d93..b0589ad0b72 100644
--- a/polkadot/runtime/common/src/lib.rs
+++ b/polkadot/runtime/common/src/lib.rs
@@ -19,7 +19,6 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
 pub mod claims;
-pub mod slot_range;
 pub mod slots;
 pub mod auctions;
 pub mod crowdloan;
@@ -28,6 +27,7 @@ pub mod impls;
 pub mod mmr;
 pub mod paras_sudo_wrapper;
 pub mod paras_registrar;
+pub mod slot_range;
 pub mod traits;
 pub mod xcm_sender;
 
diff --git a/polkadot/runtime/common/src/slot_range.rs b/polkadot/runtime/common/src/slot_range.rs
index b9751e18efb..31480a6aabe 100644
--- a/polkadot/runtime/common/src/slot_range.rs
+++ b/polkadot/runtime/common/src/slot_range.rs
@@ -17,139 +17,4 @@
 //! The SlotRange struct which succinctly handles the ten values that
 //! represent all sub ranges between 0 and 3 inclusive.
 
-use sp_std::{result, ops::Add, convert::{TryFrom, TryInto}};
-use sp_runtime::traits::CheckedSub;
-use parity_scale_codec::{Encode, Decode};
-
-/// Total number of possible sub ranges of slots.
-pub const SLOT_RANGE_COUNT: usize = 10;
-
-/// A compactly represented sub-range from the series (0, 1, 2, 3).
-#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode)]
-#[repr(u8)]
-pub enum SlotRange {
-	/// Sub range from index 0 to index 0 inclusive.
-	ZeroZero = 0,
-	/// Sub range from index 0 to index 1 inclusive.
-	ZeroOne = 1,
-	/// Sub range from index 0 to index 2 inclusive.
-	ZeroTwo = 2,
-	/// Sub range from index 0 to index 3 inclusive.
-	ZeroThree = 3,
-	/// Sub range from index 1 to index 1 inclusive.
-	OneOne = 4,
-	/// Sub range from index 1 to index 2 inclusive.
-	OneTwo = 5,
-	/// Sub range from index 1 to index 3 inclusive.
-	OneThree = 6,
-	/// Sub range from index 2 to index 2 inclusive.
-	TwoTwo = 7,
-	/// Sub range from index 2 to index 3 inclusive.
-	TwoThree = 8,
-	/// Sub range from index 3 to index 3 inclusive.
-	ThreeThree = 9,     // == SLOT_RANGE_COUNT - 1
-}
-
-#[cfg(feature = "std")]
-impl std::fmt::Debug for SlotRange {
-	fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
-		let p = self.as_pair();
-		write!(fmt, "[{}..{}]", p.0, p.1)
-	}
-}
-
-impl SlotRange {
-	pub fn new_bounded<
-		Index: Add<Output=Index> + CheckedSub + Copy + Ord + From<u32> + TryInto<u32>
-	>(
-		initial: Index,
-		first: Index,
-		last: Index
-	) -> result::Result<Self, &'static str> {
-		if first > last || first < initial || last > initial + 3.into() {
-			return Err("Invalid range for this auction")
-		}
-		let count: u32 = last.checked_sub(&first)
-			.ok_or("range ends before it begins")?
-			.try_into()
-			.map_err(|_| "range too big")?;
-		let first: u32 = first.checked_sub(&initial)
-			.ok_or("range begins too early")?
-			.try_into()
-			.map_err(|_| "start too far")?;
-		match first {
-			0 => match count {
-				0 => Some(SlotRange::ZeroZero),
-				1 => Some(SlotRange::ZeroOne),
-				2 => Some(SlotRange::ZeroTwo),
-				3 => Some(SlotRange::ZeroThree),
-				_ => None,
-			},
-			1 => match count {
-				0 => Some(SlotRange::OneOne),
-				1 => Some(SlotRange::OneTwo),
-				2 => Some(SlotRange::OneThree),
-				_ => None
-			},
-			2 => match count { 0 => Some(SlotRange::TwoTwo), 1 => Some(SlotRange::TwoThree), _ => None },
-			3 => match count { 0 => Some(SlotRange::ThreeThree), _ => None },
-			_ => return Err("range begins too late"),
-		}.ok_or("range ends too late")
-	}
-
-	pub fn as_pair(&self) -> (u8, u8) {
-		match self {
-			SlotRange::ZeroZero => (0, 0),
-			SlotRange::ZeroOne => (0, 1),
-			SlotRange::ZeroTwo => (0, 2),
-			SlotRange::ZeroThree => (0, 3),
-			SlotRange::OneOne => (1, 1),
-			SlotRange::OneTwo => (1, 2),
-			SlotRange::OneThree => (1, 3),
-			SlotRange::TwoTwo => (2, 2),
-			SlotRange::TwoThree => (2, 3),
-			SlotRange::ThreeThree => (3, 3),
-		}
-	}
-
-	pub fn intersects(&self, other: SlotRange) -> bool {
-		let a = self.as_pair();
-		let b = other.as_pair();
-		b.0 <= a.1 && a.0 <= b.1
-//		== !(b.0 > a.1 || a.0 > b.1)
-	}
-
-	pub fn len(&self) -> usize {
-		match self {
-			SlotRange::ZeroZero => 1,
-			SlotRange::ZeroOne => 2,
-			SlotRange::ZeroTwo => 3,
-			SlotRange::ZeroThree => 4,
-			SlotRange::OneOne => 1,
-			SlotRange::OneTwo => 2,
-			SlotRange::OneThree => 3,
-			SlotRange::TwoTwo => 1,
-			SlotRange::TwoThree => 2,
-			SlotRange::ThreeThree => 1,
-		}
-	}
-}
-
-impl TryFrom<usize> for SlotRange {
-	type Error = ();
-	fn try_from(x: usize) -> Result<SlotRange, ()> {
-		Ok(match x {
-			0 => SlotRange::ZeroZero,
-			1 => SlotRange::ZeroOne,
-			2 => SlotRange::ZeroTwo,
-			3 => SlotRange::ZeroThree,
-			4 => SlotRange::OneOne,
-			5 => SlotRange::OneTwo,
-			6 => SlotRange::OneThree,
-			7 => SlotRange::TwoTwo,
-			8 => SlotRange::TwoThree,
-			9 => SlotRange::ThreeThree,
-			_ => return Err(()),
-		})
-	}
-}
+slot_range_helper::generate_slot_range!(Zero(0), One(1), Two(2), Three(3));
-- 
GitLab