casper_types/
era_id.rs

1// TODO - remove once schemars stops causing warning.
2#![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/// Era ID newtype.
28#[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    /// Maximum possible value an [`EraId`] can hold.
39    pub const MAX: EraId = EraId(u64::max_value());
40
41    /// Creates new [`EraId`] instance.
42    pub const fn new(value: u64) -> EraId {
43        EraId(value)
44    }
45
46    /// Returns an iterator over era IDs of `num_eras` future eras starting from current.
47    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    /// Returns an iterator over era IDs of `num_eras` future eras starting from current, plus the
53    /// provided one.
54    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    /// Returns a successor to current era.
60    ///
61    /// For `u64::MAX`, this returns `u64::MAX` again: We want to make sure this doesn't panic, and
62    /// that era number will never be reached in practice.
63    #[must_use]
64    pub fn successor(self) -> EraId {
65        EraId::from(self.0.saturating_add(1))
66    }
67
68    /// Returns the predecessor to current era, or `None` if genesis.
69    #[must_use]
70    pub fn predecessor(self) -> Option<EraId> {
71        self.0.checked_sub(1).map(EraId)
72    }
73
74    /// Returns the current era plus `x`, or `None` if that would overflow
75    pub fn checked_add(&self, x: u64) -> Option<EraId> {
76        self.0.checked_add(x).map(EraId)
77    }
78
79    /// Returns the current era minus `x`, or `None` if that would be less than `0`.
80    pub fn checked_sub(&self, x: u64) -> Option<EraId> {
81        self.0.checked_sub(x).map(EraId)
82    }
83
84    /// Returns the current era minus `x`, or `0` if that would be less than `0`.
85    #[must_use]
86    pub fn saturating_sub(&self, x: u64) -> EraId {
87        EraId::from(self.0.saturating_sub(x))
88    }
89
90    /// Returns the current era plus `x`, or [`EraId::MAX`] if overflow would occur.
91    #[must_use]
92    pub fn saturating_add(self, rhs: u64) -> EraId {
93        EraId(self.0.saturating_add(rhs))
94    }
95
96    /// Returns the current era times `x`, or [`EraId::MAX`] if overflow would occur.
97    #[must_use]
98    pub fn saturating_mul(&self, x: u64) -> EraId {
99        EraId::from(self.0.saturating_mul(x))
100    }
101
102    /// Returns whether this is era 0.
103    pub fn is_genesis(&self) -> bool {
104        self.0 == 0
105    }
106
107    /// Returns little endian bytes.
108    pub fn to_le_bytes(self) -> [u8; 8] {
109        self.0.to_le_bytes()
110    }
111
112    /// Returns a raw value held by this [`EraId`] instance.
113    ///
114    /// You should prefer [`From`] trait implementations over this method where possible.
115    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)] // The caller must make sure this doesn't overflow.
132    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)] // The caller must make sure this doesn't overflow.
147    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(&current_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}