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    #[cfg_attr(feature = "codama", codama(size_prefix = number(u64)))]
569    pub authority_seed: String,
570    pub authority_owner: Pubkey,
571}
572
573#[cfg_attr(
574    feature = "codama",
575    derive(CodamaType),
576    codama(name = "authorizeCheckedWithSeedParams")
577)]
578#[derive(Debug, PartialEq, Eq, Clone)]
579#[cfg_attr(
580    feature = "serde",
581    derive(serde_derive::Deserialize, serde_derive::Serialize)
582)]
583pub struct AuthorizeCheckedWithSeedArgs {
584    pub stake_authorize: StakeAuthorize,
585    #[cfg_attr(feature = "codama", codama(size_prefix = number(u64)))]
586    pub authority_seed: String,
587    pub authority_owner: Pubkey,
588}
589
590#[cfg(feature = "bincode")]
591pub fn initialize(stake_pubkey: &Pubkey, authorized: &Authorized, lockup: &Lockup) -> Instruction {
592    Instruction::new_with_bincode(
593        ID,
594        &StakeInstruction::Initialize(*authorized, *lockup),
595        vec![
596            AccountMeta::new(*stake_pubkey, false),
597            AccountMeta::new_readonly(RENT_ID, false),
598        ],
599    )
600}
601
602#[cfg(feature = "bincode")]
603pub fn initialize_checked(stake_pubkey: &Pubkey, authorized: &Authorized) -> Instruction {
604    Instruction::new_with_bincode(
605        ID,
606        &StakeInstruction::InitializeChecked,
607        vec![
608            AccountMeta::new(*stake_pubkey, false),
609            AccountMeta::new_readonly(RENT_ID, false),
610            AccountMeta::new_readonly(authorized.staker, false),
611            AccountMeta::new_readonly(authorized.withdrawer, true),
612        ],
613    )
614}
615
616#[cfg(feature = "bincode")]
617pub fn create_account_with_seed(
618    from_pubkey: &Pubkey,
619    stake_pubkey: &Pubkey,
620    base: &Pubkey,
621    seed: &str,
622    authorized: &Authorized,
623    lockup: &Lockup,
624    lamports: u64,
625) -> Vec<Instruction> {
626    vec![
627        solana_system_interface::instruction::create_account_with_seed(
628            from_pubkey,
629            stake_pubkey,
630            base,
631            seed,
632            lamports,
633            StakeStateV2::size_of() as u64,
634            &ID,
635        ),
636        initialize(stake_pubkey, authorized, lockup),
637    ]
638}
639
640#[cfg(feature = "bincode")]
641pub fn create_account(
642    from_pubkey: &Pubkey,
643    stake_pubkey: &Pubkey,
644    authorized: &Authorized,
645    lockup: &Lockup,
646    lamports: u64,
647) -> Vec<Instruction> {
648    vec![
649        solana_system_interface::instruction::create_account(
650            from_pubkey,
651            stake_pubkey,
652            lamports,
653            StakeStateV2::size_of() as u64,
654            &ID,
655        ),
656        initialize(stake_pubkey, authorized, lockup),
657    ]
658}
659
660#[cfg(feature = "bincode")]
661pub fn create_account_with_seed_checked(
662    from_pubkey: &Pubkey,
663    stake_pubkey: &Pubkey,
664    base: &Pubkey,
665    seed: &str,
666    authorized: &Authorized,
667    lamports: u64,
668) -> Vec<Instruction> {
669    vec![
670        solana_system_interface::instruction::create_account_with_seed(
671            from_pubkey,
672            stake_pubkey,
673            base,
674            seed,
675            lamports,
676            StakeStateV2::size_of() as u64,
677            &ID,
678        ),
679        initialize_checked(stake_pubkey, authorized),
680    ]
681}
682
683#[cfg(feature = "bincode")]
684pub fn create_account_checked(
685    from_pubkey: &Pubkey,
686    stake_pubkey: &Pubkey,
687    authorized: &Authorized,
688    lamports: u64,
689) -> Vec<Instruction> {
690    vec![
691        solana_system_interface::instruction::create_account(
692            from_pubkey,
693            stake_pubkey,
694            lamports,
695            StakeStateV2::size_of() as u64,
696            &ID,
697        ),
698        initialize_checked(stake_pubkey, authorized),
699    ]
700}
701
702#[cfg(feature = "bincode")]
703fn _split(
704    stake_pubkey: &Pubkey,
705    authorized_pubkey: &Pubkey,
706    lamports: u64,
707    split_stake_pubkey: &Pubkey,
708) -> Instruction {
709    let account_metas = vec![
710        AccountMeta::new(*stake_pubkey, false),
711        AccountMeta::new(*split_stake_pubkey, false),
712        AccountMeta::new_readonly(*authorized_pubkey, true),
713    ];
714
715    Instruction::new_with_bincode(ID, &StakeInstruction::Split(lamports), account_metas)
716}
717
718#[cfg(feature = "bincode")]
719pub fn split(
720    stake_pubkey: &Pubkey,
721    authorized_pubkey: &Pubkey,
722    lamports: u64,
723    split_stake_pubkey: &Pubkey,
724) -> Vec<Instruction> {
725    vec![
726        solana_system_interface::instruction::allocate(
727            split_stake_pubkey,
728            StakeStateV2::size_of() as u64,
729        ),
730        solana_system_interface::instruction::assign(split_stake_pubkey, &ID),
731        _split(
732            stake_pubkey,
733            authorized_pubkey,
734            lamports,
735            split_stake_pubkey,
736        ),
737    ]
738}
739
740#[cfg(feature = "bincode")]
741pub fn split_with_seed(
742    stake_pubkey: &Pubkey,
743    authorized_pubkey: &Pubkey,
744    lamports: u64,
745    split_stake_pubkey: &Pubkey, // derived using create_with_seed()
746    base: &Pubkey,               // base
747    seed: &str,                  // seed
748) -> Vec<Instruction> {
749    vec![
750        solana_system_interface::instruction::allocate_with_seed(
751            split_stake_pubkey,
752            base,
753            seed,
754            StakeStateV2::size_of() as u64,
755            &ID,
756        ),
757        _split(
758            stake_pubkey,
759            authorized_pubkey,
760            lamports,
761            split_stake_pubkey,
762        ),
763    ]
764}
765
766#[cfg(feature = "bincode")]
767pub fn merge(
768    destination_stake_pubkey: &Pubkey,
769    source_stake_pubkey: &Pubkey,
770    authorized_pubkey: &Pubkey,
771) -> Vec<Instruction> {
772    let account_metas = vec![
773        AccountMeta::new(*destination_stake_pubkey, false),
774        AccountMeta::new(*source_stake_pubkey, false),
775        AccountMeta::new_readonly(CLOCK_ID, false),
776        AccountMeta::new_readonly(STAKE_HISTORY_ID, false),
777        AccountMeta::new_readonly(*authorized_pubkey, true),
778    ];
779
780    vec![Instruction::new_with_bincode(
781        ID,
782        &StakeInstruction::Merge,
783        account_metas,
784    )]
785}
786
787#[cfg(feature = "bincode")]
788pub fn create_account_and_delegate_stake(
789    from_pubkey: &Pubkey,
790    stake_pubkey: &Pubkey,
791    vote_pubkey: &Pubkey,
792    authorized: &Authorized,
793    lockup: &Lockup,
794    lamports: u64,
795) -> Vec<Instruction> {
796    let mut instructions = create_account(from_pubkey, stake_pubkey, authorized, lockup, lamports);
797    instructions.push(delegate_stake(
798        stake_pubkey,
799        &authorized.staker,
800        vote_pubkey,
801    ));
802    instructions
803}
804
805#[cfg(feature = "bincode")]
806#[allow(clippy::too_many_arguments)]
807pub fn create_account_with_seed_and_delegate_stake(
808    from_pubkey: &Pubkey,
809    stake_pubkey: &Pubkey,
810    base: &Pubkey,
811    seed: &str,
812    vote_pubkey: &Pubkey,
813    authorized: &Authorized,
814    lockup: &Lockup,
815    lamports: u64,
816) -> Vec<Instruction> {
817    let mut instructions = create_account_with_seed(
818        from_pubkey,
819        stake_pubkey,
820        base,
821        seed,
822        authorized,
823        lockup,
824        lamports,
825    );
826    instructions.push(delegate_stake(
827        stake_pubkey,
828        &authorized.staker,
829        vote_pubkey,
830    ));
831    instructions
832}
833
834#[cfg(feature = "bincode")]
835pub fn authorize(
836    stake_pubkey: &Pubkey,
837    authorized_pubkey: &Pubkey,
838    new_authorized_pubkey: &Pubkey,
839    stake_authorize: StakeAuthorize,
840    custodian_pubkey: Option<&Pubkey>,
841) -> Instruction {
842    let mut account_metas = vec![
843        AccountMeta::new(*stake_pubkey, false),
844        AccountMeta::new_readonly(CLOCK_ID, false),
845        AccountMeta::new_readonly(*authorized_pubkey, true),
846    ];
847
848    if let Some(custodian_pubkey) = custodian_pubkey {
849        account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
850    }
851
852    Instruction::new_with_bincode(
853        ID,
854        &StakeInstruction::Authorize(*new_authorized_pubkey, stake_authorize),
855        account_metas,
856    )
857}
858
859#[cfg(feature = "bincode")]
860pub fn authorize_checked(
861    stake_pubkey: &Pubkey,
862    authorized_pubkey: &Pubkey,
863    new_authorized_pubkey: &Pubkey,
864    stake_authorize: StakeAuthorize,
865    custodian_pubkey: Option<&Pubkey>,
866) -> Instruction {
867    let mut account_metas = vec![
868        AccountMeta::new(*stake_pubkey, false),
869        AccountMeta::new_readonly(CLOCK_ID, false),
870        AccountMeta::new_readonly(*authorized_pubkey, true),
871        AccountMeta::new_readonly(*new_authorized_pubkey, true),
872    ];
873
874    if let Some(custodian_pubkey) = custodian_pubkey {
875        account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
876    }
877
878    Instruction::new_with_bincode(
879        ID,
880        &StakeInstruction::AuthorizeChecked(stake_authorize),
881        account_metas,
882    )
883}
884
885#[cfg(feature = "bincode")]
886pub fn authorize_with_seed(
887    stake_pubkey: &Pubkey,
888    authority_base: &Pubkey,
889    authority_seed: String,
890    authority_owner: &Pubkey,
891    new_authorized_pubkey: &Pubkey,
892    stake_authorize: StakeAuthorize,
893    custodian_pubkey: Option<&Pubkey>,
894) -> Instruction {
895    let mut account_metas = vec![
896        AccountMeta::new(*stake_pubkey, false),
897        AccountMeta::new_readonly(*authority_base, true),
898        AccountMeta::new_readonly(CLOCK_ID, false),
899    ];
900
901    if let Some(custodian_pubkey) = custodian_pubkey {
902        account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
903    }
904
905    let args = AuthorizeWithSeedArgs {
906        new_authorized_pubkey: *new_authorized_pubkey,
907        stake_authorize,
908        authority_seed,
909        authority_owner: *authority_owner,
910    };
911
912    Instruction::new_with_bincode(
913        ID,
914        &StakeInstruction::AuthorizeWithSeed(args),
915        account_metas,
916    )
917}
918
919#[cfg(feature = "bincode")]
920pub fn authorize_checked_with_seed(
921    stake_pubkey: &Pubkey,
922    authority_base: &Pubkey,
923    authority_seed: String,
924    authority_owner: &Pubkey,
925    new_authorized_pubkey: &Pubkey,
926    stake_authorize: StakeAuthorize,
927    custodian_pubkey: Option<&Pubkey>,
928) -> Instruction {
929    let mut account_metas = vec![
930        AccountMeta::new(*stake_pubkey, false),
931        AccountMeta::new_readonly(*authority_base, true),
932        AccountMeta::new_readonly(CLOCK_ID, false),
933        AccountMeta::new_readonly(*new_authorized_pubkey, true),
934    ];
935
936    if let Some(custodian_pubkey) = custodian_pubkey {
937        account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
938    }
939
940    let args = AuthorizeCheckedWithSeedArgs {
941        stake_authorize,
942        authority_seed,
943        authority_owner: *authority_owner,
944    };
945
946    Instruction::new_with_bincode(
947        ID,
948        &StakeInstruction::AuthorizeCheckedWithSeed(args),
949        account_metas,
950    )
951}
952
953#[cfg(feature = "bincode")]
954pub fn delegate_stake(
955    stake_pubkey: &Pubkey,
956    authorized_pubkey: &Pubkey,
957    vote_pubkey: &Pubkey,
958) -> Instruction {
959    let account_metas = vec![
960        AccountMeta::new(*stake_pubkey, false),
961        AccountMeta::new_readonly(*vote_pubkey, false),
962        AccountMeta::new_readonly(CLOCK_ID, false),
963        AccountMeta::new_readonly(STAKE_HISTORY_ID, false),
964        // For backwards compatibility we pass the stake config, although this account is unused
965        AccountMeta::new_readonly(config::ID, false),
966        AccountMeta::new_readonly(*authorized_pubkey, true),
967    ];
968    Instruction::new_with_bincode(ID, &StakeInstruction::DelegateStake, account_metas)
969}
970
971#[cfg(feature = "bincode")]
972pub fn withdraw(
973    stake_pubkey: &Pubkey,
974    withdrawer_pubkey: &Pubkey,
975    to_pubkey: &Pubkey,
976    lamports: u64,
977    custodian_pubkey: Option<&Pubkey>,
978) -> Instruction {
979    let mut account_metas = vec![
980        AccountMeta::new(*stake_pubkey, false),
981        AccountMeta::new(*to_pubkey, false),
982        AccountMeta::new_readonly(CLOCK_ID, false),
983        AccountMeta::new_readonly(STAKE_HISTORY_ID, false),
984        AccountMeta::new_readonly(*withdrawer_pubkey, true),
985    ];
986
987    if let Some(custodian_pubkey) = custodian_pubkey {
988        account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
989    }
990
991    Instruction::new_with_bincode(ID, &StakeInstruction::Withdraw(lamports), account_metas)
992}
993
994#[cfg(feature = "bincode")]
995pub fn deactivate_stake(stake_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction {
996    let account_metas = vec![
997        AccountMeta::new(*stake_pubkey, false),
998        AccountMeta::new_readonly(CLOCK_ID, false),
999        AccountMeta::new_readonly(*authorized_pubkey, true),
1000    ];
1001    Instruction::new_with_bincode(ID, &StakeInstruction::Deactivate, account_metas)
1002}
1003
1004#[cfg(feature = "bincode")]
1005pub fn set_lockup(
1006    stake_pubkey: &Pubkey,
1007    lockup: &LockupArgs,
1008    custodian_pubkey: &Pubkey,
1009) -> Instruction {
1010    let account_metas = vec![
1011        AccountMeta::new(*stake_pubkey, false),
1012        AccountMeta::new_readonly(*custodian_pubkey, true),
1013    ];
1014    Instruction::new_with_bincode(ID, &StakeInstruction::SetLockup(*lockup), account_metas)
1015}
1016
1017#[cfg(feature = "bincode")]
1018pub fn set_lockup_checked(
1019    stake_pubkey: &Pubkey,
1020    lockup: &LockupArgs,
1021    custodian_pubkey: &Pubkey,
1022) -> Instruction {
1023    let mut account_metas = vec![
1024        AccountMeta::new(*stake_pubkey, false),
1025        AccountMeta::new_readonly(*custodian_pubkey, true),
1026    ];
1027
1028    let lockup_checked = LockupCheckedArgs {
1029        unix_timestamp: lockup.unix_timestamp,
1030        epoch: lockup.epoch,
1031    };
1032    if let Some(new_custodian) = lockup.custodian {
1033        account_metas.push(AccountMeta::new_readonly(new_custodian, true));
1034    }
1035    Instruction::new_with_bincode(
1036        ID,
1037        &StakeInstruction::SetLockupChecked(lockup_checked),
1038        account_metas,
1039    )
1040}
1041
1042#[cfg(feature = "bincode")]
1043pub fn get_minimum_delegation() -> Instruction {
1044    Instruction::new_with_bincode(ID, &StakeInstruction::GetMinimumDelegation, Vec::default())
1045}
1046
1047#[cfg(feature = "bincode")]
1048pub fn deactivate_delinquent_stake(
1049    stake_account: &Pubkey,
1050    delinquent_vote_account: &Pubkey,
1051    reference_vote_account: &Pubkey,
1052) -> Instruction {
1053    let account_metas = vec![
1054        AccountMeta::new(*stake_account, false),
1055        AccountMeta::new_readonly(*delinquent_vote_account, false),
1056        AccountMeta::new_readonly(*reference_vote_account, false),
1057    ];
1058    Instruction::new_with_bincode(ID, &StakeInstruction::DeactivateDelinquent, account_metas)
1059}
1060
1061#[cfg(feature = "bincode")]
1062fn _redelegate(
1063    stake_pubkey: &Pubkey,
1064    authorized_pubkey: &Pubkey,
1065    vote_pubkey: &Pubkey,
1066    uninitialized_stake_pubkey: &Pubkey,
1067) -> Instruction {
1068    let account_metas = vec![
1069        AccountMeta::new(*stake_pubkey, false),
1070        AccountMeta::new(*uninitialized_stake_pubkey, false),
1071        AccountMeta::new_readonly(*vote_pubkey, false),
1072        // For backwards compatibility we pass the stake config, although this account is unused
1073        AccountMeta::new_readonly(config::ID, false),
1074        AccountMeta::new_readonly(*authorized_pubkey, true),
1075    ];
1076    Instruction::new_with_bincode(ID, &StakeInstruction::Redelegate, account_metas)
1077}
1078
1079#[cfg(feature = "bincode")]
1080#[deprecated(since = "2.1.0", note = "Redelegate will not be enabled")]
1081pub fn redelegate(
1082    stake_pubkey: &Pubkey,
1083    authorized_pubkey: &Pubkey,
1084    vote_pubkey: &Pubkey,
1085    uninitialized_stake_pubkey: &Pubkey,
1086) -> Vec<Instruction> {
1087    vec![
1088        solana_system_interface::instruction::allocate(
1089            uninitialized_stake_pubkey,
1090            StakeStateV2::size_of() as u64,
1091        ),
1092        solana_system_interface::instruction::assign(uninitialized_stake_pubkey, &ID),
1093        _redelegate(
1094            stake_pubkey,
1095            authorized_pubkey,
1096            vote_pubkey,
1097            uninitialized_stake_pubkey,
1098        ),
1099    ]
1100}
1101
1102#[cfg(feature = "bincode")]
1103#[deprecated(since = "2.1.0", note = "Redelegate will not be enabled")]
1104pub fn redelegate_with_seed(
1105    stake_pubkey: &Pubkey,
1106    authorized_pubkey: &Pubkey,
1107    vote_pubkey: &Pubkey,
1108    uninitialized_stake_pubkey: &Pubkey, // derived using create_with_seed()
1109    base: &Pubkey,                       // base
1110    seed: &str,                          // seed
1111) -> Vec<Instruction> {
1112    vec![
1113        solana_system_interface::instruction::allocate_with_seed(
1114            uninitialized_stake_pubkey,
1115            base,
1116            seed,
1117            StakeStateV2::size_of() as u64,
1118            &ID,
1119        ),
1120        _redelegate(
1121            stake_pubkey,
1122            authorized_pubkey,
1123            vote_pubkey,
1124            uninitialized_stake_pubkey,
1125        ),
1126    ]
1127}
1128
1129#[cfg(feature = "bincode")]
1130pub fn move_stake(
1131    source_stake_pubkey: &Pubkey,
1132    destination_stake_pubkey: &Pubkey,
1133    authorized_pubkey: &Pubkey,
1134    lamports: u64,
1135) -> Instruction {
1136    let account_metas = vec![
1137        AccountMeta::new(*source_stake_pubkey, false),
1138        AccountMeta::new(*destination_stake_pubkey, false),
1139        AccountMeta::new_readonly(*authorized_pubkey, true),
1140    ];
1141
1142    Instruction::new_with_bincode(ID, &StakeInstruction::MoveStake(lamports), account_metas)
1143}
1144
1145#[cfg(feature = "bincode")]
1146pub fn move_lamports(
1147    source_stake_pubkey: &Pubkey,
1148    destination_stake_pubkey: &Pubkey,
1149    authorized_pubkey: &Pubkey,
1150    lamports: u64,
1151) -> Instruction {
1152    let account_metas = vec![
1153        AccountMeta::new(*source_stake_pubkey, false),
1154        AccountMeta::new(*destination_stake_pubkey, false),
1155        AccountMeta::new_readonly(*authorized_pubkey, true),
1156    ];
1157
1158    Instruction::new_with_bincode(ID, &StakeInstruction::MoveLamports(lamports), account_metas)
1159}
1160
1161#[cfg(feature = "bincode")]
1162#[cfg(test)]
1163mod tests {
1164    use super::*;
1165
1166    #[allow(deprecated)]
1167    #[test]
1168    fn test_constants() {
1169        // Ensure that the constants are in sync with the solana program.
1170        assert_eq!(CLOCK_ID, solana_sdk_ids::sysvar::clock::ID);
1171
1172        // Ensure that the constants are in sync with the solana program.
1173        assert_eq!(STAKE_HISTORY_ID, solana_sdk_ids::sysvar::stake_history::ID);
1174
1175        // Ensure that the constants are in sync with the solana rent.
1176        assert_eq!(RENT_ID, solana_sdk_ids::sysvar::rent::ID);
1177    }
1178}