1#[cfg(feature = "serde")]
21use serde::{Deserialize, Serialize};
22
23use crate::codec::{Decode, DecodeWithMemTracking, Encode, Error, Input, Output};
24
25pub type Period = u64;
27
28pub type Phase = u64;
30
31#[derive(DecodeWithMemTracking, PartialEq, Eq, Clone, Copy, sp_core::RuntimeDebug)]
33#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
34pub enum Era {
35 Immortal,
37
38 Mortal(Period, Phase),
48}
49
50impl Era {
58 pub fn mortal(period: u64, current: u64) -> Self {
66 let period = period.checked_next_power_of_two().unwrap_or(1 << 16).clamp(4, 1 << 16);
67 let phase = current % period;
68 let quantize_factor = (period >> 12).max(1);
69 let quantized_phase = phase / quantize_factor * quantize_factor;
70
71 Self::Mortal(period, quantized_phase)
72 }
73
74 pub fn immortal() -> Self {
76 Self::Immortal
77 }
78
79 pub fn is_immortal(&self) -> bool {
81 matches!(self, Self::Immortal)
82 }
83
84 pub fn birth(self, current: u64) -> u64 {
87 match self {
88 Self::Immortal => 0,
89 Self::Mortal(period, phase) => (current.max(phase) - phase) / period * period + phase,
90 }
91 }
92
93 pub fn death(self, current: u64) -> u64 {
95 match self {
96 Self::Immortal => u64::MAX,
97 Self::Mortal(period, _) => self.birth(current) + period,
98 }
99 }
100}
101
102impl Encode for Era {
103 fn encode_to<T: Output + ?Sized>(&self, output: &mut T) {
104 match self {
105 Self::Immortal => output.push_byte(0),
106 Self::Mortal(period, phase) => {
107 let quantize_factor = (*period as u64 >> 12).max(1);
108 let encoded = (period.trailing_zeros() - 1).clamp(1, 15) as u16 |
109 ((phase / quantize_factor) << 4) as u16;
110 encoded.encode_to(output);
111 },
112 }
113 }
114}
115
116impl codec::EncodeLike for Era {}
117
118impl Decode for Era {
119 fn decode<I: Input>(input: &mut I) -> Result<Self, Error> {
120 let first = input.read_byte()?;
121 if first == 0 {
122 Ok(Self::Immortal)
123 } else {
124 let encoded = first as u64 + ((input.read_byte()? as u64) << 8);
125 let period = 2 << (encoded % (1 << 4));
126 let quantize_factor = (period >> 12).max(1);
127 let phase = (encoded >> 4) * quantize_factor;
128 if period >= 4 && phase < period {
129 Ok(Self::Mortal(period, phase))
130 } else {
131 Err("Invalid period and phase".into())
132 }
133 }
134 }
135}
136
137macro_rules! mortal_variants {
139 ($variants:ident, $($index:literal),* ) => {
140 $variants
141 $(
142 .variant(concat!(stringify!(Mortal), stringify!($index)), |v| v
143 .index($index)
144 .fields(scale_info::build::Fields::unnamed().field(|f| f.ty::<u8>()))
145 )
146 )*
147 }
148}
149
150impl scale_info::TypeInfo for Era {
151 type Identity = Self;
152
153 fn type_info() -> scale_info::Type {
154 let variants = scale_info::build::Variants::new().variant("Immortal", |v| v.index(0));
155
156 let variants = mortal_variants!(
159 variants, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
160 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
161 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
162 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87,
163 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
164 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124,
165 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141,
166 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158,
167 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
168 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192,
169 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
170 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226,
171 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243,
172 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255
173 );
174
175 scale_info::Type::builder()
176 .path(scale_info::Path::new("Era", module_path!()))
177 .variant(variants)
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn immortal_works() {
187 let e = Era::immortal();
188 assert_eq!(e.birth(0), 0);
189 assert_eq!(e.death(0), u64::MAX);
190 assert_eq!(e.birth(1), 0);
191 assert_eq!(e.death(1), u64::MAX);
192 assert_eq!(e.birth(u64::MAX), 0);
193 assert_eq!(e.death(u64::MAX), u64::MAX);
194 assert!(e.is_immortal());
195
196 assert_eq!(e.encode(), vec![0u8]);
197 assert_eq!(e, Era::decode(&mut &[0u8][..]).unwrap());
198 }
199
200 #[test]
201 fn mortal_codec_works() {
202 let e = Era::mortal(64, 42);
203 assert!(!e.is_immortal());
204
205 let expected = vec![5 + 42 % 16 * 16, 42 / 16];
206 assert_eq!(e.encode(), expected);
207 assert_eq!(e, Era::decode(&mut &expected[..]).unwrap());
208 }
209
210 #[test]
211 fn long_period_mortal_codec_works() {
212 let e = Era::mortal(32768, 20000);
213
214 let expected = vec![(14 + 2500 % 16 * 16) as u8, (2500 / 16) as u8];
215 assert_eq!(e.encode(), expected);
216 assert_eq!(e, Era::decode(&mut &expected[..]).unwrap());
217 }
218
219 #[test]
220 fn era_initialization_works() {
221 assert_eq!(Era::mortal(64, 42), Era::Mortal(64, 42));
222 assert_eq!(Era::mortal(32768, 20000), Era::Mortal(32768, 20000));
223 assert_eq!(Era::mortal(200, 513), Era::Mortal(256, 1));
224 assert_eq!(Era::mortal(2, 1), Era::Mortal(4, 1));
225 assert_eq!(Era::mortal(4, 5), Era::Mortal(4, 1));
226 }
227
228 #[test]
229 fn quantized_clamped_era_initialization_works() {
230 assert_eq!(Era::mortal(1000000, 1000001), Era::Mortal(65536, 1000001 % 65536 / 4 * 4));
232 }
233
234 #[test]
235 fn mortal_birth_death_works() {
236 let e = Era::mortal(4, 6);
237 for i in 6..10 {
238 assert_eq!(e.birth(i), 6);
239 assert_eq!(e.death(i), 10);
240 }
241
242 assert_ne!(e.birth(10), 6);
244 assert_ne!(e.birth(5), 6);
245 }
246
247 #[test]
248 fn current_less_than_phase() {
249 Era::mortal(4, 3).birth(1);
251 }
252}