solana_stake_interface/
error.rs1#[cfg(feature = "codama")]
2use codama_macros::CodamaErrors;
3use {
4 num_traits::{FromPrimitive, ToPrimitive},
5 solana_program_error::ProgramError,
6};
7
8#[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 #[cfg_attr(
20 feature = "codama",
21 codama(error(message = "Not enough credits to redeem"))
22 )]
23 NoCreditsToRedeem,
24
25 #[cfg_attr(
28 feature = "codama",
29 codama(error(message = "Lockup has not yet expired"))
30 )]
31 LockupInForce,
32
33 #[cfg_attr(
36 feature = "codama",
37 codama(error(message = "Stake already deactivated"))
38 )]
39 AlreadyDeactivated,
40
41 #[cfg_attr(
44 feature = "codama",
45 codama(error(message = "One re-delegation permitted per epoch"))
46 )]
47 TooSoonToRedelegate,
48
49 #[cfg_attr(
52 feature = "codama",
53 codama(error(message = "Split amount is more than is staked"))
54 )]
55 InsufficientStake,
56
57 #[cfg_attr(
60 feature = "codama",
61 codama(error(message = "Stake account with transient stake cannot be merged"))
62 )]
63 MergeTransientStake,
64
65 #[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 #[cfg_attr(
78 feature = "codama",
79 codama(error(message = "Custodian address not present"))
80 )]
81 CustodianMissing,
82
83 #[cfg_attr(
86 feature = "codama",
87 codama(error(message = "Custodian signature not present"))
88 )]
89 CustodianSignatureMissing,
90
91 #[cfg_attr(
94 feature = "codama",
95 codama(error(message = "Insufficient voting activity in the reference vote account"))
96 )]
97 InsufficientReferenceVotes,
98
99 #[cfg_attr(
102 feature = "codama",
103 codama(error(message = "Stake account is not delegated to the provided vote account"))
104 )]
105 VoteAddressMismatch,
106
107 #[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 #[cfg_attr(
121 feature = "codama",
122 codama(error(message = "Delegation amount is less than the minimum"))
123 )]
124 InsufficientDelegation,
125
126 #[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 #[cfg_attr(
139 feature = "codama",
140 codama(error(message = "Stake redelegation to the same vote account is not permitted"))
141 )]
142 RedelegateToSameVoteAccount,
143
144 #[cfg_attr(
147 feature = "codama",
148 codama(error(message = "Redelegated stake must be fully activated before deactivation"))
149 )]
150 RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted,
151
152 #[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}