1#![allow(clippy::field_reassign_with_default)]
3
4use alloc::vec::Vec;
5use core::{
6 fmt::{self, Debug, Display, Formatter},
7 num::ParseIntError,
8 ops::{Add, AddAssign, Sub},
9 str::FromStr,
10};
11
12#[cfg(feature = "datasize")]
13use datasize::DataSize;
14use rand::{
15 distributions::{Distribution, Standard},
16 Rng,
17};
18#[cfg(feature = "json-schema")]
19use schemars::JsonSchema;
20use serde::{Deserialize, Serialize};
21
22use crate::{
23 bytesrepr::{self, FromBytes, ToBytes},
24 CLType, CLTyped,
25};
26
27#[derive(
29 Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
30)]
31#[cfg_attr(feature = "datasize", derive(DataSize))]
32#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
33#[cfg_attr(feature = "testing", derive(proptest_derive::Arbitrary))]
34#[serde(deny_unknown_fields)]
35pub struct EraId(u64);
36
37impl EraId {
38 pub const MAX: EraId = EraId(u64::max_value());
40
41 pub const fn new(value: u64) -> EraId {
43 EraId(value)
44 }
45
46 pub fn iter(&self, num_eras: u64) -> impl Iterator<Item = EraId> {
48 let current_era_id = self.0;
49 (current_era_id..current_era_id + num_eras).map(EraId)
50 }
51
52 pub fn iter_inclusive(&self, num_eras: u64) -> impl Iterator<Item = EraId> {
55 let current_era_id = self.0;
56 (current_era_id..=current_era_id + num_eras).map(EraId)
57 }
58
59 #[must_use]
64 pub fn successor(self) -> EraId {
65 EraId::from(self.0.saturating_add(1))
66 }
67
68 #[must_use]
70 pub fn predecessor(self) -> Option<EraId> {
71 self.0.checked_sub(1).map(EraId)
72 }
73
74 pub fn checked_add(&self, x: u64) -> Option<EraId> {
76 self.0.checked_add(x).map(EraId)
77 }
78
79 pub fn checked_sub(&self, x: u64) -> Option<EraId> {
81 self.0.checked_sub(x).map(EraId)
82 }
83
84 #[must_use]
86 pub fn saturating_sub(&self, x: u64) -> EraId {
87 EraId::from(self.0.saturating_sub(x))
88 }
89
90 #[must_use]
92 pub fn saturating_add(self, rhs: u64) -> EraId {
93 EraId(self.0.saturating_add(rhs))
94 }
95
96 #[must_use]
98 pub fn saturating_mul(&self, x: u64) -> EraId {
99 EraId::from(self.0.saturating_mul(x))
100 }
101
102 pub fn is_genesis(&self) -> bool {
104 self.0 == 0
105 }
106
107 pub fn to_le_bytes(self) -> [u8; 8] {
109 self.0.to_le_bytes()
110 }
111
112 pub fn value(self) -> u64 {
116 self.0
117 }
118}
119
120impl FromStr for EraId {
121 type Err = ParseIntError;
122
123 fn from_str(s: &str) -> Result<Self, Self::Err> {
124 u64::from_str(s).map(EraId)
125 }
126}
127
128impl Add<u64> for EraId {
129 type Output = EraId;
130
131 #[allow(clippy::arithmetic_side_effects)] fn add(self, x: u64) -> EraId {
133 EraId::from(self.0 + x)
134 }
135}
136
137impl AddAssign<u64> for EraId {
138 fn add_assign(&mut self, x: u64) {
139 self.0 += x;
140 }
141}
142
143impl Sub<u64> for EraId {
144 type Output = EraId;
145
146 #[allow(clippy::arithmetic_side_effects)] fn sub(self, x: u64) -> EraId {
148 EraId::from(self.0 - x)
149 }
150}
151
152impl Display for EraId {
153 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
154 write!(f, "era {}", self.0)
155 }
156}
157
158impl From<EraId> for u64 {
159 fn from(era_id: EraId) -> Self {
160 era_id.value()
161 }
162}
163
164impl From<u64> for EraId {
165 fn from(era_id: u64) -> Self {
166 EraId(era_id)
167 }
168}
169
170impl ToBytes for EraId {
171 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
172 self.0.to_bytes()
173 }
174
175 fn serialized_length(&self) -> usize {
176 self.0.serialized_length()
177 }
178
179 #[inline(always)]
180 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
181 self.0.write_bytes(writer)?;
182 Ok(())
183 }
184}
185
186impl FromBytes for EraId {
187 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
188 let (id_value, remainder) = u64::from_bytes(bytes)?;
189 let era_id = EraId::from(id_value);
190 Ok((era_id, remainder))
191 }
192}
193
194impl CLTyped for EraId {
195 fn cl_type() -> CLType {
196 CLType::U64
197 }
198}
199
200impl Distribution<EraId> for Standard {
201 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> EraId {
202 EraId(rng.gen_range(0..1_000_000))
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use proptest::prelude::*;
209
210 use super::*;
211 use crate::gens::era_id_arb;
212
213 #[test]
214 fn should_calculate_correct_inclusive_future_eras() {
215 let auction_delay = 3;
216
217 let current_era = EraId::from(42);
218
219 let window: Vec<EraId> = current_era.iter_inclusive(auction_delay).collect();
220 assert_eq!(window.len(), auction_delay as usize + 1);
221 assert_eq!(window.get(0), Some(¤t_era));
222 assert_eq!(
223 window.iter().next_back(),
224 Some(&(current_era + auction_delay))
225 );
226 }
227
228 #[test]
229 fn should_have_valid_genesis_era_id() {
230 let expected_initial_era_id = EraId::from(0);
231 assert!(expected_initial_era_id.is_genesis());
232 assert!(!expected_initial_era_id.successor().is_genesis())
233 }
234
235 proptest! {
236 #[test]
237 fn bytesrepr_roundtrip(era_id in era_id_arb()) {
238 bytesrepr::test_serialization_roundtrip(&era_id);
239 }
240 }
241}