casper_types/
era_id.rs

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/// Era ID newtype.
25#[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    /// Maximum possible value an [`EraId`] can hold.
36    pub const MAX: EraId = EraId(u64::MAX);
37
38    /// Creates new [`EraId`] instance.
39    pub const fn new(value: u64) -> EraId {
40        EraId(value)
41    }
42
43    /// Returns an iterator over era IDs of `num_eras` future eras starting from current.
44    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    /// Returns an iterator over era IDs of `num_eras` future eras starting from current, plus the
50    /// provided one.
51    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    /// Returns an iterator over a range of era IDs, starting from `start` and ending at `end`,
57    /// inclusive.
58    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    /// Increments the era.
66    ///
67    /// For `u64::MAX`, this returns `u64::MAX` again: We want to make sure this doesn't panic, and
68    /// that era number will never be reached in practice.
69    pub fn increment(&mut self) {
70        self.0 = self.0.saturating_add(1);
71    }
72
73    /// Returns a successor to current era.
74    ///
75    /// For `u64::MAX`, this returns `u64::MAX` again: We want to make sure this doesn't panic, and
76    /// that era number will never be reached in practice.
77    #[must_use]
78    pub fn successor(self) -> EraId {
79        EraId::from(self.0.saturating_add(1))
80    }
81
82    /// Returns the predecessor to current era, or `None` if genesis.
83    #[must_use]
84    pub fn predecessor(self) -> Option<EraId> {
85        self.0.checked_sub(1).map(EraId)
86    }
87
88    /// Returns the current era plus `x`, or `None` if that would overflow
89    pub fn checked_add(&self, x: u64) -> Option<EraId> {
90        self.0.checked_add(x).map(EraId)
91    }
92
93    /// Returns the current era minus `x`, or `None` if that would be less than `0`.
94    pub fn checked_sub(&self, x: u64) -> Option<EraId> {
95        self.0.checked_sub(x).map(EraId)
96    }
97
98    /// Returns the current era minus `x`, or `0` if that would be less than `0`.
99    #[must_use]
100    pub fn saturating_sub(&self, x: u64) -> EraId {
101        EraId::from(self.0.saturating_sub(x))
102    }
103
104    /// Returns the current era plus `x`, or [`EraId::MAX`] if overflow would occur.
105    #[must_use]
106    pub fn saturating_add(self, rhs: u64) -> EraId {
107        EraId(self.0.saturating_add(rhs))
108    }
109
110    /// Returns the current era times `x`, or [`EraId::MAX`] if overflow would occur.
111    #[must_use]
112    pub fn saturating_mul(&self, x: u64) -> EraId {
113        EraId::from(self.0.saturating_mul(x))
114    }
115
116    /// Returns whether this is era 0.
117    pub fn is_genesis(&self) -> bool {
118        self.0 == 0
119    }
120
121    /// Returns little endian bytes.
122    pub fn to_le_bytes(self) -> [u8; 8] {
123        self.0.to_le_bytes()
124    }
125
126    /// Returns a raw value held by this [`EraId`] instance.
127    ///
128    /// You should prefer [`From`] trait implementations over this method where possible.
129    pub fn value(self) -> u64 {
130        self.0
131    }
132
133    /// Returns a random `EraId`.
134    #[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)] // The caller must make sure this doesn't overflow.
152    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)] // The caller must make sure this doesn't overflow.
167    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(&current_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}