Skip to main content

solana_stake_interface/
instruction.rs

1// Remove the following `allow` when the `Redelegate` variant is renamed to
2// `Unused` starting from v3.
3// Required to avoid warnings from uses of deprecated types during trait derivations.
4#![allow(deprecated)]
5
6#[cfg(feature = "codama")]
7use codama_macros::{CodamaInstructions, CodamaType};
8use {
9    crate::state::{Authorized, Lockup, StakeAuthorize},
10    solana_clock::{Epoch, UnixTimestamp},
11    solana_pubkey::Pubkey,
12};
13#[cfg(feature = "bincode")]
14use {
15    crate::{config, program::ID, state::StakeStateV2},
16    solana_instruction::{AccountMeta, Instruction},
17};
18
19// Inline some constants to avoid dependencies.
20//
21// Note: replace these inline IDs with the corresponding value from
22// `solana_sdk_ids` once the version is updated to 2.2.0.
23
24#[cfg(feature = "bincode")]
25const CLOCK_ID: Pubkey = Pubkey::from_str_const("SysvarC1ock11111111111111111111111111111111");
26
27#[cfg(feature = "bincode")]
28const RENT_ID: Pubkey = Pubkey::from_str_const("SysvarRent111111111111111111111111111111111");
29
30#[cfg(feature = "bincode")]
31const STAKE_HISTORY_ID: Pubkey =
32    Pubkey::from_str_const("SysvarStakeHistory1111111111111111111111111");
33
34// NOTE the stake program is in the process of removing dependence on all sysvars
35// once this version of the program is live on all clusters, we can remove them here
36// namely, from all doc comments in `StakeInstruction` and in all instruction builders
37// we may also remove all use of and reference to the stake config account
38#[cfg_attr(
39    feature = "serde",
40    derive(serde_derive::Deserialize, serde_derive::Serialize)
41)]
42#[cfg_attr(feature = "codama", derive(CodamaInstructions))]
43#[derive(Debug, PartialEq, Eq, Clone)]
44pub enum StakeInstruction {
45    /// Initialize a stake with lockup and authorization information
46    ///
47    /// # Account references
48    ///   0. `[WRITE]` Uninitialized stake account
49    ///   1. `[]` Rent sysvar
50    ///
51    /// [`Authorized`] carries pubkeys that must sign staker transactions
52    /// and withdrawer transactions; [`Lockup`] carries information about
53    /// withdrawal restrictions.
54    #[cfg_attr(
55        feature = "codama",
56        codama(account(name = "stake", writable, docs = "Uninitialized stake account")),
57        codama(account(name = "rent_sysvar", docs = "Rent sysvar", default_value = sysvar("rent")))
58    )]
59    Initialize(Authorized, Lockup),
60
61    /// Authorize a key to manage stake or withdrawal
62    ///
63    /// # Account references
64    ///   0. `[WRITE]` Stake account to be updated
65    ///   1. `[]` Clock sysvar
66    ///   2. `[SIGNER]` The stake or withdraw authority
67    ///   3. Optional: `[SIGNER]` Lockup authority, if updating `StakeAuthorize::Withdrawer` before
68    ///      lockup expiration
69    #[cfg_attr(
70        feature = "codama",
71        codama(account(name = "stake", writable, docs = "Stake account to be updated")),
72        codama(account(name = "clock_sysvar", docs = "Clock sysvar", default_value = sysvar("clock"))),
73        codama(account(name = "authority", signer, docs = "The stake or withdraw authority")),
74        codama(account(
75            name = "lockup_authority",
76            optional,
77            signer,
78            docs = "Lockup authority, if updating `StakeAuthorize::Withdrawer` before lockup expiration"
79        ))
80    )]
81    Authorize(Pubkey, StakeAuthorize),
82
83    /// Delegate a stake to a particular vote account
84    ///
85    /// # Account references
86    ///   0. `[WRITE]` Initialized stake account to be delegated
87    ///   1. `[]` Vote account to which this stake will be delegated
88    ///   2. `[]` Clock sysvar
89    ///   3. `[]` Stake history sysvar that carries stake warmup/cooldown history
90    ///   4. `[]` Unused account, formerly the stake config
91    ///   5. `[SIGNER]` Stake authority
92    ///
93    /// The entire balance of the staking account is staked. `DelegateStake`
94    /// can be called multiple times, but re-delegation is delayed by one epoch.
95    #[cfg_attr(
96        feature = "codama",
97        codama(account(
98            name = "stake",
99            writable,
100            docs = "Initialized stake account to be delegated"
101        )),
102        codama(account(
103            name = "vote",
104            docs = "Vote account to which this stake will be delegated"
105        )),
106        codama(account(name = "clock_sysvar", docs = "Clock sysvar", default_value = sysvar("clock"))),
107        codama(account(
108            name = "stake_history",
109            docs = "Stake history sysvar that carries stake warmup/cooldown history",
110            default_value = sysvar("stake_history")
111        )),
112        codama(account(name = "unused", docs = "Unused account, formerly the stake config")),
113        codama(account(name = "stake_authority", signer, docs = "Stake authority"))
114    )]
115    DelegateStake,
116
117    /// Split `u64` tokens and stake off a stake account into another stake account.
118    ///
119    /// # Account references
120    ///   0. `[WRITE]` Stake account to be split; must be in the Initialized or Stake state
121    ///   1. `[WRITE]` Uninitialized stake account that will take the split-off amount
122    ///   2. `[SIGNER]` Stake authority
123    #[cfg_attr(
124        feature = "codama",
125        codama(account(
126            name = "stake",
127            writable,
128            docs = "Stake account to be split; must be in the Initialized or Stake state"
129        )),
130        codama(account(
131            name = "split_stake",
132            writable,
133            docs = "Uninitialized stake account that will take the split-off amount"
134        )),
135        codama(account(name = "stake_authority", signer, docs = "Stake authority"))
136    )]
137    // `args` name is required for backwards compatibility with the old Anchor-generated
138    // IDL. Changing this name could break existing clients.
139    Split(#[cfg_attr(feature = "codama", codama(name = "args"))] u64),
140
141    /// Withdraw unstaked lamports from the stake account
142    ///
143    /// # Account references
144    ///   0. `[WRITE]` Stake account from which to withdraw
145    ///   1. `[WRITE]` Recipient account
146    ///   2. `[]` Clock sysvar
147    ///   3. `[]` Stake history sysvar that carries stake warmup/cooldown history
148    ///   4. `[SIGNER]` Withdraw authority
149    ///   5. Optional: `[SIGNER]` Lockup authority, if before lockup expiration
150    ///
151    /// The `u64` is the portion of the stake account balance to be withdrawn,
152    /// must be `<= StakeAccount.lamports - staked_lamports`.
153    #[cfg_attr(
154        feature = "codama",
155        codama(account(
156            name = "stake",
157            writable,
158            docs = "Stake account from which to withdraw"
159        )),
160        codama(account(name = "recipient", writable, docs = "Recipient account")),
161        codama(account(name = "clock_sysvar", docs = "Clock sysvar", default_value = sysvar("clock"))),
162        codama(account(
163            name = "stake_history",
164            docs = "Stake history sysvar that carries stake warmup/cooldown history",
165            default_value = sysvar("stake_history")
166        )),
167        codama(account(name = "withdraw_authority", signer, docs = "Withdraw authority")),
168        codama(account(
169            name = "lockup_authority",
170            optional,
171            signer,
172            docs = "Lockup authority, if before lockup expiration"
173        ))
174    )]
175    // `args` name is required for backwards compatibility with the old Anchor-generated
176    // IDL. Changing this name could break existing clients.
177    Withdraw(#[cfg_attr(feature = "codama", codama(name = "args"))] u64),
178
179    /// Deactivates the stake in the account
180    ///
181    /// # Account references
182    ///   0. `[WRITE]` Delegated stake account
183    ///   1. `[]` Clock sysvar
184    ///   2. `[SIGNER]` Stake authority
185    #[cfg_attr(
186        feature = "codama",
187        codama(account(
188            name = "stake",
189            writable,
190            docs = "Delegated stake account to be deactivated"
191        )),
192        codama(account(name = "clock_sysvar", docs = "Clock sysvar", default_value = sysvar("clock"))),
193        codama(account(name = "stake_authority", signer, docs = "Stake authority"))
194    )]
195    Deactivate,
196
197    /// Set stake lockup
198    ///
199    /// If a lockup is not active, the withdraw authority may set a new lockup
200    /// If a lockup is active, the lockup custodian may update the lockup parameters
201    ///
202    /// # Account references
203    ///   0. `[WRITE]` Initialized stake account
204    ///   1. `[SIGNER]` Lockup authority or withdraw authority
205    #[cfg_attr(
206        feature = "codama",
207        codama(account(name = "stake", writable, docs = "Initialized stake account")),
208        codama(account(
209            name = "authority",
210            signer,
211            docs = "Lockup authority or withdraw authority"
212        ))
213    )]
214    SetLockup(LockupArgs),
215
216    /// Merge two stake accounts.
217    ///
218    /// Both accounts must have identical lockup and authority keys. A merge
219    /// is possible between two stakes in the following states with no additional
220    /// conditions:
221    ///
222    /// * two deactivated stakes
223    /// * an inactive stake into an activating stake during its activation epoch
224    ///
225    /// For the following cases, the voter pubkey and vote credits observed must match:
226    ///
227    /// * two activated stakes
228    /// * two activating accounts that share an activation epoch, during the activation epoch
229    ///
230    /// All other combinations of stake states will fail to merge, including all
231    /// "transient" states, where a stake is activating or deactivating with a
232    /// non-zero effective stake.
233    ///
234    /// # Account references
235    ///   0. `[WRITE]` Destination stake account for the merge
236    ///   1. `[WRITE]` Source stake account for to merge.  This account will be drained
237    ///   2. `[]` Clock sysvar
238    ///   3. `[]` Stake history sysvar that carries stake warmup/cooldown history
239    ///   4. `[SIGNER]` Stake authority
240    #[cfg_attr(
241        feature = "codama",
242        codama(account(
243            name = "destination_stake",
244            writable,
245            docs = "Destination stake account for the merge"
246        )),
247        codama(account(
248            name = "source_stake",
249            writable,
250            docs = "Source stake account for to merge.  This account will be drained"
251        )),
252        codama(account(name = "clock_sysvar", docs = "Clock sysvar", default_value = sysvar("clock"))),
253        codama(account(
254            name = "stake_history",
255            docs = "Stake history sysvar that carries stake warmup/cooldown history",
256            default_value = sysvar("stake_history")
257        )),
258        codama(account(name = "stake_authority", signer, docs = "Stake authority"))
259    )]
260    Merge,
261
262    /// Authorize a key to manage stake or withdrawal with a derived key
263    ///
264    /// # Account references
265    ///   0. `[WRITE]` Stake account to be updated
266    ///   1. `[SIGNER]` Base key of stake or withdraw authority
267    ///   2. `[]` Clock sysvar
268    ///   3. Optional: `[SIGNER]` Lockup authority, if updating [`StakeAuthorize::Withdrawer`]
269    ///      before lockup expiration
270    #[cfg_attr(
271        feature = "codama",
272        codama(account(name = "stake", writable, docs = "Stake account to be updated")),
273        codama(account(
274            name = "base",
275            signer,
276            docs = "Base key of stake or withdraw authority"
277        )),
278        codama(account(name = "clock_sysvar", docs = "Clock sysvar", default_value = sysvar("clock"))),
279        codama(account(
280            name = "lockup_authority",
281            optional,
282            signer,
283            docs = "Lockup authority, if updating `StakeAuthorize::Withdrawer` before lockup expiration"
284        ))
285    )]
286    AuthorizeWithSeed(AuthorizeWithSeedArgs),
287
288    /// Initialize a stake with authorization information
289    ///
290    /// This instruction is similar to `Initialize` except that the withdraw authority
291    /// must be a signer, and no lockup is applied to the account.
292    ///
293    /// # Account references
294    ///   0. `[WRITE]` Uninitialized stake account
295    ///   1. `[]` Rent sysvar
296    ///   2. `[]` The stake authority
297    ///   3. `[SIGNER]` The withdraw authority
298    #[cfg_attr(
299        feature = "codama",
300        codama(account(name = "stake", writable, docs = "Uninitialized stake account")),
301        codama(account(name = "rent_sysvar", docs = "Rent sysvar", default_value = sysvar("rent"))),
302        codama(account(name = "stake_authority", docs = "The stake authority")),
303        codama(account(name = "withdraw_authority", signer, docs = "The withdraw authority"))
304    )]
305    InitializeChecked,
306
307    /// Authorize a key to manage stake or withdrawal
308    ///
309    /// This instruction behaves like `Authorize` with the additional requirement that the new
310    /// stake or withdraw authority must also be a signer.
311    ///
312    /// # Account references
313    ///   0. `[WRITE]` Stake account to be updated
314    ///   1. `[]` Clock sysvar
315    ///   2. `[SIGNER]` The stake or withdraw authority
316    ///   3. `[SIGNER]` The new stake or withdraw authority
317    ///   4. Optional: `[SIGNER]` Lockup authority, if updating [`StakeAuthorize::Withdrawer`]
318    ///      before lockup expiration
319    #[cfg_attr(
320        feature = "codama",
321        codama(account(name = "stake", writable, docs = "Stake account to be updated")),
322        codama(account(name = "clock_sysvar", docs = "Clock sysvar", default_value = sysvar("clock"))),
323        codama(account(name = "authority", signer, docs = "The stake or withdraw authority")),
324        codama(account(
325            name = "new_authority",
326            signer,
327            docs = "The new stake or withdraw authority"
328        )),
329        codama(account(
330            name = "lockup_authority",
331            optional,
332            signer,
333            docs = "Lockup authority, if updating `StakeAuthorize::Withdrawer` before lockup expiration"
334        ))
335    )]
336    AuthorizeChecked(
337        #[cfg_attr(feature = "codama", codama(name = "stakeAuthorize"))] StakeAuthorize,
338    ),
339
340    /// Authorize a key to manage stake or withdrawal with a derived key
341    ///
342    /// This instruction behaves like `AuthorizeWithSeed` with the additional requirement that
343    /// the new stake or withdraw authority must also be a signer.
344    ///
345    /// # Account references
346    ///   0. `[WRITE]` Stake account to be updated
347    ///   1. `[SIGNER]` Base key of stake or withdraw authority
348    ///   2. `[]` Clock sysvar
349    ///   3. `[SIGNER]` The new stake or withdraw authority
350    ///   4. Optional: `[SIGNER]` Lockup authority, if updating [`StakeAuthorize::Withdrawer`]
351    ///      before lockup expiration
352    #[cfg_attr(
353        feature = "codama",
354        codama(account(name = "stake", writable, docs = "Stake account to be updated")),
355        codama(account(
356            name = "base",
357            signer,
358            docs = "Base key of stake or withdraw authority"
359        )),
360        codama(account(name = "clock_sysvar", docs = "Clock sysvar", default_value = sysvar("clock"))),
361        codama(account(
362            name = "new_authority",
363            signer,
364            docs = "The new stake or withdraw authority"
365        )),
366        codama(account(
367            name = "lockup_authority",
368            optional,
369            signer,
370            docs = "Lockup authority, if updating `StakeAuthorize::Withdrawer` before lockup expiration"
371        ))
372    )]
373    AuthorizeCheckedWithSeed(AuthorizeCheckedWithSeedArgs),
374
375    /// Set stake lockup
376    ///
377    /// This instruction behaves like `SetLockup` with the additional requirement that
378    /// the new lockup authority also be a signer.
379    ///
380    /// If a lockup is not active, the withdraw authority may set a new lockup
381    /// If a lockup is active, the lockup custodian may update the lockup parameters
382    ///
383    /// # Account references
384    ///   0. `[WRITE]` Initialized stake account
385    ///   1. `[SIGNER]` Lockup authority or withdraw authority
386    ///   2. Optional: `[SIGNER]` New lockup authority
387    #[cfg_attr(
388        feature = "codama",
389        codama(account(name = "stake", writable, docs = "Initialized stake account")),
390        codama(account(
391            name = "authority",
392            signer,
393            docs = "Lockup authority or withdraw authority"
394        )),
395        codama(account(
396            name = "new_authority",
397            optional,
398            signer,
399            docs = "New lockup authority"
400        ))
401    )]
402    SetLockupChecked(LockupCheckedArgs),
403
404    /// Get the minimum stake delegation, in lamports
405    ///
406    /// # Account references
407    ///   None
408    ///
409    /// Returns the minimum delegation as a little-endian encoded `u64` value.
410    /// Programs can use the [`get_minimum_delegation()`] helper function to invoke and
411    /// retrieve the return value for this instruction.
412    ///
413    /// [`get_minimum_delegation()`]: crate::tools::get_minimum_delegation
414    GetMinimumDelegation,
415
416    /// Deactivate stake delegated to a vote account that has been delinquent for at least
417    /// `MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION` epochs.
418    ///
419    /// No signer is required for this instruction as it is a common good to deactivate abandoned
420    /// stake.
421    ///
422    /// # Account references
423    ///   0. `[WRITE]` Delegated stake account
424    ///   1. `[]` Delinquent vote account for the delegated stake account
425    ///   2. `[]` Reference vote account that has voted at least once in the last
426    ///      `MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION` epochs
427    #[cfg_attr(
428        feature = "codama",
429        codama(account(name = "stake", writable, docs = "Delegated stake account")),
430        codama(account(
431            name = "delinquent_vote",
432            docs = "Delinquent vote account for the delegated stake account"
433        )),
434        codama(account(
435            name = "reference_vote",
436            docs = "Reference vote account that has voted at least once in the last `MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION` epochs"
437        ))
438    )]
439    DeactivateDelinquent,
440
441    /// Redelegate activated stake to another vote account.
442    ///
443    /// Upon success:
444    ///   * the balance of the delegated stake account will be reduced to the undelegated amount in
445    ///     the account (rent exempt minimum and any additional lamports not part of the delegation),
446    ///     and scheduled for deactivation.
447    ///   * the provided uninitialized stake account will receive the original balance of the
448    ///     delegated stake account, minus the rent exempt minimum, and scheduled for activation to
449    ///     the provided vote account. Any existing lamports in the uninitialized stake account
450    ///     will also be included in the re-delegation.
451    ///
452    /// # Account references
453    ///   0. `[WRITE]` Delegated stake account to be redelegated. The account must be fully
454    ///      activated and carry a balance greater than or equal to the minimum delegation amount
455    ///      plus rent exempt minimum
456    ///   1. `[WRITE]` Uninitialized stake account that will hold the redelegated stake
457    ///   2. `[]` Vote account to which this stake will be re-delegated
458    ///   3. `[]` Unused account, formerly the stake config
459    ///   4. `[SIGNER]` Stake authority
460    ///
461    #[deprecated(since = "2.1.0", note = "Redelegate will not be enabled")]
462    // NOTE: No codama attributes - this instruction is disabled and excluded from IDL
463    Redelegate,
464
465    /// Move stake between accounts with the same authorities and lockups, using Staker authority.
466    ///
467    /// The source account must be fully active. If its entire delegation is moved, it immediately
468    /// becomes inactive. Otherwise, at least the minimum delegation of active stake must remain.
469    ///
470    /// The destination account must be fully active or fully inactive. If it is active, it must
471    /// be delegated to the same vote account as the source. If it is inactive, it
472    /// immediately becomes active, and must contain at least the minimum delegation. The
473    /// destination must be pre-funded with the rent-exempt reserve.
474    ///
475    /// This instruction only affects or moves active stake. Additional unstaked lamports are never
476    /// moved, activated, or deactivated, and accounts are never deallocated.
477    ///
478    /// # Account references
479    ///   0. `[WRITE]` Active source stake account
480    ///   1. `[WRITE]` Active or inactive destination stake account
481    ///   2. `[SIGNER]` Stake authority
482    ///
483    /// The `u64` is the portion of the stake to move, which may be the entire delegation
484    #[cfg_attr(
485        feature = "codama",
486        codama(account(name = "sourceStake", writable, docs = "Active source stake account")),
487        codama(account(
488            name = "destinationStake",
489            writable,
490            docs = "Active or inactive destination stake account"
491        )),
492        codama(account(name = "stake_authority", signer, docs = "Stake authority"))
493    )]
494    // sadly named `args` to avoid breaking users of old IDL
495    MoveStake(#[cfg_attr(feature = "codama", codama(name = "args"))] u64),
496
497    /// Move unstaked lamports between accounts with the same authorities and lockups, using Staker
498    /// authority.
499    ///
500    /// The source account must be fully active or fully inactive. The destination may be in any
501    /// mergeable state (active, inactive, or activating, but not in warmup cooldown). Only lamports that
502    /// are neither backing a delegation nor required for rent-exemption may be moved.
503    ///
504    /// # Account references
505    ///   0. `[WRITE]` Active or inactive source stake account
506    ///   1. `[WRITE]` Mergeable destination stake account
507    ///   2. `[SIGNER]` Stake authority
508    ///
509    /// The `u64` is the portion of available lamports to move
510    #[cfg_attr(
511        feature = "codama",
512        codama(account(
513            name = "source_stake",
514            writable,
515            docs = "Active or inactive source stake account"
516        )),
517        codama(account(
518            name = "destination_stake",
519            writable,
520            docs = "Mergeable destination stake account"
521        )),
522        codama(account(name = "stake_authority", signer, docs = "Stake authority"))
523    )]
524    // sadly named `args` to avoid breaking users of old IDL
525    MoveLamports(#[cfg_attr(feature = "codama", codama(name = "args"))] u64),
526}
527
528#[cfg_attr(feature = "codama", derive(CodamaType), codama(name = "lockupParams"))]
529#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
530#[cfg_attr(
531    feature = "serde",
532    derive(serde_derive::Deserialize, serde_derive::Serialize)
533)]
534pub struct LockupArgs {
535    pub unix_timestamp: Option<UnixTimestamp>,
536    pub epoch: Option<Epoch>,
537    pub custodian: Option<Pubkey>,
538}
539
540#[cfg_attr(
541    feature = "codama",
542    derive(CodamaType),
543    codama(name = "lockupCheckedParams")
544)]
545#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
546#[cfg_attr(
547    feature = "serde",
548    derive(serde_derive::Deserialize, serde_derive::Serialize)
549)]
550pub struct LockupCheckedArgs {
551    pub unix_timestamp: Option<UnixTimestamp>,
552    pub epoch: Option<Epoch>,
553}
554
555#[cfg_attr(
556    feature = "codama",
557    derive(CodamaType),
558    codama(name = "authorizeWithSeedParams")
559)]
560#[derive(Debug, PartialEq, Eq, Clone)]
561#[cfg_attr(
562    feature = "serde",
563    derive(serde_derive::Deserialize, serde_derive::Serialize)
564)]
565pub struct AuthorizeWithSeedArgs {
566    pub new_authorized_pubkey: Pubkey,
567    pub stake_authorize: StakeAuthorize,
568    pub authority_seed: String,
569    pub authority_owner: Pubkey,
570}
571
572#[cfg_attr(
573    feature = "codama",
574    derive(CodamaType),
575    codama(name = "authorizeCheckedWithSeedParams")
576)]
577#[derive(Debug, PartialEq, Eq, Clone)]
578#[cfg_attr(
579    feature = "serde",
580    derive(serde_derive::Deserialize, serde_derive::Serialize)
581)]
582pub struct AuthorizeCheckedWithSeedArgs {
583    pub stake_authorize: StakeAuthorize,
584    pub authority_seed: String,
585    pub authority_owner: Pubkey,
586}
587
588#[cfg(feature = "bincode")]
589pub fn initialize(stake_pubkey: &Pubkey, authorized: &Authorized, lockup: &Lockup) -> Instruction {
590    Instruction::new_with_bincode(
591        ID,
592        &StakeInstruction::Initialize(*authorized, *lockup),
593        vec![
594            AccountMeta::new(*stake_pubkey, false),
595            AccountMeta::new_readonly(RENT_ID, false),
596        ],
597    )
598}
599
600#[cfg(feature = "bincode")]
601pub fn initialize_checked(stake_pubkey: &Pubkey, authorized: &Authorized) -> Instruction {
602    Instruction::new_with_bincode(
603        ID,
604        &StakeInstruction::InitializeChecked,
605        vec![
606            AccountMeta::new(*stake_pubkey, false),
607            AccountMeta::new_readonly(RENT_ID, false),
608            AccountMeta::new_readonly(authorized.staker, false),
609            AccountMeta::new_readonly(authorized.withdrawer, true),
610        ],
611    )
612}
613
614#[cfg(feature = "bincode")]
615pub fn create_account_with_seed(
616    from_pubkey: &Pubkey,
617    stake_pubkey: &Pubkey,
618    base: &Pubkey,
619    seed: &str,
620    authorized: &Authorized,
621    lockup: &Lockup,
622    lamports: u64,
623) -> Vec<Instruction> {
624    vec![
625        solana_system_interface::instruction::create_account_with_seed(
626            from_pubkey,
627            stake_pubkey,
628            base,
629            seed,
630            lamports,
631            StakeStateV2::size_of() as u64,
632            &ID,
633        ),
634        initialize(stake_pubkey, authorized, lockup),
635    ]
636}
637
638#[cfg(feature = "bincode")]
639pub fn create_account(
640    from_pubkey: &Pubkey,
641    stake_pubkey: &Pubkey,
642    authorized: &Authorized,
643    lockup: &Lockup,
644    lamports: u64,
645) -> Vec<Instruction> {
646    vec![
647        solana_system_interface::instruction::create_account(
648            from_pubkey,
649            stake_pubkey,
650            lamports,
651            StakeStateV2::size_of() as u64,
652            &ID,
653        ),
654        initialize(stake_pubkey, authorized, lockup),
655    ]
656}
657
658#[cfg(feature = "bincode")]
659pub fn create_account_with_seed_checked(
660    from_pubkey: &Pubkey,
661    stake_pubkey: &Pubkey,
662    base: &Pubkey,
663    seed: &str,
664    authorized: &Authorized,
665    lamports: u64,
666) -> Vec<Instruction> {
667    vec![
668        solana_system_interface::instruction::create_account_with_seed(
669            from_pubkey,
670            stake_pubkey,
671            base,
672            seed,
673            lamports,
674            StakeStateV2::size_of() as u64,
675            &ID,
676        ),
677        initialize_checked(stake_pubkey, authorized),
678    ]
679}
680
681#[cfg(feature = "bincode")]
682pub fn create_account_checked(
683    from_pubkey: &Pubkey,
684    stake_pubkey: &Pubkey,
685    authorized: &Authorized,
686    lamports: u64,
687) -> Vec<Instruction> {
688    vec![
689        solana_system_interface::instruction::create_account(
690            from_pubkey,
691            stake_pubkey,
692            lamports,
693            StakeStateV2::size_of() as u64,
694            &ID,
695        ),
696        initialize_checked(stake_pubkey, authorized),
697    ]
698}
699
700#[cfg(feature = "bincode")]
701fn _split(
702    stake_pubkey: &Pubkey,
703    authorized_pubkey: &Pubkey,
704    lamports: u64,
705    split_stake_pubkey: &Pubkey,
706) -> Instruction {
707    let account_metas = vec![
708        AccountMeta::new(*stake_pubkey, false),
709        AccountMeta::new(*split_stake_pubkey, false),
710        AccountMeta::new_readonly(*authorized_pubkey, true),
711    ];
712
713    Instruction::new_with_bincode(ID, &StakeInstruction::Split(lamports), account_metas)
714}
715
716#[cfg(feature = "bincode")]
717pub fn split(
718    stake_pubkey: &Pubkey,
719    authorized_pubkey: &Pubkey,
720    lamports: u64,
721    split_stake_pubkey: &Pubkey,
722) -> Vec<Instruction> {
723    vec![
724        solana_system_interface::instruction::allocate(
725            split_stake_pubkey,
726            StakeStateV2::size_of() as u64,
727        ),
728        solana_system_interface::instruction::assign(split_stake_pubkey, &ID),
729        _split(
730            stake_pubkey,
731            authorized_pubkey,
732            lamports,
733            split_stake_pubkey,
734        ),
735    ]
736}
737
738#[cfg(feature = "bincode")]
739pub fn split_with_seed(
740    stake_pubkey: &Pubkey,
741    authorized_pubkey: &Pubkey,
742    lamports: u64,
743    split_stake_pubkey: &Pubkey, // derived using create_with_seed()
744    base: &Pubkey,               // base
745    seed: &str,                  // seed
746) -> Vec<Instruction> {
747    vec![
748        solana_system_interface::instruction::allocate_with_seed(
749            split_stake_pubkey,
750            base,
751            seed,
752            StakeStateV2::size_of() as u64,
753            &ID,
754        ),
755        _split(
756            stake_pubkey,
757            authorized_pubkey,
758            lamports,
759            split_stake_pubkey,
760        ),
761    ]
762}
763
764#[cfg(feature = "bincode")]
765pub fn merge(
766    destination_stake_pubkey: &Pubkey,
767    source_stake_pubkey: &Pubkey,
768    authorized_pubkey: &Pubkey,
769) -> Vec<Instruction> {
770    let account_metas = vec![
771        AccountMeta::new(*destination_stake_pubkey, false),
772        AccountMeta::new(*source_stake_pubkey, false),
773        AccountMeta::new_readonly(CLOCK_ID, false),
774        AccountMeta::new_readonly(STAKE_HISTORY_ID, false),
775        AccountMeta::new_readonly(*authorized_pubkey, true),
776    ];
777
778    vec![Instruction::new_with_bincode(
779        ID,
780        &StakeInstruction::Merge,
781        account_metas,
782    )]
783}
784
785#[cfg(feature = "bincode")]
786pub fn create_account_and_delegate_stake(
787    from_pubkey: &Pubkey,
788    stake_pubkey: &Pubkey,
789    vote_pubkey: &Pubkey,
790    authorized: &Authorized,
791    lockup: &Lockup,
792    lamports: u64,
793) -> Vec<Instruction> {
794    let mut instructions = create_account(from_pubkey, stake_pubkey, authorized, lockup, lamports);
795    instructions.push(delegate_stake(
796        stake_pubkey,
797        &authorized.staker,
798        vote_pubkey,
799    ));
800    instructions
801}
802
803#[cfg(feature = "bincode")]
804#[allow(clippy::too_many_arguments)]
805pub fn create_account_with_seed_and_delegate_stake(
806    from_pubkey: &Pubkey,
807    stake_pubkey: &Pubkey,
808    base: &Pubkey,
809    seed: &str,
810    vote_pubkey: &Pubkey,
811    authorized: &Authorized,
812    lockup: &Lockup,
813    lamports: u64,
814) -> Vec<Instruction> {
815    let mut instructions = create_account_with_seed(
816        from_pubkey,
817        stake_pubkey,
818        base,
819        seed,
820        authorized,
821        lockup,
822        lamports,
823    );
824    instructions.push(delegate_stake(
825        stake_pubkey,
826        &authorized.staker,
827        vote_pubkey,
828    ));
829    instructions
830}
831
832#[cfg(feature = "bincode")]
833pub fn authorize(
834    stake_pubkey: &Pubkey,
835    authorized_pubkey: &Pubkey,
836    new_authorized_pubkey: &Pubkey,
837    stake_authorize: StakeAuthorize,
838    custodian_pubkey: Option<&Pubkey>,
839) -> Instruction {
840    let mut account_metas = vec![
841        AccountMeta::new(*stake_pubkey, false),
842        AccountMeta::new_readonly(CLOCK_ID, false),
843        AccountMeta::new_readonly(*authorized_pubkey, true),
844    ];
845
846    if let Some(custodian_pubkey) = custodian_pubkey {
847        account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
848    }
849
850    Instruction::new_with_bincode(
851        ID,
852        &StakeInstruction::Authorize(*new_authorized_pubkey, stake_authorize),
853        account_metas,
854    )
855}
856
857#[cfg(feature = "bincode")]
858pub fn authorize_checked(
859    stake_pubkey: &Pubkey,
860    authorized_pubkey: &Pubkey,
861    new_authorized_pubkey: &Pubkey,
862    stake_authorize: StakeAuthorize,
863    custodian_pubkey: Option<&Pubkey>,
864) -> Instruction {
865    let mut account_metas = vec![
866        AccountMeta::new(*stake_pubkey, false),
867        AccountMeta::new_readonly(CLOCK_ID, false),
868        AccountMeta::new_readonly(*authorized_pubkey, true),
869        AccountMeta::new_readonly(*new_authorized_pubkey, true),
870    ];
871
872    if let Some(custodian_pubkey) = custodian_pubkey {
873        account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
874    }
875
876    Instruction::new_with_bincode(
877        ID,
878        &StakeInstruction::AuthorizeChecked(stake_authorize),
879        account_metas,
880    )
881}
882
883#[cfg(feature = "bincode")]
884pub fn authorize_with_seed(
885    stake_pubkey: &Pubkey,
886    authority_base: &Pubkey,
887    authority_seed: String,
888    authority_owner: &Pubkey,
889    new_authorized_pubkey: &Pubkey,
890    stake_authorize: StakeAuthorize,
891    custodian_pubkey: Option<&Pubkey>,
892) -> Instruction {
893    let mut account_metas = vec![
894        AccountMeta::new(*stake_pubkey, false),
895        AccountMeta::new_readonly(*authority_base, true),
896        AccountMeta::new_readonly(CLOCK_ID, false),
897    ];
898
899    if let Some(custodian_pubkey) = custodian_pubkey {
900        account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
901    }
902
903    let args = AuthorizeWithSeedArgs {
904        new_authorized_pubkey: *new_authorized_pubkey,
905        stake_authorize,
906        authority_seed,
907        authority_owner: *authority_owner,
908    };
909
910    Instruction::new_with_bincode(
911        ID,
912        &StakeInstruction::AuthorizeWithSeed(args),
913        account_metas,
914    )
915}
916
917#[cfg(feature = "bincode")]
918pub fn authorize_checked_with_seed(
919    stake_pubkey: &Pubkey,
920    authority_base: &Pubkey,
921    authority_seed: String,
922    authority_owner: &Pubkey,
923    new_authorized_pubkey: &Pubkey,
924    stake_authorize: StakeAuthorize,
925    custodian_pubkey: Option<&Pubkey>,
926) -> Instruction {
927    let mut account_metas = vec![
928        AccountMeta::new(*stake_pubkey, false),
929        AccountMeta::new_readonly(*authority_base, true),
930        AccountMeta::new_readonly(CLOCK_ID, false),
931        AccountMeta::new_readonly(*new_authorized_pubkey, true),
932    ];
933
934    if let Some(custodian_pubkey) = custodian_pubkey {
935        account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
936    }
937
938    let args = AuthorizeCheckedWithSeedArgs {
939        stake_authorize,
940        authority_seed,
941        authority_owner: *authority_owner,
942    };
943
944    Instruction::new_with_bincode(
945        ID,
946        &StakeInstruction::AuthorizeCheckedWithSeed(args),
947        account_metas,
948    )
949}
950
951#[cfg(feature = "bincode")]
952pub fn delegate_stake(
953    stake_pubkey: &Pubkey,
954    authorized_pubkey: &Pubkey,
955    vote_pubkey: &Pubkey,
956) -> Instruction {
957    let account_metas = vec![
958        AccountMeta::new(*stake_pubkey, false),
959        AccountMeta::new_readonly(*vote_pubkey, false),
960        AccountMeta::new_readonly(CLOCK_ID, false),
961        AccountMeta::new_readonly(STAKE_HISTORY_ID, false),
962        // For backwards compatibility we pass the stake config, although this account is unused
963        AccountMeta::new_readonly(config::ID, false),
964        AccountMeta::new_readonly(*authorized_pubkey, true),
965    ];
966    Instruction::new_with_bincode(ID, &StakeInstruction::DelegateStake, account_metas)
967}
968
969#[cfg(feature = "bincode")]
970pub fn withdraw(
971    stake_pubkey: &Pubkey,
972    withdrawer_pubkey: &Pubkey,
973    to_pubkey: &Pubkey,
974    lamports: u64,
975    custodian_pubkey: Option<&Pubkey>,
976) -> Instruction {
977    let mut account_metas = vec![
978        AccountMeta::new(*stake_pubkey, false),
979        AccountMeta::new(*to_pubkey, false),
980        AccountMeta::new_readonly(CLOCK_ID, false),
981        AccountMeta::new_readonly(STAKE_HISTORY_ID, false),
982        AccountMeta::new_readonly(*withdrawer_pubkey, true),
983    ];
984
985    if let Some(custodian_pubkey) = custodian_pubkey {
986        account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
987    }
988
989    Instruction::new_with_bincode(ID, &StakeInstruction::Withdraw(lamports), account_metas)
990}
991
992#[cfg(feature = "bincode")]
993pub fn deactivate_stake(stake_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction {
994    let account_metas = vec![
995        AccountMeta::new(*stake_pubkey, false),
996        AccountMeta::new_readonly(CLOCK_ID, false),
997        AccountMeta::new_readonly(*authorized_pubkey, true),
998    ];
999    Instruction::new_with_bincode(ID, &StakeInstruction::Deactivate, account_metas)
1000}
1001
1002#[cfg(feature = "bincode")]
1003pub fn set_lockup(
1004    stake_pubkey: &Pubkey,
1005    lockup: &LockupArgs,
1006    custodian_pubkey: &Pubkey,
1007) -> Instruction {
1008    let account_metas = vec![
1009        AccountMeta::new(*stake_pubkey, false),
1010        AccountMeta::new_readonly(*custodian_pubkey, true),
1011    ];
1012    Instruction::new_with_bincode(ID, &StakeInstruction::SetLockup(*lockup), account_metas)
1013}
1014
1015#[cfg(feature = "bincode")]
1016pub fn set_lockup_checked(
1017    stake_pubkey: &Pubkey,
1018    lockup: &LockupArgs,
1019    custodian_pubkey: &Pubkey,
1020) -> Instruction {
1021    let mut account_metas = vec![
1022        AccountMeta::new(*stake_pubkey, false),
1023        AccountMeta::new_readonly(*custodian_pubkey, true),
1024    ];
1025
1026    let lockup_checked = LockupCheckedArgs {
1027        unix_timestamp: lockup.unix_timestamp,
1028        epoch: lockup.epoch,
1029    };
1030    if let Some(new_custodian) = lockup.custodian {
1031        account_metas.push(AccountMeta::new_readonly(new_custodian, true));
1032    }
1033    Instruction::new_with_bincode(
1034        ID,
1035        &StakeInstruction::SetLockupChecked(lockup_checked),
1036        account_metas,
1037    )
1038}
1039
1040#[cfg(feature = "bincode")]
1041pub fn get_minimum_delegation() -> Instruction {
1042    Instruction::new_with_bincode(ID, &StakeInstruction::GetMinimumDelegation, Vec::default())
1043}
1044
1045#[cfg(feature = "bincode")]
1046pub fn deactivate_delinquent_stake(
1047    stake_account: &Pubkey,
1048    delinquent_vote_account: &Pubkey,
1049    reference_vote_account: &Pubkey,
1050) -> Instruction {
1051    let account_metas = vec![
1052        AccountMeta::new(*stake_account, false),
1053        AccountMeta::new_readonly(*delinquent_vote_account, false),
1054        AccountMeta::new_readonly(*reference_vote_account, false),
1055    ];
1056    Instruction::new_with_bincode(ID, &StakeInstruction::DeactivateDelinquent, account_metas)
1057}
1058
1059#[cfg(feature = "bincode")]
1060fn _redelegate(
1061    stake_pubkey: &Pubkey,
1062    authorized_pubkey: &Pubkey,
1063    vote_pubkey: &Pubkey,
1064    uninitialized_stake_pubkey: &Pubkey,
1065) -> Instruction {
1066    let account_metas = vec![
1067        AccountMeta::new(*stake_pubkey, false),
1068        AccountMeta::new(*uninitialized_stake_pubkey, false),
1069        AccountMeta::new_readonly(*vote_pubkey, false),
1070        // For backwards compatibility we pass the stake config, although this account is unused
1071        AccountMeta::new_readonly(config::ID, false),
1072        AccountMeta::new_readonly(*authorized_pubkey, true),
1073    ];
1074    Instruction::new_with_bincode(ID, &StakeInstruction::Redelegate, account_metas)
1075}
1076
1077#[cfg(feature = "bincode")]
1078#[deprecated(since = "2.1.0", note = "Redelegate will not be enabled")]
1079pub fn redelegate(
1080    stake_pubkey: &Pubkey,
1081    authorized_pubkey: &Pubkey,
1082    vote_pubkey: &Pubkey,
1083    uninitialized_stake_pubkey: &Pubkey,
1084) -> Vec<Instruction> {
1085    vec![
1086        solana_system_interface::instruction::allocate(
1087            uninitialized_stake_pubkey,
1088            StakeStateV2::size_of() as u64,
1089        ),
1090        solana_system_interface::instruction::assign(uninitialized_stake_pubkey, &ID),
1091        _redelegate(
1092            stake_pubkey,
1093            authorized_pubkey,
1094            vote_pubkey,
1095            uninitialized_stake_pubkey,
1096        ),
1097    ]
1098}
1099
1100#[cfg(feature = "bincode")]
1101#[deprecated(since = "2.1.0", note = "Redelegate will not be enabled")]
1102pub fn redelegate_with_seed(
1103    stake_pubkey: &Pubkey,
1104    authorized_pubkey: &Pubkey,
1105    vote_pubkey: &Pubkey,
1106    uninitialized_stake_pubkey: &Pubkey, // derived using create_with_seed()
1107    base: &Pubkey,                       // base
1108    seed: &str,                          // seed
1109) -> Vec<Instruction> {
1110    vec![
1111        solana_system_interface::instruction::allocate_with_seed(
1112            uninitialized_stake_pubkey,
1113            base,
1114            seed,
1115            StakeStateV2::size_of() as u64,
1116            &ID,
1117        ),
1118        _redelegate(
1119            stake_pubkey,
1120            authorized_pubkey,
1121            vote_pubkey,
1122            uninitialized_stake_pubkey,
1123        ),
1124    ]
1125}
1126
1127#[cfg(feature = "bincode")]
1128pub fn move_stake(
1129    source_stake_pubkey: &Pubkey,
1130    destination_stake_pubkey: &Pubkey,
1131    authorized_pubkey: &Pubkey,
1132    lamports: u64,
1133) -> Instruction {
1134    let account_metas = vec![
1135        AccountMeta::new(*source_stake_pubkey, false),
1136        AccountMeta::new(*destination_stake_pubkey, false),
1137        AccountMeta::new_readonly(*authorized_pubkey, true),
1138    ];
1139
1140    Instruction::new_with_bincode(ID, &StakeInstruction::MoveStake(lamports), account_metas)
1141}
1142
1143#[cfg(feature = "bincode")]
1144pub fn move_lamports(
1145    source_stake_pubkey: &Pubkey,
1146    destination_stake_pubkey: &Pubkey,
1147    authorized_pubkey: &Pubkey,
1148    lamports: u64,
1149) -> Instruction {
1150    let account_metas = vec![
1151        AccountMeta::new(*source_stake_pubkey, false),
1152        AccountMeta::new(*destination_stake_pubkey, false),
1153        AccountMeta::new_readonly(*authorized_pubkey, true),
1154    ];
1155
1156    Instruction::new_with_bincode(ID, &StakeInstruction::MoveLamports(lamports), account_metas)
1157}
1158
1159#[cfg(feature = "bincode")]
1160#[cfg(test)]
1161mod tests {
1162    use super::*;
1163
1164    #[allow(deprecated)]
1165    #[test]
1166    fn test_constants() {
1167        // Ensure that the constants are in sync with the solana program.
1168        assert_eq!(CLOCK_ID, solana_sdk_ids::sysvar::clock::ID);
1169
1170        // Ensure that the constants are in sync with the solana program.
1171        assert_eq!(STAKE_HISTORY_ID, solana_sdk_ids::sysvar::stake_history::ID);
1172
1173        // Ensure that the constants are in sync with the solana rent.
1174        assert_eq!(RENT_ID, solana_sdk_ids::sysvar::rent::ID);
1175    }
1176}