solana_stake_interface/
tools.rs

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