Skip to main content

solana_stake_interface/
error.rs

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