solana_program/stake/
tools.rs

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