1use std::fmt::{Display, Formatter};
2use std::num::TryFromIntError;
3use std::ops::{Deref, DerefMut};
4
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8use crate::entities::arithmetic_operation_wrapper::{
9 impl_add_to_wrapper, impl_partial_eq_to_wrapper, impl_sub_to_wrapper,
10};
11
12#[derive(
14 Debug, Copy, Clone, Default, PartialEq, Serialize, Deserialize, Hash, Eq, PartialOrd, Ord,
15)]
16pub struct Epoch(pub u64);
17
18impl Epoch {
19 pub const SIGNER_RETRIEVAL_OFFSET: i64 = -1;
21
22 pub const NEXT_SIGNER_RETRIEVAL_OFFSET: u64 = 0;
25
26 pub const SIGNER_RECORDING_OFFSET: u64 = 1;
28
29 pub const EPOCH_SETTINGS_RECORDING_OFFSET: u64 = 2;
31
32 pub const SIGNER_SIGNING_OFFSET: u64 = 2;
35
36 pub const CARDANO_STAKE_DISTRIBUTION_SNAPSHOT_OFFSET: u64 = 2;
39
40 pub const SIGNER_LEADER_SYNCHRONIZATION_OFFSET: u64 = 0;
42
43 pub fn offset_by(&self, epoch_offset: i64) -> Result<Self, EpochError> {
47 let epoch_new = self.0 as i64 + epoch_offset;
48 if epoch_new < 0 {
49 return Err(EpochError::EpochOffset(self.0, epoch_offset));
50 }
51 Ok(Epoch(epoch_new as u64))
52 }
53
54 pub fn offset_to_signer_retrieval_epoch(&self) -> Result<Self, EpochError> {
56 self.offset_by(Self::SIGNER_RETRIEVAL_OFFSET)
57 }
58
59 pub fn offset_to_next_signer_retrieval_epoch(&self) -> Self {
61 *self + Self::NEXT_SIGNER_RETRIEVAL_OFFSET
62 }
63
64 pub fn offset_to_recording_epoch(&self) -> Self {
66 *self + Self::SIGNER_RECORDING_OFFSET
67 }
68
69 pub fn offset_to_epoch_settings_recording_epoch(&self) -> Self {
71 *self + Self::EPOCH_SETTINGS_RECORDING_OFFSET
72 }
73
74 pub fn offset_to_signer_signing_offset(&self) -> Self {
76 *self + Self::SIGNER_SIGNING_OFFSET
77 }
78
79 pub fn offset_to_cardano_stake_distribution_snapshot_epoch(&self) -> Self {
81 *self + Self::CARDANO_STAKE_DISTRIBUTION_SNAPSHOT_OFFSET
82 }
83
84 pub fn offset_to_leader_synchronization_epoch(&self) -> Self {
86 *self + Self::SIGNER_LEADER_SYNCHRONIZATION_OFFSET
87 }
88
89 pub fn next(&self) -> Self {
91 *self + 1
92 }
93
94 pub fn previous(&self) -> Result<Self, EpochError> {
96 self.offset_by(-1)
97 }
98
99 pub fn has_gap_with(&self, other: &Epoch) -> bool {
101 self.0.abs_diff(other.0) > 1
102 }
103}
104
105impl Deref for Epoch {
106 type Target = u64;
107
108 fn deref(&self) -> &Self::Target {
109 &self.0
110 }
111}
112
113impl DerefMut for Epoch {
114 fn deref_mut(&mut self) -> &mut Self::Target {
115 &mut self.0
116 }
117}
118
119impl_add_to_wrapper!(Epoch, u64);
120impl_sub_to_wrapper!(Epoch, u64);
121impl_partial_eq_to_wrapper!(Epoch, u64);
122
123impl Display for Epoch {
124 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
125 write!(f, "{}", self.0)
126 }
127}
128
129impl TryInto<i64> for Epoch {
130 type Error = TryFromIntError;
131
132 fn try_into(self) -> Result<i64, Self::Error> {
133 self.0.try_into()
134 }
135}
136
137impl TryInto<i64> for &Epoch {
138 type Error = TryFromIntError;
139
140 fn try_into(self) -> Result<i64, Self::Error> {
141 self.0.try_into()
142 }
143}
144
145impl From<Epoch> for f64 {
146 fn from(value: Epoch) -> f64 {
147 value.0 as f64
148 }
149}
150
151#[derive(Error, Debug)]
153pub enum EpochError {
154 #[error("epoch offset error")]
156 EpochOffset(u64, i64),
157}
158
159#[cfg(test)]
160mod tests {
161 use crate::entities::arithmetic_operation_wrapper::tests::test_op_assign;
162
163 use super::*;
164
165 #[test]
166 fn test_display() {
167 assert_eq!(format!("{}", Epoch(72)), "72");
168 assert_eq!(format!("{}", &Epoch(13224)), "13224");
169 }
170
171 #[test]
172 fn test_serialize() {
173 assert_eq!(serde_json::to_string(&Epoch(72)).unwrap(), "72");
174 }
175
176 #[test]
177 fn test_deserialize() {
178 let block_number: Epoch = serde_json::from_str("13224").unwrap();
179 assert_eq!(block_number, Epoch(13224));
180 }
181
182 #[test]
183 #[allow(clippy::op_ref)]
184 fn test_add() {
185 assert_eq!(Epoch(4), Epoch(1) + Epoch(3));
186 assert_eq!(Epoch(4), Epoch(1) + 3_u64);
187 assert_eq!(Epoch(4), Epoch(1) + &3_u64);
188
189 assert_eq!(Epoch(4), 3_u64 + Epoch(1));
190 assert_eq!(Epoch(4), 3_u64 + &Epoch(1));
191 assert_eq!(Epoch(4), &3_u64 + Epoch(1));
192 assert_eq!(Epoch(4), &3_u64 + &Epoch(1));
193
194 test_op_assign!(Epoch(1), +=, Epoch(3) => Epoch(4));
195 test_op_assign!(Epoch(1), +=, 3_u64 => Epoch(4));
196 test_op_assign!(Epoch(1), +=, &3_u64 => Epoch(4));
197
198 test_op_assign!(1_u64, +=, Epoch(3) => 4_u64);
199 test_op_assign!(1_u64, +=, &Epoch(3) => 4_u64);
200 }
201
202 #[test]
203 #[allow(clippy::op_ref)]
204 fn test_sub() {
205 assert_eq!(Epoch(8), Epoch(14) - Epoch(6));
206 assert_eq!(Epoch(8), Epoch(14) - 6_u64);
207 assert_eq!(Epoch(8), Epoch(14) - &6_u64);
208
209 assert_eq!(Epoch(8), 6_u64 - Epoch(14));
210 assert_eq!(Epoch(8), 6_u64 - &Epoch(14));
211 assert_eq!(Epoch(8), &6_u64 - Epoch(14));
212 assert_eq!(Epoch(8), &6_u64 - &Epoch(14));
213
214 test_op_assign!(Epoch(14), -=, Epoch(6) => Epoch(8));
215 test_op_assign!(Epoch(14), -=, 6_u64 => Epoch(8));
216 test_op_assign!(Epoch(14), -=, &6_u64 => Epoch(8));
217
218 test_op_assign!(14_u64, -=, Epoch(6) => 8_u64);
219 test_op_assign!(14_u64, -=, &Epoch(6) => 8_u64);
220 }
221
222 #[test]
223 fn saturating_sub() {
224 assert_eq!(Epoch(0), Epoch(1) - Epoch(5));
225 assert_eq!(Epoch(0), Epoch(1) - 5_u64);
226 }
227
228 #[test]
229 fn test_previous() {
230 assert_eq!(Epoch(2), Epoch(3).previous().unwrap());
231 assert!(Epoch(0).previous().is_err());
232 }
233
234 #[test]
235 fn test_next() {
236 assert_eq!(Epoch(4), Epoch(3).next());
237 }
238
239 #[test]
240 fn test_eq() {
241 assert_eq!(Epoch(1), Epoch(1));
242 assert_eq!(Epoch(2), &Epoch(2));
243 assert_eq!(&Epoch(3), Epoch(3));
244 assert_eq!(&Epoch(4), &Epoch(4));
245
246 assert_eq!(Epoch(5), 5);
247 assert_eq!(Epoch(6), &6);
248 assert_eq!(&Epoch(7), 7);
249 assert_eq!(&Epoch(8), &8);
250
251 assert_eq!(9, Epoch(9));
252 assert_eq!(10, &Epoch(10));
253 assert_eq!(&11, Epoch(11));
254 assert_eq!(&12, &Epoch(12));
255 }
256
257 #[test]
258 fn test_has_gap_ok() {
259 assert!(Epoch(3).has_gap_with(&Epoch(5)));
260 assert!(!Epoch(3).has_gap_with(&Epoch(4)));
261 assert!(!Epoch(3).has_gap_with(&Epoch(3)));
262 assert!(!Epoch(3).has_gap_with(&Epoch(2)));
263 assert!(Epoch(3).has_gap_with(&Epoch(0)));
264 }
265}