cbe_program/stake/
tools.rs

1//! Utility functions
2use crate::{
3    clock::Epoch, program_error::ProgramError, stake::MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION,
4};
5
6/// Helper function for programs to call [`GetMinimumDelegation`] and then fetch the return data
7///
8/// This fn handles performing the CPI to call the [`GetMinimumDelegation`] function, and then
9/// calls [`get_return_data()`] to fetch the return data.
10///
11/// [`GetMinimumDelegation`]: super::instruction::StakeInstruction::GetMinimumDelegation
12/// [`get_return_data()`]: crate::program::get_return_data
13pub fn get_minimum_delegation() -> Result<u64, ProgramError> {
14    let instruction = super::instruction::get_minimum_delegation();
15    crate::program::invoke_unchecked(&instruction, &[])?;
16    get_minimum_delegation_return_data()
17}
18
19/// Helper function for programs to get the return data after calling [`GetMinimumDelegation`]
20///
21/// This fn handles calling [`get_return_data()`], ensures the result is from the correct
22/// program, and returns the correct type.
23///
24/// [`GetMinimumDelegation`]: super::instruction::StakeInstruction::GetMinimumDelegation
25/// [`get_return_data()`]: crate::program::get_return_data
26fn get_minimum_delegation_return_data() -> Result<u64, ProgramError> {
27    crate::program::get_return_data()
28        .ok_or(ProgramError::InvalidInstructionData)
29        .and_then(|(program_id, return_data)| {
30            (program_id == super::program::id())
31                .then_some(return_data)
32                .ok_or(ProgramError::IncorrectProgramId)
33        })
34        .and_then(|return_data| {
35            return_data
36                .try_into()
37                .or(Err(ProgramError::InvalidInstructionData))
38        })
39        .map(u64::from_le_bytes)
40}
41
42// Check if the provided `epoch_credits` demonstrate active voting over the previous
43// `MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION`
44pub fn acceptable_reference_epoch_credits(
45    epoch_credits: &[(Epoch, u64, u64)],
46    current_epoch: Epoch,
47) -> bool {
48    if let Some(epoch_index) = epoch_credits
49        .len()
50        .checked_sub(MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION)
51    {
52        let mut epoch = current_epoch;
53        for (vote_epoch, ..) in epoch_credits[epoch_index..].iter().rev() {
54            if *vote_epoch != epoch {
55                return false;
56            }
57            epoch = epoch.saturating_sub(1);
58        }
59        true
60    } else {
61        false
62    }
63}
64
65// Check if the provided `epoch_credits` demonstrate delinquency over the previous
66// `MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION`
67pub fn eligible_for_deactivate_delinquent(
68    epoch_credits: &[(Epoch, u64, u64)],
69    current_epoch: Epoch,
70) -> bool {
71    match epoch_credits.last() {
72        None => true,
73        Some((epoch, ..)) => {
74            if let Some(minimum_epoch) =
75                current_epoch.checked_sub(MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION as Epoch)
76            {
77                *epoch <= minimum_epoch
78            } else {
79                false
80            }
81        }
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn test_acceptable_reference_epoch_credits() {
91        let epoch_credits = [];
92        assert!(!acceptable_reference_epoch_credits(&epoch_credits, 0));
93
94        let epoch_credits = [(0, 42, 42), (1, 42, 42), (2, 42, 42), (3, 42, 42)];
95        assert!(!acceptable_reference_epoch_credits(&epoch_credits, 3));
96
97        let epoch_credits = [
98            (0, 42, 42),
99            (1, 42, 42),
100            (2, 42, 42),
101            (3, 42, 42),
102            (4, 42, 42),
103        ];
104        assert!(!acceptable_reference_epoch_credits(&epoch_credits, 3));
105        assert!(acceptable_reference_epoch_credits(&epoch_credits, 4));
106
107        let epoch_credits = [
108            (1, 42, 42),
109            (2, 42, 42),
110            (3, 42, 42),
111            (4, 42, 42),
112            (5, 42, 42),
113        ];
114        assert!(acceptable_reference_epoch_credits(&epoch_credits, 5));
115
116        let epoch_credits = [
117            (0, 42, 42),
118            (2, 42, 42),
119            (3, 42, 42),
120            (4, 42, 42),
121            (5, 42, 42),
122        ];
123        assert!(!acceptable_reference_epoch_credits(&epoch_credits, 5));
124    }
125
126    #[test]
127    fn test_eligible_for_deactivate_delinquent() {
128        let epoch_credits = [];
129        assert!(eligible_for_deactivate_delinquent(&epoch_credits, 42));
130
131        let epoch_credits = [(0, 42, 42)];
132        assert!(!eligible_for_deactivate_delinquent(&epoch_credits, 0));
133
134        let epoch_credits = [(0, 42, 42)];
135        assert!(!eligible_for_deactivate_delinquent(
136            &epoch_credits,
137            MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION as Epoch - 1
138        ));
139        assert!(eligible_for_deactivate_delinquent(
140            &epoch_credits,
141            MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION as Epoch
142        ));
143
144        let epoch_credits = [(100, 42, 42)];
145        assert!(!eligible_for_deactivate_delinquent(
146            &epoch_credits,
147            100 + MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION as Epoch - 1
148        ));
149        assert!(eligible_for_deactivate_delinquent(
150            &epoch_credits,
151            100 + MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION as Epoch
152        ));
153    }
154}