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;
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 pub fn epoch_number(&self, slot: Slot) -> ScEpochNumber {
91 self.slots_per_epoch.epoch_number(slot)
92 }
93
94 pub fn first_slot_number(&self, epoch: ScEpochNumber) -> Slot {
96 self.slots_per_epoch.first_slot_number(epoch)
97 }
98
99 pub fn slot_from_timestamp(&self, timestamp: u64) -> Slot {
101 Slot::from_timestamp(timestamp.into(), self.slot_duration)
102 }
103
104 pub fn epoch_start_time(&self, epoch: ScEpochNumber) -> Option<Timestamp> {
106 self.first_slot_number(epoch)
107 .timestamp(self.slot_duration)
108 .map(|s| Timestamp::from_unix_millis(s.as_millis()))
109 }
110}
111
112pub fn epoch_number(slot: Slot, slots_per_epoch: u32) -> ScEpochNumber {
114 ScEpochNumber(*slot / u64::from(slots_per_epoch))
115}
116
117pub fn first_slot_number(epoch: ScEpochNumber, slots_per_epoch: u32) -> Slot {
119 Slot::from(epoch.0 * slots_per_epoch as u64)
120}
121
122pub fn slot_number_in_epoch(slot: Slot, slots_per_epoch: u32) -> u32 {
124 u32::try_from(slot.rem(u64::from(slots_per_epoch)))
125 .expect("slots_per_epoch is u32, thus any modulo reminder of it is also u32")
126}
127
128pub fn is_last_slot_of_an_epoch(slot: Slot, slots_per_epoch: u32) -> bool {
130 slot_number_in_epoch(slot, slots_per_epoch) == slots_per_epoch - 1
131}
132
133pub enum Error {
135 OverflowError,
137}
138
139sp_api::decl_runtime_apis! {
140 pub trait SlotApi {
142 fn slot_config() -> ScSlotConfig;
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use proptest::prelude::*;
151
152 prop_compose! {
153 fn arb_slot()(slot_number in 0..u64::MAX) -> Slot {
154 Slot::from(slot_number)
155 }
156 }
157
158 proptest! {
159 #[test]
160 fn slot_number_is_slot_modulo_slots_per_epoch(slot in arb_slot(), slots_per_epoch in 1..u32::MAX) {
161 let expected =u32::try_from(*slot % u64::from(slots_per_epoch)).unwrap();
162 assert_eq!(expected, slot_number_in_epoch(slot, slots_per_epoch))
163 }
164 }
165}