clone_solana_stake_interface/
error.rs1use {
2 clone_solana_decode_error::DecodeError,
3 clone_solana_program_error::ProgramError,
4 num_traits::{FromPrimitive, ToPrimitive},
5};
6
7#[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 NoCreditsToRedeem,
18
19 LockupInForce,
21
22 AlreadyDeactivated,
24
25 TooSoonToRedelegate,
27
28 InsufficientStake,
30
31 MergeTransientStake,
34
35 MergeMismatch,
37
38 CustodianMissing,
40
41 CustodianSignatureMissing,
43
44 InsufficientReferenceVotes,
46
47 VoteAddressMismatch,
50
51 MinimumDelinquentEpochsForDeactivationNotMet,
54
55 InsufficientDelegation,
57
58 RedelegateTransientOrInactiveStake,
60
61 RedelegateToSameVoteAccount,
63
64 RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted,
67
68 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}