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