clone_solana_stake_interface/
error.rs

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