solana_stake_interface/
error.rs

1use {
2    num_traits::{FromPrimitive, ToPrimitive},
3    solana_program_error::ProgramError,
4};
5
6/// Reasons the Stake might have had an error.
7#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
8#[cfg_attr(
9    feature = "serde",
10    derive(serde_derive::Deserialize, serde_derive::Serialize)
11)]
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub enum StakeError {
14    // 0
15    /// Not enough credits to redeem.
16    NoCreditsToRedeem,
17
18    /// Lockup has not yet expired.
19    LockupInForce,
20
21    /// Stake already deactivated.
22    AlreadyDeactivated,
23
24    /// One re-delegation permitted per epoch.
25    TooSoonToRedelegate,
26
27    /// Split amount is more than is staked.
28    InsufficientStake,
29
30    // 5
31    /// Stake account with transient stake cannot be merged.
32    MergeTransientStake,
33
34    /// Stake account merge failed due to different authority, lockups or state.
35    MergeMismatch,
36
37    /// Custodian address not present.
38    CustodianMissing,
39
40    /// Custodian signature not present.
41    CustodianSignatureMissing,
42
43    /// Insufficient voting activity in the reference vote account.
44    InsufficientReferenceVotes,
45
46    // 10
47    /// Stake account is not delegated to the provided vote account.
48    VoteAddressMismatch,
49
50    /// Stake account has not been delinquent for the minimum epochs required
51    /// for deactivation.
52    MinimumDelinquentEpochsForDeactivationNotMet,
53
54    /// Delegation amount is less than the minimum.
55    InsufficientDelegation,
56
57    /// Stake account with transient or inactive stake cannot be redelegated.
58    RedelegateTransientOrInactiveStake,
59
60    /// Stake redelegation to the same vote account is not permitted.
61    RedelegateToSameVoteAccount,
62
63    // 15
64    /// Redelegated stake must be fully activated before deactivation.
65    RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted,
66
67    /// Stake action is not permitted while the epoch rewards period is active.
68    EpochRewardsActive,
69}
70
71impl From<StakeError> for ProgramError {
72    fn from(e: StakeError) -> Self {
73        ProgramError::Custom(e as u32)
74    }
75}
76
77impl FromPrimitive for StakeError {
78    #[inline]
79    fn from_i64(n: i64) -> Option<Self> {
80        if n == Self::NoCreditsToRedeem as i64 {
81            Some(Self::NoCreditsToRedeem)
82        } else if n == Self::LockupInForce as i64 {
83            Some(Self::LockupInForce)
84        } else if n == Self::AlreadyDeactivated as i64 {
85            Some(Self::AlreadyDeactivated)
86        } else if n == Self::TooSoonToRedelegate as i64 {
87            Some(Self::TooSoonToRedelegate)
88        } else if n == Self::InsufficientStake as i64 {
89            Some(Self::InsufficientStake)
90        } else if n == Self::MergeTransientStake as i64 {
91            Some(Self::MergeTransientStake)
92        } else if n == Self::MergeMismatch as i64 {
93            Some(Self::MergeMismatch)
94        } else if n == Self::CustodianMissing as i64 {
95            Some(Self::CustodianMissing)
96        } else if n == Self::CustodianSignatureMissing as i64 {
97            Some(Self::CustodianSignatureMissing)
98        } else if n == Self::InsufficientReferenceVotes as i64 {
99            Some(Self::InsufficientReferenceVotes)
100        } else if n == Self::VoteAddressMismatch as i64 {
101            Some(Self::VoteAddressMismatch)
102        } else if n == Self::MinimumDelinquentEpochsForDeactivationNotMet as i64 {
103            Some(Self::MinimumDelinquentEpochsForDeactivationNotMet)
104        } else if n == Self::InsufficientDelegation as i64 {
105            Some(Self::InsufficientDelegation)
106        } else if n == Self::RedelegateTransientOrInactiveStake as i64 {
107            Some(Self::RedelegateTransientOrInactiveStake)
108        } else if n == Self::RedelegateToSameVoteAccount as i64 {
109            Some(Self::RedelegateToSameVoteAccount)
110        } else if n == Self::RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted as i64 {
111            Some(Self::RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted)
112        } else if n == Self::EpochRewardsActive as i64 {
113            Some(Self::EpochRewardsActive)
114        } else {
115            None
116        }
117    }
118    #[inline]
119    fn from_u64(n: u64) -> Option<Self> {
120        Self::from_i64(n as i64)
121    }
122}
123
124impl ToPrimitive for StakeError {
125    #[inline]
126    fn to_i64(&self) -> Option<i64> {
127        Some(match *self {
128            Self::NoCreditsToRedeem => Self::NoCreditsToRedeem as i64,
129            Self::LockupInForce => Self::LockupInForce as i64,
130            Self::AlreadyDeactivated => Self::AlreadyDeactivated as i64,
131            Self::TooSoonToRedelegate => Self::TooSoonToRedelegate as i64,
132            Self::InsufficientStake => Self::InsufficientStake as i64,
133            Self::MergeTransientStake => Self::MergeTransientStake as i64,
134            Self::MergeMismatch => Self::MergeMismatch as i64,
135            Self::CustodianMissing => Self::CustodianMissing as i64,
136            Self::CustodianSignatureMissing => Self::CustodianSignatureMissing as i64,
137            Self::InsufficientReferenceVotes => Self::InsufficientReferenceVotes as i64,
138            Self::VoteAddressMismatch => Self::VoteAddressMismatch as i64,
139            Self::MinimumDelinquentEpochsForDeactivationNotMet => {
140                Self::MinimumDelinquentEpochsForDeactivationNotMet as i64
141            }
142            Self::InsufficientDelegation => Self::InsufficientDelegation as i64,
143            Self::RedelegateTransientOrInactiveStake => {
144                Self::RedelegateTransientOrInactiveStake as i64
145            }
146            Self::RedelegateToSameVoteAccount => Self::RedelegateToSameVoteAccount as i64,
147            Self::RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted => {
148                Self::RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted as i64
149            }
150            Self::EpochRewardsActive => Self::EpochRewardsActive as i64,
151        })
152    }
153    #[inline]
154    fn to_u64(&self) -> Option<u64> {
155        self.to_i64().map(|x| x as u64)
156    }
157}
158
159impl core::error::Error for StakeError {}
160
161impl core::fmt::Display for StakeError {
162    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
163        match self {
164            StakeError::NoCreditsToRedeem => f.write_str("not enough credits to redeem"),
165            StakeError::LockupInForce => f.write_str("lockup has not yet expired"),
166            StakeError::AlreadyDeactivated => f.write_str("stake already deactivated"),
167            StakeError::TooSoonToRedelegate => f.write_str("one re-delegation permitted per epoch"),
168            StakeError::InsufficientStake => f.write_str("split amount is more than is staked"),
169            StakeError::MergeTransientStake => {
170                f.write_str("stake account with transient stake cannot be merged")
171            }
172            StakeError::MergeMismatch => f.write_str(
173                "stake account merge failed due to different authority, lockups or state",
174            ),
175            StakeError::CustodianMissing => f.write_str("custodian address not present"),
176            StakeError::CustodianSignatureMissing => f.write_str("custodian signature not present"),
177            StakeError::InsufficientReferenceVotes => {
178                f.write_str("insufficient voting activity in the reference vote account")
179            }
180            StakeError::VoteAddressMismatch => {
181                f.write_str("stake account is not delegated to the provided vote account")
182            }
183            StakeError::MinimumDelinquentEpochsForDeactivationNotMet => f.write_str(
184                "stake account has not been delinquent for the minimum epochs required for \
185                     deactivation",
186            ),
187            StakeError::InsufficientDelegation => {
188                f.write_str("delegation amount is less than the minimum")
189            }
190            StakeError::RedelegateTransientOrInactiveStake => {
191                f.write_str("stake account with transient or inactive stake cannot be redelegated")
192            }
193            StakeError::RedelegateToSameVoteAccount => {
194                f.write_str("stake redelegation to the same vote account is not permitted")
195            }
196            StakeError::RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted => {
197                f.write_str("redelegated stake must be fully activated before deactivation")
198            }
199            StakeError::EpochRewardsActive => f.write_str(
200                "stake action is not permitted while the epoch rewards period is active",
201            ),
202        }
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use {super::StakeError, num_traits::FromPrimitive, strum::IntoEnumIterator};
209
210    #[test]
211    fn test_stake_error_from_primitive_exhaustive() {
212        for variant in StakeError::iter() {
213            let variant_i64 = variant.clone() as i64;
214            assert_eq!(
215                StakeError::from_repr(variant_i64 as usize),
216                StakeError::from_i64(variant_i64)
217            );
218        }
219    }
220}