1use alloc::vec::Vec;
2use core::{
3 fmt::{self, Debug, Display, Formatter},
4 num::ParseIntError,
5 ops::{Add, AddAssign, Sub},
6 str::FromStr,
7};
8
9#[cfg(feature = "datasize")]
10use datasize::DataSize;
11#[cfg(any(feature = "testing", test))]
12use rand::Rng;
13#[cfg(feature = "json-schema")]
14use schemars::JsonSchema;
15use serde::{Deserialize, Serialize};
16
17#[cfg(any(feature = "testing", test))]
18use crate::testing::TestRng;
19use crate::{
20 bytesrepr::{self, FromBytes, ToBytes},
21 CLType, CLTyped,
22};
23
24#[derive(
26 Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
27)]
28#[cfg_attr(feature = "datasize", derive(DataSize))]
29#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
30#[cfg_attr(feature = "testing", derive(proptest_derive::Arbitrary))]
31#[serde(deny_unknown_fields)]
32pub struct EraId(u64);
33
34impl EraId {
35 pub const MAX: EraId = EraId(u64::MAX);
37
38 pub const fn new(value: u64) -> EraId {
40 EraId(value)
41 }
42
43 pub fn iter(&self, num_eras: u64) -> impl Iterator<Item = EraId> {
45 let current_era_id = self.0;
46 (current_era_id..current_era_id + num_eras).map(EraId)
47 }
48
49 pub fn iter_inclusive(&self, num_eras: u64) -> impl Iterator<Item = EraId> {
52 let current_era_id = self.0;
53 (current_era_id..=current_era_id + num_eras).map(EraId)
54 }
55
56 pub fn iter_range_inclusive(
59 start: EraId,
60 end: EraId,
61 ) -> impl DoubleEndedIterator<Item = EraId> {
62 (start.0..=end.0).map(EraId)
63 }
64
65 pub fn increment(&mut self) {
70 self.0 = self.0.saturating_add(1);
71 }
72
73 #[must_use]
78 pub fn successor(self) -> EraId {
79 EraId::from(self.0.saturating_add(1))
80 }
81
82 #[must_use]
84 pub fn predecessor(self) -> Option<EraId> {
85 self.0.checked_sub(1).map(EraId)
86 }
87
88 pub fn checked_add(&self, x: u64) -> Option<EraId> {
90 self.0.checked_add(x).map(EraId)
91 }
92
93 pub fn checked_sub(&self, x: u64) -> Option<EraId> {
95 self.0.checked_sub(x).map(EraId)
96 }
97
98 #[must_use]
100 pub fn saturating_sub(&self, x: u64) -> EraId {
101 EraId::from(self.0.saturating_sub(x))
102 }
103
104 #[must_use]
106 pub fn saturating_add(self, rhs: u64) -> EraId {
107 EraId(self.0.saturating_add(rhs))
108 }
109
110 #[must_use]
112 pub fn saturating_mul(&self, x: u64) -> EraId {
113 EraId::from(self.0.saturating_mul(x))
114 }
115
116 pub fn is_genesis(&self) -> bool {
118 self.0 == 0
119 }
120
121 pub fn to_le_bytes(self) -> [u8; 8] {
123 self.0.to_le_bytes()
124 }
125
126 pub fn value(self) -> u64 {
130 self.0
131 }
132
133 #[cfg(any(feature = "testing", test))]
135 pub fn random(rng: &mut TestRng) -> Self {
136 EraId(rng.gen_range(0..1_000_000))
137 }
138}
139
140impl FromStr for EraId {
141 type Err = ParseIntError;
142
143 fn from_str(s: &str) -> Result<Self, Self::Err> {
144 u64::from_str(s).map(EraId)
145 }
146}
147
148impl Add<u64> for EraId {
149 type Output = EraId;
150
151 #[allow(clippy::arithmetic_side_effects)] fn add(self, x: u64) -> EraId {
153 EraId::from(self.0 + x)
154 }
155}
156
157impl AddAssign<u64> for EraId {
158 fn add_assign(&mut self, x: u64) {
159 self.0 += x;
160 }
161}
162
163impl Sub<u64> for EraId {
164 type Output = EraId;
165
166 #[allow(clippy::arithmetic_side_effects)] fn sub(self, x: u64) -> EraId {
168 EraId::from(self.0 - x)
169 }
170}
171
172impl Display for EraId {
173 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
174 write!(f, "era {}", self.0)
175 }
176}
177
178impl From<EraId> for u64 {
179 fn from(era_id: EraId) -> Self {
180 era_id.value()
181 }
182}
183
184impl From<u64> for EraId {
185 fn from(era_id: u64) -> Self {
186 EraId(era_id)
187 }
188}
189
190impl ToBytes for EraId {
191 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
192 self.0.to_bytes()
193 }
194
195 fn serialized_length(&self) -> usize {
196 self.0.serialized_length()
197 }
198
199 #[inline(always)]
200 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
201 self.0.write_bytes(writer)?;
202 Ok(())
203 }
204}
205
206impl FromBytes for EraId {
207 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
208 let (id_value, remainder) = u64::from_bytes(bytes)?;
209 let era_id = EraId::from(id_value);
210 Ok((era_id, remainder))
211 }
212}
213
214impl CLTyped for EraId {
215 fn cl_type() -> CLType {
216 CLType::U64
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use proptest::prelude::*;
223
224 use super::*;
225 use crate::gens::era_id_arb;
226
227 #[test]
228 fn should_calculate_correct_inclusive_future_eras() {
229 let auction_delay = 3;
230
231 let current_era = EraId::from(42);
232
233 let window: Vec<EraId> = current_era.iter_inclusive(auction_delay).collect();
234 assert_eq!(window.len(), auction_delay as usize + 1);
235 assert_eq!(window.first(), Some(¤t_era));
236 assert_eq!(
237 window.iter().next_back(),
238 Some(&(current_era + auction_delay))
239 );
240 }
241
242 #[test]
243 fn should_have_valid_genesis_era_id() {
244 let expected_initial_era_id = EraId::from(0);
245 assert!(expected_initial_era_id.is_genesis());
246 assert!(!expected_initial_era_id.successor().is_genesis())
247 }
248
249 #[test]
250 fn should_increment_era_id() {
251 let mut era = EraId::from(0);
252 assert!(era.is_genesis());
253 era.increment();
254 assert_eq!(era.value(), 1, "should have incremented to 1");
255 }
256
257 proptest! {
258 #[test]
259 fn bytesrepr_roundtrip(era_id in era_id_arb()) {
260 bytesrepr::test_serialization_roundtrip(&era_id);
261 }
262 }
263}