1#![cfg_attr(not(feature = "std"), no_std)]
5#![deny(missing_docs)]
6
7#[cfg(feature = "std")]
8pub mod runtime_api_client;
9
10use core::{ops::Rem, time::Duration};
11use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
12use scale_info::TypeInfo;
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15use sidechain_domain::{ScEpochNumber, ScSlotNumber};
16pub use sp_consensus_slots::{Slot, SlotDuration};
17use sp_core::offchain::Timestamp;
18
19#[derive(Clone, Copy, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
27#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
28pub struct SlotsPerEpoch(pub u32);
29
30pub fn default_slots_per_epoch() -> u32 {
34 60
35}
36
37impl Default for SlotsPerEpoch {
38 fn default() -> Self {
40 SlotsPerEpoch(60)
41 }
42}
43
44impl SlotsPerEpoch {
45 #[cfg(all(feature = "std", feature = "serde"))]
47 pub fn read_from_env() -> Result<Self, envy::Error> {
48 #[derive(Serialize, Deserialize)]
49 struct SlotsPerEpochEnvConfig {
50 #[serde(default = "default_slots_per_epoch")]
51 slots_per_epoch: u32,
52 }
53
54 let raw = envy::from_env::<SlotsPerEpochEnvConfig>()?;
55 Ok(Self(raw.slots_per_epoch))
56 }
57
58 pub fn epoch_number(&self, slot: Slot) -> ScEpochNumber {
60 epoch_number(slot, self.0)
61 }
62
63 pub fn epoch_number_from_sc_slot(&self, slot: ScSlotNumber) -> ScEpochNumber {
65 epoch_number(Slot::from(slot.0), self.0)
66 }
67
68 pub fn first_slot_number(&self, epoch: ScEpochNumber) -> Slot {
70 first_slot_number(epoch, self.0)
71 }
72
73 pub fn slot_number_in_epoch(&self, slot: Slot) -> u32 {
75 slot_number_in_epoch(slot, self.0)
76 }
77}
78
79#[derive(Clone, Debug, Encode, Decode, TypeInfo)]
81pub struct ScSlotConfig {
82 pub slots_per_epoch: SlotsPerEpoch,
84 pub slot_duration: SlotDuration,
86}
87
88impl ScSlotConfig {
89 #[cfg(feature = "std")]
90 pub fn epoch_duration(&self) -> Duration {
92 self.slot_duration.as_duration() * self.slots_per_epoch.0
93 }
94
95 pub fn epoch_number(&self, slot: Slot) -> ScEpochNumber {
97 self.slots_per_epoch.epoch_number(slot)
98 }
99
100 pub fn first_slot_number(&self, epoch: ScEpochNumber) -> Slot {
102 self.slots_per_epoch.first_slot_number(epoch)
103 }
104
105 pub fn slot_from_timestamp(&self, timestamp: u64) -> Slot {
107 Slot::from_timestamp(timestamp.into(), self.slot_duration)
108 }
109
110 pub fn slot_start_time(&self, slot: Slot) -> Timestamp {
112 Timestamp::from_unix_millis(self.slot_duration.as_millis() * u64::from(slot))
113 }
114
115 pub fn epoch_start_time(&self, epoch: ScEpochNumber) -> Option<Timestamp> {
117 self.first_slot_number(epoch)
118 .timestamp(self.slot_duration)
119 .map(|s| Timestamp::from_unix_millis(s.as_millis()))
120 }
121}
122
123pub fn epoch_number(slot: Slot, slots_per_epoch: u32) -> ScEpochNumber {
125 ScEpochNumber(*slot / u64::from(slots_per_epoch))
126}
127
128pub fn first_slot_number(epoch: ScEpochNumber, slots_per_epoch: u32) -> Slot {
130 Slot::from(epoch.0 * slots_per_epoch as u64)
131}
132
133pub fn slot_number_in_epoch(slot: Slot, slots_per_epoch: u32) -> u32 {
135 u32::try_from(slot.rem(u64::from(slots_per_epoch)))
136 .expect("slots_per_epoch is u32, thus any modulo reminder of it is also u32")
137}
138
139pub fn is_last_slot_of_an_epoch(slot: Slot, slots_per_epoch: u32) -> bool {
141 slot_number_in_epoch(slot, slots_per_epoch) == slots_per_epoch - 1
142}
143
144pub enum Error {
146 OverflowError,
148}
149
150sp_api::decl_runtime_apis! {
151 pub trait SlotApi {
153 fn slot_config() -> ScSlotConfig;
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use proptest::prelude::*;
162
163 prop_compose! {
164 fn arb_slot()(slot_number in 0..u64::MAX) -> Slot {
165 Slot::from(slot_number)
166 }
167 }
168
169 proptest! {
170 #[test]
171 fn slot_number_is_slot_modulo_slots_per_epoch(slot in arb_slot(), slots_per_epoch in 1..u32::MAX) {
172 let expected =u32::try_from(*slot % u64::from(slots_per_epoch)).unwrap();
173 assert_eq!(expected, slot_number_in_epoch(slot, slots_per_epoch))
174 }
175 }
176}