1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
use crate::errors::RegistryError;
use aligned_sized::aligned_sized;
use anchor_lang::prelude::*;

#[aligned_sized(anchor)]
#[derive(Debug)]
#[account]
pub struct ProtocolConfigPda {
    pub authority: Pubkey,
    pub bump: u8,
    pub config: ProtocolConfig,
}

/// Epoch Phases:
/// 1. Registration
/// 2. Active
/// 3. Report Work
/// 4. Post (Epoch has ended, and rewards can be claimed.)
/// - There is always an active phase in progress, registration and report work
///   phases run in parallel to a currently active phase.
#[derive(Debug, Clone, Copy, PartialEq, Eq, AnchorSerialize, AnchorDeserialize)]
pub struct ProtocolConfig {
    /// Solana slot when the protocol starts operating.
    pub genesis_slot: u64,
    /// Minimum weight required for a forester to register to an epoch.
    pub min_weight: u64,
    /// Light protocol slot length.
    pub slot_length: u64,
    /// Foresters can register for this phase.
    pub registration_phase_length: u64,
    /// Foresters can perform work in this phase.
    pub active_phase_length: u64,
    /// Foresters can report work to receive performance based rewards in this
    /// phase.
    pub report_work_phase_length: u64,
    pub network_fee: u64,
    pub cpi_context_size: u64,
    pub finalize_counter_limit: u64,
    /// Placeholder for future protocol updates.
    pub place_holder: Pubkey,
    pub place_holder_a: u64,
    pub place_holder_b: u64,
    pub place_holder_c: u64,
    pub place_holder_d: u64,
    pub place_holder_e: u64,
    pub place_holder_f: u64,
}

impl Default for ProtocolConfig {
    fn default() -> Self {
        Self {
            genesis_slot: 0,
            min_weight: 1,
            slot_length: 10,
            registration_phase_length: 100,
            active_phase_length: 1000,
            report_work_phase_length: 100,
            network_fee: 5000,
            cpi_context_size: 20 * 1024 + 8,
            finalize_counter_limit: 100,
            place_holder: Pubkey::default(),
            place_holder_a: 0,
            place_holder_b: 0,
            place_holder_c: 0,
            place_holder_d: 0,
            place_holder_e: 0,
            place_holder_f: 0,
        }
    }
}

#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub enum EpochState {
    Registration,
    Active,
    ReportWork,
    Post,
    #[default]
    Pre,
}

/// Light Epoch Example:
///
///  Diagram of epochs 0 and 1.
/// Registration 0 starts at genesis slot.
/// |---- Registration 0 ----|------------------ Active 0 ------|---- Report Work 0 ----|---- Post 0 ----
///                                        |-- Registration 1 --|------------------ Active 1 -----------------
/// (Post epoch does not end unlike the other phases.)
///
/// let genesis = 0;
/// let registration_phase_length = 100;
/// let active_phase_length = 1000;
/// let report_work_phase_length = 100;
/// let slot = 10;
///
/// To get the latest registry epoch:
/// - slot = 0;
/// let current_registry_epoch = (slot - genesis) / active_phase_length;
/// current_registry_epoch =  (0 - 0) / 1000 = 0;
/// first active phase starts at genesis + registration_phase_length
///     = 0 + 100 = 100;
///
/// To get the current active epoch:
/// - slot = 100;
/// let current_active_epoch =
///     (slot - genesis - registration_phase_length) / active_phase_length;
/// current_active_epoch = (100 - 0 - 100) / 1000 = 0;
///
/// Epoch 0:
/// - Registration 0: 0 - 100
/// - Active 0: 100 - 1100
/// - Report Work 0: 1100 - 1200
/// - Post 0: 1200 - inf
///
/// Epoch 1:
/// - Registration 1: 1000 - 1100
/// - Active 1: 1100 - 2100
/// - Report Work 1: 2100 - 2200
/// - Post 1: 2200 - inf
///
/// Epoch 2:
/// - Registration 2: 2000 - 2100
/// - Active 2: 2100 - 3100
/// - Report Work 2: 3100 - 3200
/// - Post 2: 3200 - inf
///
impl ProtocolConfig {
    /// Current epoch including registration phase.
    pub fn get_latest_register_epoch(&self, slot: u64) -> Result<u64> {
        let slot = slot
            .checked_sub(self.genesis_slot)
            .ok_or(RegistryError::GetLatestedRegisterEpochFailed)?;
        Ok(slot / self.active_phase_length)
    }

    pub fn get_current_epoch(&self, slot: u64) -> u64 {
        (slot.saturating_sub(self.genesis_slot)) / self.active_phase_length
    }

    pub fn get_current_active_epoch(&self, slot: u64) -> Result<u64> {
        let slot = slot
            .checked_sub(self.genesis_slot + self.registration_phase_length)
            .ok_or(RegistryError::GetLatestActiveEpochFailed)?;
        Ok(slot / self.active_phase_length)
    }

    pub fn get_latest_register_epoch_progress(&self, slot: u64) -> Result<u64> {
        Ok(slot
            .checked_sub(self.genesis_slot)
            .ok_or(RegistryError::ArithmeticUnderflow)?
            % self.active_phase_length)
    }

    pub fn get_current_active_epoch_progress(&self, slot: u64) -> u64 {
        (slot
            .checked_sub(self.genesis_slot + self.registration_phase_length)
            .unwrap())
            % self.active_phase_length
    }

    /// In the last part of the active phase the registration phase starts.
    /// Returns end slot of the registration phase/start slot of the next active phase.
    pub fn is_registration_phase(&self, slot: u64) -> Result<u64> {
        let latest_register_epoch = self.get_latest_register_epoch(slot)?;
        let latest_register_epoch_progress = self.get_latest_register_epoch_progress(slot)?;
        if latest_register_epoch_progress >= self.registration_phase_length {
            return err!(RegistryError::NotInRegistrationPeriod);
        }
        Ok((latest_register_epoch) * self.active_phase_length
            + self.genesis_slot
            + self.registration_phase_length)
    }

    pub fn is_active_phase(&self, slot: u64, epoch: u64) -> Result<()> {
        if self.get_current_active_epoch(slot)? != epoch {
            return err!(RegistryError::NotInActivePhase);
        }
        Ok(())
    }

    pub fn is_report_work_phase(&self, slot: u64, epoch: u64) -> Result<()> {
        self.is_active_phase(slot, epoch + 1)?;
        let current_epoch_progress = self.get_current_active_epoch_progress(slot);
        if current_epoch_progress >= self.report_work_phase_length {
            return err!(RegistryError::NotInReportWorkPhase);
        }
        Ok(())
    }

    pub fn is_post_epoch(&self, slot: u64, epoch: u64) -> Result<()> {
        if self.get_current_active_epoch(slot)? <= epoch {
            return err!(RegistryError::InvalidEpoch);
        }
        Ok(())
    }
}