referrerpolicy=no-referrer-when-downgrade

slot_range_helper/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! A helper macro for generating `SlotRange` enum.
18
19#![cfg_attr(not(feature = "std"), no_std)]
20
21pub use codec::{Decode, Encode};
22pub use core::{ops::Add, result};
23pub use enumn::N;
24pub use paste;
25pub use sp_runtime::traits::CheckedSub;
26
27/// This macro generates a `SlotRange` enum of arbitrary length for use in the Slot Auction
28/// mechanism on Polkadot.
29///
30/// Usage:
31/// ```
32/// slot_range_helper::generate_slot_range!(Zero(0), One(1), Two(2), Three(3));
33/// ```
34///
35/// To extend the usage, continue to add `Identifier(value)` items to the macro.
36///
37/// This will generate an enum `SlotRange` with the following properties:
38///
39/// * Enum variants will range from all consecutive combinations of inputs, i.e. `ZeroZero`,
40///   `ZeroOne`, `ZeroTwo`, `ZeroThree`, `OneOne`, `OneTwo`, `OneThree`...
41/// * A constant `LEASE_PERIODS_PER_SLOT` will count the number of lease periods.
42/// * A constant `SLOT_RANGE_COUNT` will count the total number of enum variants.
43/// * A function `as_pair` will return a tuple representation of the `SlotRange`.
44/// * A function `intersects` will tell you if two slot ranges intersect with one another.
45/// * A function `len` will tell you the length of occupying a `SlotRange`.
46/// * A function `new_bounded` will generate a `SlotRange` from an input of the current lease
47///   period, the starting lease period, and the final lease period.
48#[macro_export]
49macro_rules! generate_slot_range{
50	// Entry point
51	($( $x:ident ( $e:expr ) ),*) => {
52		$crate::generate_lease_period_per_slot!( $( $x )* );
53		$crate::generate_slot_range!(@inner
54			{ }
55			$( $x ( $e ) )*
56		);
57	};
58	// Does the magic...
59	(@inner
60		{ $( $parsed:ident ( $t1:expr, $t2:expr ) )* }
61		$current:ident ( $ce:expr )
62		$( $remaining:ident ( $re:expr ) )*
63	) => {
64		$crate::paste::paste! {
65			$crate::generate_slot_range!(@inner
66				{
67					$( $parsed ( $t1, $t2 ) )*
68					[< $current $current >] ( $ce, $ce )
69					$( [< $current $remaining >] ($ce, $re) )*
70				}
71				$( $remaining ( $re ) )*
72			);
73		}
74	};
75	(@inner
76		{ $( $parsed:ident ( $t1:expr, $t2:expr ) )* }
77	) => {
78		$crate::generate_slot_range_enum!(@inner $( $parsed )* );
79
80		$crate::generate_slot_range_count!( $( $parsed )* );
81
82		#[cfg(feature = "std")]
83		impl std::fmt::Debug for SlotRange {
84			fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
85				let p = self.as_pair();
86				write!(fmt, "[{}..{}]", p.0, p.1)
87			}
88		}
89
90		impl SlotRange {
91			pub const LEASE_PERIODS_PER_SLOT: usize = LEASE_PERIODS_PER_SLOT;
92			pub const SLOT_RANGE_COUNT: usize = SLOT_RANGE_COUNT;
93
94			$crate::generate_slot_range_as_pair!(@inner $( $parsed ( $t1, $t2 ) )* );
95
96			$crate::generate_slot_range_len!(@inner $( $parsed ( $t1, $t2 ) )* );
97
98			$crate::generate_slot_range_new_bounded!(@inner $( $parsed ( $t1, $t2 ) )* );
99		}
100	};
101}
102
103#[macro_export]
104#[doc(hidden)]
105macro_rules! generate_slot_range_enum {
106	(@inner
107		$( $parsed:ident )*
108	) => {
109		/// A compactly represented sub-range from the series.
110		#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, $crate::Encode, $crate::Decode, $crate::N)]
111		#[repr(u8)]
112		pub enum SlotRange { $( $parsed ),* }
113	};
114}
115
116#[macro_export]
117#[doc(hidden)]
118macro_rules! generate_slot_range_as_pair {
119	(@inner
120		$( $parsed:ident ( $t1:expr, $t2:expr ) )*
121	) => {
122		/// Return true if two `SlotRange` intersect in their lease periods.
123		pub fn intersects(&self, other: SlotRange) -> bool {
124			let a = self.as_pair();
125			let b = other.as_pair();
126			b.0 <= a.1 && a.0 <= b.1
127			// == !(b.0 > a.1 || a.0 > b.1)
128		}
129
130		/// Return a tuple representation of the `SlotRange`.
131		///
132		/// Example:`SlotRange::OneTwo.as_pair() == (1, 2)`
133		pub fn as_pair(&self) -> (u8, u8) {
134			match self {
135				$( SlotRange::$parsed => { ($t1, $t2) } )*
136			}
137		}
138	};
139}
140
141#[macro_export]
142#[doc(hidden)]
143macro_rules! generate_slot_range_len {
144	// Use evaluated length in function.
145	(@inner
146		$( $parsed:ident ( $t1:expr, $t2:expr ) )*
147	) => {
148		/// Return the length of occupying a `SlotRange`.
149		///
150		/// Example:`SlotRange::OneTwo.len() == 2`
151		pub fn len(&self) -> usize {
152			match self {
153				// len (0, 2) = 2 - 0 + 1 = 3
154				$( SlotRange::$parsed => { ( $t2 - $t1 + 1) } )*
155			}
156		}
157	};
158}
159
160#[macro_export]
161#[doc(hidden)]
162macro_rules! generate_slot_range_new_bounded {
163	(@inner
164		$( $parsed:ident ( $t1:expr, $t2:expr ) )*
165	) => {
166		/// Construct a `SlotRange` from the current lease period, the first lease period of the range,
167		/// and the last lease period of the range.
168		///
169		/// For example: `SlotRange::new_bounded(1, 2, 3) == SlotRange::OneTwo`.
170		pub fn new_bounded<
171			Index: $crate::Add<Output=Index> + $crate::CheckedSub + Copy + Ord + From<u32> + TryInto<u32>
172		>(
173			current: Index,
174			first: Index,
175			last: Index
176		) -> $crate::result::Result<Self, &'static str> {
177			if first > last || first < current || last >= current + (LEASE_PERIODS_PER_SLOT as u32).into() {
178				return Err("Invalid range for this auction")
179			}
180			let count: u32 = last.checked_sub(&first)
181				.ok_or("range ends before it begins")?
182				.try_into()
183				.map_err(|_| "range too big")?;
184			let first: u32 = first.checked_sub(&current)
185				.ok_or("range begins too early")?
186				.try_into()
187				.map_err(|_| "start too far")?;
188			match (first, first + count) {
189				$( ($t1, $t2) => { Ok(SlotRange::$parsed) })*
190				_ => Err("bad range"),
191			}
192		}
193	};
194}
195
196#[macro_export]
197#[doc(hidden)]
198macro_rules! generate_slot_range_count {
199	(
200		$start:ident $( $rest:ident )*
201	) => {
202		$crate::generate_slot_range_count!(@inner 1; $( $rest )*);
203	};
204	(@inner
205		$count:expr;
206		$start:ident $( $rest:ident )*
207	) => {
208		$crate::generate_slot_range_count!(@inner $count + 1; $( $rest )*);
209	};
210	(@inner
211		$count:expr;
212	) => {
213		const SLOT_RANGE_COUNT: usize = $count;
214	};
215}
216
217#[macro_export]
218#[doc(hidden)]
219macro_rules! generate_lease_period_per_slot {
220	(
221		$start:ident $( $rest:ident )*
222	) => {
223		$crate::generate_lease_period_per_slot!(@inner 1; $( $rest )*);
224	};
225	(@inner
226		$count:expr;
227		$start:ident $( $rest:ident )*
228	) => {
229		$crate::generate_lease_period_per_slot!(@inner $count + 1; $( $rest )*);
230	};
231	(@inner
232		$count:expr;
233	) => {
234		const LEASE_PERIODS_PER_SLOT: usize = $count;
235	};
236}
237
238#[cfg(test)]
239mod tests {
240	use super::*;
241
242	#[test]
243	fn slot_range_4_works() {
244		generate_slot_range!(Zero(0), One(1), Two(2), Three(3));
245
246		assert_eq!(SlotRange::LEASE_PERIODS_PER_SLOT, 4);
247		// Sum over n from 0 - 4
248		assert_eq!(SlotRange::SLOT_RANGE_COUNT, 10);
249		assert_eq!(SlotRange::new_bounded(0u32, 1u32, 2u32).unwrap(), SlotRange::OneTwo);
250		assert_eq!(SlotRange::new_bounded(5u32, 6u32, 7u32).unwrap(), SlotRange::OneTwo);
251		assert!(SlotRange::new_bounded(10u32, 6u32, 7u32).is_err());
252		assert!(SlotRange::new_bounded(10u32, 16u32, 17u32).is_err());
253		assert!(SlotRange::new_bounded(10u32, 11u32, 10u32).is_err());
254		assert_eq!(SlotRange::TwoTwo.len(), 1);
255		assert_eq!(SlotRange::OneTwo.len(), 2);
256		assert_eq!(SlotRange::ZeroThree.len(), 4);
257		assert!(SlotRange::ZeroOne.intersects(SlotRange::OneThree));
258		assert!(!SlotRange::ZeroOne.intersects(SlotRange::TwoThree));
259		assert_eq!(SlotRange::ZeroZero.as_pair(), (0, 0));
260		assert_eq!(SlotRange::OneThree.as_pair(), (1, 3));
261	}
262
263	#[test]
264	fn slot_range_8_works() {
265		generate_slot_range!(Zero(0), One(1), Two(2), Three(3), Four(4), Five(5), Six(6), Seven(7));
266
267		assert_eq!(SlotRange::LEASE_PERIODS_PER_SLOT, 8);
268		// Sum over n from 0 to 8
269		assert_eq!(SlotRange::SLOT_RANGE_COUNT, 36);
270		assert_eq!(SlotRange::new_bounded(0u32, 1u32, 2u32).unwrap(), SlotRange::OneTwo);
271		assert_eq!(SlotRange::new_bounded(5u32, 6u32, 7u32).unwrap(), SlotRange::OneTwo);
272		assert!(SlotRange::new_bounded(10u32, 6u32, 7u32).is_err());
273		// This one passes with slot range 8
274		assert_eq!(SlotRange::new_bounded(10u32, 16u32, 17u32).unwrap(), SlotRange::SixSeven);
275		assert!(SlotRange::new_bounded(10u32, 17u32, 18u32).is_err());
276		assert!(SlotRange::new_bounded(10u32, 20u32, 21u32).is_err());
277		assert!(SlotRange::new_bounded(10u32, 11u32, 10u32).is_err());
278		assert_eq!(SlotRange::TwoTwo.len(), 1);
279		assert_eq!(SlotRange::OneTwo.len(), 2);
280		assert_eq!(SlotRange::ZeroThree.len(), 4);
281		assert_eq!(SlotRange::ZeroSeven.len(), 8);
282		assert!(SlotRange::ZeroOne.intersects(SlotRange::OneThree));
283		assert!(!SlotRange::ZeroOne.intersects(SlotRange::TwoThree));
284		assert!(SlotRange::FiveSix.intersects(SlotRange::SixSeven));
285		assert!(!SlotRange::ThreeFive.intersects(SlotRange::SixSeven));
286		assert_eq!(SlotRange::ZeroZero.as_pair(), (0, 0));
287		assert_eq!(SlotRange::OneThree.as_pair(), (1, 3));
288		assert_eq!(SlotRange::SixSeven.as_pair(), (6, 7));
289	}
290}