miraland_program/stake/
instruction.rs

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