gemachain_program/stake/
instruction.rs

1use {
2    crate::stake::{
3        config,
4        program::id,
5        state::{Authorized, Lockup, StakeAuthorize, StakeState},
6    },
7    crate::{
8        clock::{Epoch, UnixTimestamp},
9        decode_error::DecodeError,
10        instruction::{AccountMeta, Instruction},
11        pubkey::Pubkey,
12        system_instruction, sysvar,
13    },
14    log::*,
15    num_derive::{FromPrimitive, ToPrimitive},
16    serde_derive::{Deserialize, Serialize},
17    thiserror::Error,
18};
19
20/// Reasons the stake might have had an error
21#[derive(Error, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)]
22pub enum StakeError {
23    #[error("not enough credits to redeem")]
24    NoCreditsToRedeem,
25
26    #[error("lockup has not yet expired")]
27    LockupInForce,
28
29    #[error("stake already deactivated")]
30    AlreadyDeactivated,
31
32    #[error("one re-delegation permitted per epoch")]
33    TooSoonToRedelegate,
34
35    #[error("split amount is more than is staked")]
36    InsufficientStake,
37
38    #[error("stake account with transient stake cannot be merged")]
39    MergeTransientStake,
40
41    #[error("stake account merge failed due to different authority, lockups or state")]
42    MergeMismatch,
43
44    #[error("custodian address not present")]
45    CustodianMissing,
46
47    #[error("custodian signature not present")]
48    CustodianSignatureMissing,
49}
50
51impl<E> DecodeError<E> for StakeError {
52    fn type_of() -> &'static str {
53        "StakeError"
54    }
55}
56
57#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
58pub enum StakeInstruction {
59    /// Initialize a stake with lockup and authorization information
60    ///
61    /// # Account references
62    ///   0. `[WRITE]` Uninitialized stake account
63    ///   1. `[]` Rent sysvar
64    ///
65    /// Authorized carries pubkeys that must sign staker transactions
66    ///   and withdrawer transactions.
67    /// Lockup carries information about withdrawal restrictions
68    Initialize(Authorized, Lockup),
69
70    /// Authorize a key to manage stake or withdrawal
71    ///
72    /// # Account references
73    ///   0. `[WRITE]` Stake account to be updated
74    ///   1. `[]` Clock sysvar
75    ///   2. `[SIGNER]` The stake or withdraw authority
76    ///   3. Optional: `[SIGNER]` Lockup authority, if updating StakeAuthorize::Withdrawer before
77    ///      lockup expiration
78    Authorize(Pubkey, StakeAuthorize),
79
80    /// Delegate a stake to a particular vote account
81    ///
82    /// # Account references
83    ///   0. `[WRITE]` Initialized stake account to be delegated
84    ///   1. `[]` Vote account to which this stake will be delegated
85    ///   2. `[]` Clock sysvar
86    ///   3. `[]` Stake history sysvar that carries stake warmup/cooldown history
87    ///   4. `[]` Address of config account that carries stake config
88    ///   5. `[SIGNER]` Stake authority
89    ///
90    /// The entire balance of the staking account is staked.  DelegateStake
91    ///   can be called multiple times, but re-delegation is delayed
92    ///   by one epoch
93    DelegateStake,
94
95    /// Split u64 tokens and stake off a stake account into another stake account.
96    ///
97    /// # Account references
98    ///   0. `[WRITE]` Stake account to be split; must be in the Initialized or Stake state
99    ///   1. `[WRITE]` Uninitialized stake account that will take the split-off amount
100    ///   2. `[SIGNER]` Stake authority
101    Split(u64),
102
103    /// Withdraw unstaked carats from the stake account
104    ///
105    /// # Account references
106    ///   0. `[WRITE]` Stake account from which to withdraw
107    ///   1. `[WRITE]` Recipient account
108    ///   2. `[]` Clock sysvar
109    ///   3. `[]` Stake history sysvar that carries stake warmup/cooldown history
110    ///   4. `[SIGNER]` Withdraw authority
111    ///   5. Optional: `[SIGNER]` Lockup authority, if before lockup expiration
112    ///
113    /// The u64 is the portion of the stake account balance to be withdrawn,
114    ///    must be `<= StakeAccount.carats - staked_carats`.
115    Withdraw(u64),
116
117    /// Deactivates the stake in the account
118    ///
119    /// # Account references
120    ///   0. `[WRITE]` Delegated stake account
121    ///   1. `[]` Clock sysvar
122    ///   2. `[SIGNER]` Stake authority
123    Deactivate,
124
125    /// Set stake lockup
126    ///
127    /// If a lockup is not active, the withdraw authority may set a new lockup
128    /// If a lockup is active, the lockup custodian may update the lockup parameters
129    ///
130    /// # Account references
131    ///   0. `[WRITE]` Initialized stake account
132    ///   1. `[SIGNER]` Lockup authority or withdraw authority
133    SetLockup(LockupArgs),
134
135    /// Merge two stake accounts.
136    ///
137    /// Both accounts must have identical lockup and authority keys. A merge
138    /// is possible between two stakes in the following states with no additional
139    /// conditions:
140    ///
141    /// * two deactivated stakes
142    /// * an inactive stake into an activating stake during its activation epoch
143    ///
144    /// For the following cases, the voter pubkey and vote credits observed must match:
145    ///
146    /// * two activated stakes
147    /// * two activating accounts that share an activation epoch, during the activation epoch
148    ///
149    /// All other combinations of stake states will fail to merge, including all
150    /// "transient" states, where a stake is activating or deactivating with a
151    /// non-zero effective stake.
152    ///
153    /// # Account references
154    ///   0. `[WRITE]` Destination stake account for the merge
155    ///   1. `[WRITE]` Source stake account for to merge.  This account will be drained
156    ///   2. `[]` Clock sysvar
157    ///   3. `[]` Stake history sysvar that carries stake warmup/cooldown history
158    ///   4. `[SIGNER]` Stake authority
159    Merge,
160
161    /// Authorize a key to manage stake or withdrawal with a derived key
162    ///
163    /// # Account references
164    ///   0. `[WRITE]` Stake account to be updated
165    ///   1. `[SIGNER]` Base key of stake or withdraw authority
166    ///   2. `[]` Clock sysvar
167    ///   3. Optional: `[SIGNER]` Lockup authority, if updating StakeAuthorize::Withdrawer before
168    ///      lockup expiration
169    AuthorizeWithSeed(AuthorizeWithSeedArgs),
170
171    /// Initialize a stake with authorization information
172    ///
173    /// This instruction is similar to `Initialize` except that the withdraw authority
174    /// must be a signer, and no lockup is applied to the account.
175    ///
176    /// # Account references
177    ///   0. `[WRITE]` Uninitialized stake account
178    ///   1. `[]` Rent sysvar
179    ///   2. `[]` The stake authority
180    ///   3. `[SIGNER]` The withdraw authority
181    ///
182    InitializeChecked,
183
184    /// Authorize a key to manage stake or withdrawal
185    ///
186    /// This instruction behaves like `Authorize` with the additional requirement that the new
187    /// stake or withdraw authority must also be a signer.
188    ///
189    /// # Account references
190    ///   0. `[WRITE]` Stake account to be updated
191    ///   1. `[]` Clock sysvar
192    ///   2. `[SIGNER]` The stake or withdraw authority
193    ///   3. `[SIGNER]` The new stake or withdraw authority
194    ///   4. Optional: `[SIGNER]` Lockup authority, if updating StakeAuthorize::Withdrawer before
195    ///      lockup expiration
196    AuthorizeChecked(StakeAuthorize),
197
198    /// Authorize a key to manage stake or withdrawal with a derived key
199    ///
200    /// This instruction behaves like `AuthorizeWithSeed` with the additional requirement that
201    /// the new stake or withdraw authority must also be a signer.
202    ///
203    /// # Account references
204    ///   0. `[WRITE]` Stake account to be updated
205    ///   1. `[SIGNER]` Base key of stake or withdraw authority
206    ///   2. `[]` Clock sysvar
207    ///   3. `[SIGNER]` The new stake or withdraw authority
208    ///   4. Optional: `[SIGNER]` Lockup authority, if updating StakeAuthorize::Withdrawer before
209    ///      lockup expiration
210    AuthorizeCheckedWithSeed(AuthorizeCheckedWithSeedArgs),
211
212    /// Set stake lockup
213    ///
214    /// This instruction behaves like `SetLockup` with the additional requirement that
215    /// the new lockup authority also be a signer.
216    ///
217    /// If a lockup is not active, the withdraw authority may set a new lockup
218    /// If a lockup is active, the lockup custodian may update the lockup parameters
219    ///
220    /// # Account references
221    ///   0. `[WRITE]` Initialized stake account
222    ///   1. `[SIGNER]` Lockup authority or withdraw authority
223    ///   2. Optional: `[SIGNER]` New lockup authority
224    SetLockupChecked(LockupCheckedArgs),
225}
226
227#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
228pub struct LockupArgs {
229    pub unix_timestamp: Option<UnixTimestamp>,
230    pub epoch: Option<Epoch>,
231    pub custodian: Option<Pubkey>,
232}
233
234#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
235pub struct LockupCheckedArgs {
236    pub unix_timestamp: Option<UnixTimestamp>,
237    pub epoch: Option<Epoch>,
238}
239
240#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
241pub struct AuthorizeWithSeedArgs {
242    pub new_authorized_pubkey: Pubkey,
243    pub stake_authorize: StakeAuthorize,
244    pub authority_seed: String,
245    pub authority_owner: Pubkey,
246}
247
248#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
249pub struct AuthorizeCheckedWithSeedArgs {
250    pub stake_authorize: StakeAuthorize,
251    pub authority_seed: String,
252    pub authority_owner: Pubkey,
253}
254
255pub fn initialize(stake_pubkey: &Pubkey, authorized: &Authorized, lockup: &Lockup) -> Instruction {
256    Instruction::new_with_bincode(
257        id(),
258        &StakeInstruction::Initialize(*authorized, *lockup),
259        vec![
260            AccountMeta::new(*stake_pubkey, false),
261            AccountMeta::new_readonly(sysvar::rent::id(), false),
262        ],
263    )
264}
265
266pub fn initialize_checked(stake_pubkey: &Pubkey, authorized: &Authorized) -> Instruction {
267    Instruction::new_with_bincode(
268        id(),
269        &StakeInstruction::InitializeChecked,
270        vec![
271            AccountMeta::new(*stake_pubkey, false),
272            AccountMeta::new_readonly(sysvar::rent::id(), false),
273            AccountMeta::new_readonly(authorized.staker, false),
274            AccountMeta::new_readonly(authorized.withdrawer, true),
275        ],
276    )
277}
278
279pub fn create_account_with_seed(
280    from_pubkey: &Pubkey,
281    stake_pubkey: &Pubkey,
282    base: &Pubkey,
283    seed: &str,
284    authorized: &Authorized,
285    lockup: &Lockup,
286    carats: u64,
287) -> Vec<Instruction> {
288    vec![
289        system_instruction::create_account_with_seed(
290            from_pubkey,
291            stake_pubkey,
292            base,
293            seed,
294            carats,
295            std::mem::size_of::<StakeState>() as u64,
296            &id(),
297        ),
298        initialize(stake_pubkey, authorized, lockup),
299    ]
300}
301
302pub fn create_account(
303    from_pubkey: &Pubkey,
304    stake_pubkey: &Pubkey,
305    authorized: &Authorized,
306    lockup: &Lockup,
307    carats: u64,
308) -> Vec<Instruction> {
309    vec![
310        system_instruction::create_account(
311            from_pubkey,
312            stake_pubkey,
313            carats,
314            std::mem::size_of::<StakeState>() as u64,
315            &id(),
316        ),
317        initialize(stake_pubkey, authorized, lockup),
318    ]
319}
320
321pub fn create_account_with_seed_checked(
322    from_pubkey: &Pubkey,
323    stake_pubkey: &Pubkey,
324    base: &Pubkey,
325    seed: &str,
326    authorized: &Authorized,
327    carats: u64,
328) -> Vec<Instruction> {
329    vec![
330        system_instruction::create_account_with_seed(
331            from_pubkey,
332            stake_pubkey,
333            base,
334            seed,
335            carats,
336            std::mem::size_of::<StakeState>() as u64,
337            &id(),
338        ),
339        initialize_checked(stake_pubkey, authorized),
340    ]
341}
342
343pub fn create_account_checked(
344    from_pubkey: &Pubkey,
345    stake_pubkey: &Pubkey,
346    authorized: &Authorized,
347    carats: u64,
348) -> Vec<Instruction> {
349    vec![
350        system_instruction::create_account(
351            from_pubkey,
352            stake_pubkey,
353            carats,
354            std::mem::size_of::<StakeState>() as u64,
355            &id(),
356        ),
357        initialize_checked(stake_pubkey, authorized),
358    ]
359}
360
361fn _split(
362    stake_pubkey: &Pubkey,
363    authorized_pubkey: &Pubkey,
364    carats: u64,
365    split_stake_pubkey: &Pubkey,
366) -> Instruction {
367    let account_metas = vec![
368        AccountMeta::new(*stake_pubkey, false),
369        AccountMeta::new(*split_stake_pubkey, false),
370        AccountMeta::new_readonly(*authorized_pubkey, true),
371    ];
372
373    Instruction::new_with_bincode(id(), &StakeInstruction::Split(carats), account_metas)
374}
375
376pub fn split(
377    stake_pubkey: &Pubkey,
378    authorized_pubkey: &Pubkey,
379    carats: u64,
380    split_stake_pubkey: &Pubkey,
381) -> Vec<Instruction> {
382    vec![
383        system_instruction::allocate(split_stake_pubkey, std::mem::size_of::<StakeState>() as u64),
384        system_instruction::assign(split_stake_pubkey, &id()),
385        _split(
386            stake_pubkey,
387            authorized_pubkey,
388            carats,
389            split_stake_pubkey,
390        ),
391    ]
392}
393
394pub fn split_with_seed(
395    stake_pubkey: &Pubkey,
396    authorized_pubkey: &Pubkey,
397    carats: u64,
398    split_stake_pubkey: &Pubkey, // derived using create_with_seed()
399    base: &Pubkey,               // base
400    seed: &str,                  // seed
401) -> Vec<Instruction> {
402    vec![
403        system_instruction::allocate_with_seed(
404            split_stake_pubkey,
405            base,
406            seed,
407            std::mem::size_of::<StakeState>() as u64,
408            &id(),
409        ),
410        _split(
411            stake_pubkey,
412            authorized_pubkey,
413            carats,
414            split_stake_pubkey,
415        ),
416    ]
417}
418
419pub fn merge(
420    destination_stake_pubkey: &Pubkey,
421    source_stake_pubkey: &Pubkey,
422    authorized_pubkey: &Pubkey,
423) -> Vec<Instruction> {
424    let account_metas = vec![
425        AccountMeta::new(*destination_stake_pubkey, false),
426        AccountMeta::new(*source_stake_pubkey, false),
427        AccountMeta::new_readonly(sysvar::clock::id(), false),
428        AccountMeta::new_readonly(sysvar::stake_history::id(), false),
429        AccountMeta::new_readonly(*authorized_pubkey, true),
430    ];
431
432    vec![Instruction::new_with_bincode(
433        id(),
434        &StakeInstruction::Merge,
435        account_metas,
436    )]
437}
438
439pub fn create_account_and_delegate_stake(
440    from_pubkey: &Pubkey,
441    stake_pubkey: &Pubkey,
442    vote_pubkey: &Pubkey,
443    authorized: &Authorized,
444    lockup: &Lockup,
445    carats: u64,
446) -> Vec<Instruction> {
447    let mut instructions = create_account(from_pubkey, stake_pubkey, authorized, lockup, carats);
448    instructions.push(delegate_stake(
449        stake_pubkey,
450        &authorized.staker,
451        vote_pubkey,
452    ));
453    instructions
454}
455
456pub fn create_account_with_seed_and_delegate_stake(
457    from_pubkey: &Pubkey,
458    stake_pubkey: &Pubkey,
459    base: &Pubkey,
460    seed: &str,
461    vote_pubkey: &Pubkey,
462    authorized: &Authorized,
463    lockup: &Lockup,
464    carats: u64,
465) -> Vec<Instruction> {
466    let mut instructions = create_account_with_seed(
467        from_pubkey,
468        stake_pubkey,
469        base,
470        seed,
471        authorized,
472        lockup,
473        carats,
474    );
475    instructions.push(delegate_stake(
476        stake_pubkey,
477        &authorized.staker,
478        vote_pubkey,
479    ));
480    instructions
481}
482
483pub fn authorize(
484    stake_pubkey: &Pubkey,
485    authorized_pubkey: &Pubkey,
486    new_authorized_pubkey: &Pubkey,
487    stake_authorize: StakeAuthorize,
488    custodian_pubkey: Option<&Pubkey>,
489) -> Instruction {
490    let mut account_metas = vec![
491        AccountMeta::new(*stake_pubkey, false),
492        AccountMeta::new_readonly(sysvar::clock::id(), false),
493        AccountMeta::new_readonly(*authorized_pubkey, true),
494    ];
495
496    if let Some(custodian_pubkey) = custodian_pubkey {
497        account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
498    }
499
500    Instruction::new_with_bincode(
501        id(),
502        &StakeInstruction::Authorize(*new_authorized_pubkey, stake_authorize),
503        account_metas,
504    )
505}
506
507pub fn authorize_checked(
508    stake_pubkey: &Pubkey,
509    authorized_pubkey: &Pubkey,
510    new_authorized_pubkey: &Pubkey,
511    stake_authorize: StakeAuthorize,
512    custodian_pubkey: Option<&Pubkey>,
513) -> Instruction {
514    let mut account_metas = vec![
515        AccountMeta::new(*stake_pubkey, false),
516        AccountMeta::new_readonly(sysvar::clock::id(), false),
517        AccountMeta::new_readonly(*authorized_pubkey, true),
518        AccountMeta::new_readonly(*new_authorized_pubkey, true),
519    ];
520
521    if let Some(custodian_pubkey) = custodian_pubkey {
522        account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
523    }
524
525    Instruction::new_with_bincode(
526        id(),
527        &StakeInstruction::AuthorizeChecked(stake_authorize),
528        account_metas,
529    )
530}
531
532pub fn authorize_with_seed(
533    stake_pubkey: &Pubkey,
534    authority_base: &Pubkey,
535    authority_seed: String,
536    authority_owner: &Pubkey,
537    new_authorized_pubkey: &Pubkey,
538    stake_authorize: StakeAuthorize,
539    custodian_pubkey: Option<&Pubkey>,
540) -> Instruction {
541    let mut account_metas = vec![
542        AccountMeta::new(*stake_pubkey, false),
543        AccountMeta::new_readonly(*authority_base, true),
544        AccountMeta::new_readonly(sysvar::clock::id(), false),
545    ];
546
547    if let Some(custodian_pubkey) = custodian_pubkey {
548        account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
549    }
550
551    let args = AuthorizeWithSeedArgs {
552        new_authorized_pubkey: *new_authorized_pubkey,
553        stake_authorize,
554        authority_seed,
555        authority_owner: *authority_owner,
556    };
557
558    Instruction::new_with_bincode(
559        id(),
560        &StakeInstruction::AuthorizeWithSeed(args),
561        account_metas,
562    )
563}
564
565pub fn authorize_checked_with_seed(
566    stake_pubkey: &Pubkey,
567    authority_base: &Pubkey,
568    authority_seed: String,
569    authority_owner: &Pubkey,
570    new_authorized_pubkey: &Pubkey,
571    stake_authorize: StakeAuthorize,
572    custodian_pubkey: Option<&Pubkey>,
573) -> Instruction {
574    let mut account_metas = vec![
575        AccountMeta::new(*stake_pubkey, false),
576        AccountMeta::new_readonly(*authority_base, true),
577        AccountMeta::new_readonly(sysvar::clock::id(), false),
578        AccountMeta::new_readonly(*new_authorized_pubkey, true),
579    ];
580
581    if let Some(custodian_pubkey) = custodian_pubkey {
582        account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
583    }
584
585    let args = AuthorizeCheckedWithSeedArgs {
586        stake_authorize,
587        authority_seed,
588        authority_owner: *authority_owner,
589    };
590
591    Instruction::new_with_bincode(
592        id(),
593        &StakeInstruction::AuthorizeCheckedWithSeed(args),
594        account_metas,
595    )
596}
597
598pub fn delegate_stake(
599    stake_pubkey: &Pubkey,
600    authorized_pubkey: &Pubkey,
601    vote_pubkey: &Pubkey,
602) -> Instruction {
603    let account_metas = vec![
604        AccountMeta::new(*stake_pubkey, false),
605        AccountMeta::new_readonly(*vote_pubkey, false),
606        AccountMeta::new_readonly(sysvar::clock::id(), false),
607        AccountMeta::new_readonly(sysvar::stake_history::id(), false),
608        AccountMeta::new_readonly(config::id(), false),
609        AccountMeta::new_readonly(*authorized_pubkey, true),
610    ];
611    Instruction::new_with_bincode(id(), &StakeInstruction::DelegateStake, account_metas)
612}
613
614pub fn withdraw(
615    stake_pubkey: &Pubkey,
616    withdrawer_pubkey: &Pubkey,
617    to_pubkey: &Pubkey,
618    carats: u64,
619    custodian_pubkey: Option<&Pubkey>,
620) -> Instruction {
621    let mut account_metas = vec![
622        AccountMeta::new(*stake_pubkey, false),
623        AccountMeta::new(*to_pubkey, false),
624        AccountMeta::new_readonly(sysvar::clock::id(), false),
625        AccountMeta::new_readonly(sysvar::stake_history::id(), false),
626        AccountMeta::new_readonly(*withdrawer_pubkey, true),
627    ];
628
629    if let Some(custodian_pubkey) = custodian_pubkey {
630        account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
631    }
632
633    Instruction::new_with_bincode(id(), &StakeInstruction::Withdraw(carats), account_metas)
634}
635
636pub fn deactivate_stake(stake_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction {
637    let account_metas = vec![
638        AccountMeta::new(*stake_pubkey, false),
639        AccountMeta::new_readonly(sysvar::clock::id(), false),
640        AccountMeta::new_readonly(*authorized_pubkey, true),
641    ];
642    Instruction::new_with_bincode(id(), &StakeInstruction::Deactivate, account_metas)
643}
644
645pub fn set_lockup(
646    stake_pubkey: &Pubkey,
647    lockup: &LockupArgs,
648    custodian_pubkey: &Pubkey,
649) -> Instruction {
650    let account_metas = vec![
651        AccountMeta::new(*stake_pubkey, false),
652        AccountMeta::new_readonly(*custodian_pubkey, true),
653    ];
654    Instruction::new_with_bincode(id(), &StakeInstruction::SetLockup(*lockup), account_metas)
655}
656
657pub fn set_lockup_checked(
658    stake_pubkey: &Pubkey,
659    lockup: &LockupArgs,
660    custodian_pubkey: &Pubkey,
661) -> Instruction {
662    let mut account_metas = vec![
663        AccountMeta::new(*stake_pubkey, false),
664        AccountMeta::new_readonly(*custodian_pubkey, true),
665    ];
666
667    let lockup_checked = LockupCheckedArgs {
668        unix_timestamp: lockup.unix_timestamp,
669        epoch: lockup.epoch,
670    };
671    if let Some(new_custodian) = lockup.custodian {
672        account_metas.push(AccountMeta::new_readonly(new_custodian, true));
673    }
674    Instruction::new_with_bincode(
675        id(),
676        &StakeInstruction::SetLockupChecked(lockup_checked),
677        account_metas,
678    )
679}
680
681#[cfg(test)]
682mod tests {
683    use super::*;
684    use crate::instruction::InstructionError;
685
686    #[test]
687    fn test_custom_error_decode() {
688        use num_traits::FromPrimitive;
689        fn pretty_err<T>(err: InstructionError) -> String
690        where
691            T: 'static + std::error::Error + DecodeError<T> + FromPrimitive,
692        {
693            if let InstructionError::Custom(code) = err {
694                let specific_error: T = T::decode_custom_error_to_enum(code).unwrap();
695                format!(
696                    "{:?}: {}::{:?} - {}",
697                    err,
698                    T::type_of(),
699                    specific_error,
700                    specific_error,
701                )
702            } else {
703                "".to_string()
704            }
705        }
706        assert_eq!(
707            "Custom(0): StakeError::NoCreditsToRedeem - not enough credits to redeem",
708            pretty_err::<StakeError>(StakeError::NoCreditsToRedeem.into())
709        )
710    }
711}