clone_solana_stake_program/
processor.rs

1use {
2    crate::{helpers::*, id, PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH},
3    clone_solana_program::{
4        account_info::{next_account_info, AccountInfo},
5        clock::Clock,
6        entrypoint::ProgramResult,
7        msg,
8        program::set_return_data,
9        program_error::ProgramError,
10        pubkey::Pubkey,
11        rent::Rent,
12        stake::{
13            instruction::{
14                AuthorizeCheckedWithSeedArgs, AuthorizeWithSeedArgs, LockupArgs, LockupCheckedArgs,
15                StakeError, StakeInstruction,
16            },
17            stake_flags::StakeFlags,
18            state::{Authorized, Lockup, Meta, StakeAuthorize, StakeStateV2},
19            tools::{acceptable_reference_epoch_credits, eligible_for_deactivate_delinquent},
20        },
21        sysvar::{epoch_rewards::EpochRewards, stake_history::StakeHistorySysvar, Sysvar},
22        vote::{program as clone_solana_vote_program, state::VoteState},
23    },
24    std::{collections::HashSet, mem::MaybeUninit},
25};
26
27fn get_vote_state(vote_account_info: &AccountInfo) -> Result<Box<VoteState>, ProgramError> {
28    if *vote_account_info.owner != clone_solana_vote_program::id() {
29        return Err(ProgramError::IncorrectProgramId);
30    }
31
32    let mut vote_state = Box::new(MaybeUninit::uninit());
33    VoteState::deserialize_into_uninit(&vote_account_info.try_borrow_data()?, vote_state.as_mut())
34        .map_err(|_| ProgramError::InvalidAccountData)?;
35    let vote_state = unsafe { Box::from_raw(Box::into_raw(vote_state) as *mut VoteState) };
36
37    Ok(vote_state)
38}
39
40fn get_stake_state(stake_account_info: &AccountInfo) -> Result<StakeStateV2, ProgramError> {
41    if *stake_account_info.owner != id() {
42        return Err(ProgramError::InvalidAccountOwner);
43    }
44
45    stake_account_info
46        .deserialize_data()
47        .map_err(|_| ProgramError::InvalidAccountData)
48}
49
50fn set_stake_state(stake_account_info: &AccountInfo, new_state: &StakeStateV2) -> ProgramResult {
51    let serialized_size =
52        bincode::serialized_size(new_state).map_err(|_| ProgramError::InvalidAccountData)?;
53    if serialized_size > stake_account_info.data_len() as u64 {
54        return Err(ProgramError::AccountDataTooSmall);
55    }
56
57    bincode::serialize_into(&mut stake_account_info.data.borrow_mut()[..], new_state)
58        .map_err(|_| ProgramError::InvalidAccountData)
59}
60
61// dont call this "move" because we have an instruction MoveLamports
62fn relocate_lamports(
63    source_account_info: &AccountInfo,
64    destination_account_info: &AccountInfo,
65    lamports: u64,
66) -> ProgramResult {
67    {
68        let mut source_lamports = source_account_info.try_borrow_mut_lamports()?;
69        **source_lamports = source_lamports
70            .checked_sub(lamports)
71            .ok_or(ProgramError::InsufficientFunds)?;
72    }
73
74    {
75        let mut destination_lamports = destination_account_info.try_borrow_mut_lamports()?;
76        **destination_lamports = destination_lamports
77            .checked_add(lamports)
78            .ok_or(ProgramError::ArithmeticOverflow)?;
79    }
80
81    Ok(())
82}
83
84// almost all native stake program processors accumulate every account signer
85// they then defer all signer validation to functions on Meta or Authorized
86// this results in an instruction interface that is much looser than the one documented
87// to avoid breaking backwards compatibility, we do the same here
88// in the future, we may decide to tighten the interface and break badly formed transactions
89fn collect_signers(accounts: &[AccountInfo]) -> HashSet<Pubkey> {
90    let mut signers = HashSet::new();
91
92    for account in accounts {
93        if account.is_signer {
94            signers.insert(*account.key);
95        }
96    }
97
98    signers
99}
100
101// MoveStake, MoveLamports, Withdraw, and AuthorizeWithSeed assemble signers explicitly
102fn collect_signers_checked<'a>(
103    authority_info: Option<&'a AccountInfo>,
104    custodian_info: Option<&'a AccountInfo>,
105) -> Result<(HashSet<Pubkey>, Option<&'a Pubkey>), ProgramError> {
106    let mut signers = HashSet::new();
107
108    if let Some(authority_info) = authority_info {
109        if authority_info.is_signer {
110            signers.insert(*authority_info.key);
111        } else {
112            return Err(ProgramError::MissingRequiredSignature);
113        }
114    }
115
116    let custodian = if let Some(custodian_info) = custodian_info {
117        if custodian_info.is_signer {
118            signers.insert(*custodian_info.key);
119            Some(custodian_info.key)
120        } else {
121            return Err(ProgramError::MissingRequiredSignature);
122        }
123    } else {
124        None
125    };
126
127    Ok((signers, custodian))
128}
129
130fn do_initialize(
131    stake_account_info: &AccountInfo,
132    authorized: Authorized,
133    lockup: Lockup,
134    rent: &Rent,
135) -> ProgramResult {
136    if stake_account_info.data_len() != StakeStateV2::size_of() {
137        return Err(ProgramError::InvalidAccountData);
138    }
139
140    if let StakeStateV2::Uninitialized = get_stake_state(stake_account_info)? {
141        let rent_exempt_reserve = rent.minimum_balance(stake_account_info.data_len());
142        if stake_account_info.lamports() >= rent_exempt_reserve {
143            let stake_state = StakeStateV2::Initialized(Meta {
144                rent_exempt_reserve,
145                authorized,
146                lockup,
147            });
148
149            set_stake_state(stake_account_info, &stake_state)
150        } else {
151            Err(ProgramError::InsufficientFunds)
152        }
153    } else {
154        Err(ProgramError::InvalidAccountData)
155    }
156}
157
158fn do_authorize(
159    stake_account_info: &AccountInfo,
160    signers: &HashSet<Pubkey>,
161    new_authority: &Pubkey,
162    authority_type: StakeAuthorize,
163    custodian: Option<&Pubkey>,
164    clock: &Clock,
165) -> ProgramResult {
166    match get_stake_state(stake_account_info)? {
167        StakeStateV2::Initialized(mut meta) => {
168            meta.authorized
169                .authorize(
170                    signers,
171                    new_authority,
172                    authority_type,
173                    Some((&meta.lockup, clock, custodian)),
174                )
175                .map_err(to_program_error)?;
176
177            set_stake_state(stake_account_info, &StakeStateV2::Initialized(meta))
178        }
179        StakeStateV2::Stake(mut meta, stake, stake_flags) => {
180            meta.authorized
181                .authorize(
182                    signers,
183                    new_authority,
184                    authority_type,
185                    Some((&meta.lockup, clock, custodian)),
186                )
187                .map_err(to_program_error)?;
188
189            set_stake_state(
190                stake_account_info,
191                &StakeStateV2::Stake(meta, stake, stake_flags),
192            )
193        }
194        _ => Err(ProgramError::InvalidAccountData),
195    }
196}
197
198fn do_set_lockup(
199    stake_account_info: &AccountInfo,
200    signers: &HashSet<Pubkey>,
201    lockup: &LockupArgs,
202    clock: &Clock,
203) -> ProgramResult {
204    match get_stake_state(stake_account_info)? {
205        StakeStateV2::Initialized(mut meta) => {
206            meta.set_lockup(lockup, signers, clock)
207                .map_err(to_program_error)?;
208
209            set_stake_state(stake_account_info, &StakeStateV2::Initialized(meta))
210        }
211        StakeStateV2::Stake(mut meta, stake, stake_flags) => {
212            meta.set_lockup(lockup, signers, clock)
213                .map_err(to_program_error)?;
214
215            set_stake_state(
216                stake_account_info,
217                &StakeStateV2::Stake(meta, stake, stake_flags),
218            )
219        }
220        _ => Err(ProgramError::InvalidAccountData),
221    }
222}
223
224fn move_stake_or_lamports_shared_checks(
225    source_stake_account_info: &AccountInfo,
226    lamports: u64,
227    destination_stake_account_info: &AccountInfo,
228    stake_authority_info: &AccountInfo,
229) -> Result<(MergeKind, MergeKind), ProgramError> {
230    // authority must sign
231    let (signers, _) = collect_signers_checked(Some(stake_authority_info), None)?;
232
233    // confirm not the same account
234    if *source_stake_account_info.key == *destination_stake_account_info.key {
235        return Err(ProgramError::InvalidInstructionData);
236    }
237
238    // source and destination must be writable
239    // runtime guards against unowned writes, but MoveStake and MoveLamports are defined by SIMD
240    // we check explicitly to avoid any possibility of a successful no-op that never attempts to write
241    if !source_stake_account_info.is_writable || !destination_stake_account_info.is_writable {
242        return Err(ProgramError::InvalidInstructionData);
243    }
244
245    // must move something
246    if lamports == 0 {
247        return Err(ProgramError::InvalidArgument);
248    }
249
250    let clock = Clock::get()?;
251    let stake_history = StakeHistorySysvar(clock.epoch);
252
253    // get_if_mergeable ensures accounts are not partly activated or in any form of deactivating
254    // we still need to exclude activating state ourselves
255    let source_merge_kind = MergeKind::get_if_mergeable(
256        &get_stake_state(source_stake_account_info)?,
257        source_stake_account_info.lamports(),
258        &clock,
259        &stake_history,
260    )?;
261
262    // Authorized staker is allowed to move stake
263    source_merge_kind
264        .meta()
265        .authorized
266        .check(&signers, StakeAuthorize::Staker)
267        .map_err(to_program_error)?;
268
269    // same transient assurance as with source
270    let destination_merge_kind = MergeKind::get_if_mergeable(
271        &get_stake_state(destination_stake_account_info)?,
272        destination_stake_account_info.lamports(),
273        &clock,
274        &stake_history,
275    )?;
276
277    // ensure all authorities match and lockups match if lockup is in force
278    MergeKind::metas_can_merge(
279        source_merge_kind.meta(),
280        destination_merge_kind.meta(),
281        &clock,
282    )?;
283
284    Ok((source_merge_kind, destination_merge_kind))
285}
286
287// NOTE our usage of the accounts iter is idiosyncratic, in imitation of the native stake program
288// native stake typically accumulates all signers from the accounts array indiscriminately
289// each instruction processor also asserts a required number of instruction accounts
290// but this is extremely ad hoc, essentially allowing any account to act as a signing authority
291// when lengths are asserted in setup, accounts are retrieved via hardcoded index from InstructionContext
292// but after control is passed to main processing functions, they are pulled from the TransactionContext
293//
294// we aim to implement this behavior exactly, such that both programs are consensus compatible:
295// * all transactions that would fail on one program also fail on the other
296// * all transactions that would succeed on one program also succeed on the other
297// * for successful transactions, all account state transitions are identical
298// error codes and log output may differ
299//
300// this is not strictly necessary, since the switchover will be feature-gated. so this is not a security issue
301// mostly its so no one can blame the bpf switchover for breaking their usecase, even pathological ones
302//
303// in service to this end, all accounts iters are commented with how the native program uses them
304// for accounts that should always, or almost always, exist, which the native program does not assert...
305// ...we leave a commented-out `next_account_info()` call, to aid in a future refactor
306// after the bpf switchover ships, we may update to strictly assert these accounts exist
307pub struct Processor {}
308impl Processor {
309    fn process_initialize(
310        accounts: &[AccountInfo],
311        authorized: Authorized,
312        lockup: Lockup,
313    ) -> ProgramResult {
314        let account_info_iter = &mut accounts.iter();
315
316        // native asserts: 2 accounts (1 sysvar)
317        let stake_account_info = next_account_info(account_info_iter)?;
318        let rent_info = next_account_info(account_info_iter)?;
319
320        let rent = &Rent::from_account_info(rent_info)?;
321
322        // `get_stake_state()` is called unconditionally, which checks owner
323        do_initialize(stake_account_info, authorized, lockup, rent)?;
324
325        Ok(())
326    }
327
328    fn process_authorize(
329        accounts: &[AccountInfo],
330        new_authority: Pubkey,
331        authority_type: StakeAuthorize,
332    ) -> ProgramResult {
333        let signers = collect_signers(accounts);
334        let account_info_iter = &mut accounts.iter();
335
336        // native asserts: 3 accounts (1 sysvar)
337        let stake_account_info = next_account_info(account_info_iter)?;
338        let clock_info = next_account_info(account_info_iter)?;
339        let _stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
340
341        // other accounts
342        let option_lockup_authority_info = next_account_info(account_info_iter).ok();
343
344        let clock = &Clock::from_account_info(clock_info)?;
345
346        let custodian = option_lockup_authority_info
347            .filter(|a| a.is_signer)
348            .map(|a| a.key);
349
350        // `get_stake_state()` is called unconditionally, which checks owner
351        do_authorize(
352            stake_account_info,
353            &signers,
354            &new_authority,
355            authority_type,
356            custodian,
357            clock,
358        )?;
359
360        Ok(())
361    }
362
363    fn process_delegate(accounts: &[AccountInfo]) -> ProgramResult {
364        let signers = collect_signers(accounts);
365        let account_info_iter = &mut accounts.iter();
366
367        // native asserts: 5 accounts (2 sysvars + stake config)
368        let stake_account_info = next_account_info(account_info_iter)?;
369        let vote_account_info = next_account_info(account_info_iter)?;
370        let clock_info = next_account_info(account_info_iter)?;
371        let _stake_history_info = next_account_info(account_info_iter)?;
372        let _stake_config_info = next_account_info(account_info_iter)?;
373
374        // other accounts
375        // let _stake_authority_info = next_account_info(account_info_iter);
376
377        let clock = &Clock::from_account_info(clock_info)?;
378        let stake_history = &StakeHistorySysvar(clock.epoch);
379
380        let vote_state = get_vote_state(vote_account_info)?;
381
382        match get_stake_state(stake_account_info)? {
383            StakeStateV2::Initialized(meta) => {
384                meta.authorized
385                    .check(&signers, StakeAuthorize::Staker)
386                    .map_err(to_program_error)?;
387
388                let ValidatedDelegatedInfo { stake_amount } =
389                    validate_delegated_amount(stake_account_info, &meta)?;
390
391                let stake = new_stake(
392                    stake_amount,
393                    vote_account_info.key,
394                    &vote_state,
395                    clock.epoch,
396                );
397
398                set_stake_state(
399                    stake_account_info,
400                    &StakeStateV2::Stake(meta, stake, StakeFlags::empty()),
401                )
402            }
403            StakeStateV2::Stake(meta, mut stake, flags) => {
404                meta.authorized
405                    .check(&signers, StakeAuthorize::Staker)
406                    .map_err(to_program_error)?;
407
408                let ValidatedDelegatedInfo { stake_amount } =
409                    validate_delegated_amount(stake_account_info, &meta)?;
410
411                redelegate_stake(
412                    &mut stake,
413                    stake_amount,
414                    vote_account_info.key,
415                    &vote_state,
416                    clock.epoch,
417                    stake_history,
418                )?;
419
420                set_stake_state(stake_account_info, &StakeStateV2::Stake(meta, stake, flags))
421            }
422            _ => Err(ProgramError::InvalidAccountData),
423        }?;
424
425        Ok(())
426    }
427
428    fn process_split(accounts: &[AccountInfo], split_lamports: u64) -> ProgramResult {
429        let signers = collect_signers(accounts);
430        let account_info_iter = &mut accounts.iter();
431
432        // native asserts: 2 accounts
433        let source_stake_account_info = next_account_info(account_info_iter)?;
434        let destination_stake_account_info = next_account_info(account_info_iter)?;
435
436        // other accounts
437        // let _stake_authority_info = next_account_info(account_info_iter);
438
439        let clock = Clock::get()?;
440        let stake_history = &StakeHistorySysvar(clock.epoch);
441
442        let destination_data_len = destination_stake_account_info.data_len();
443        if destination_data_len != StakeStateV2::size_of() {
444            return Err(ProgramError::InvalidAccountData);
445        }
446
447        if let StakeStateV2::Uninitialized = get_stake_state(destination_stake_account_info)? {
448            // we can split into this
449        } else {
450            return Err(ProgramError::InvalidAccountData);
451        }
452
453        let source_lamport_balance = source_stake_account_info.lamports();
454        let destination_lamport_balance = destination_stake_account_info.lamports();
455
456        if split_lamports > source_lamport_balance {
457            return Err(ProgramError::InsufficientFunds);
458        }
459
460        match get_stake_state(source_stake_account_info)? {
461            StakeStateV2::Stake(source_meta, mut source_stake, stake_flags) => {
462                source_meta
463                    .authorized
464                    .check(&signers, StakeAuthorize::Staker)
465                    .map_err(to_program_error)?;
466
467                let minimum_delegation = crate::get_minimum_delegation();
468
469                let status = source_stake.delegation.stake_activating_and_deactivating(
470                    clock.epoch,
471                    stake_history,
472                    PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
473                );
474
475                let is_active = status.effective > 0;
476
477                // NOTE this function also internally summons Rent via syscall
478                let validated_split_info = validate_split_amount(
479                    source_lamport_balance,
480                    destination_lamport_balance,
481                    split_lamports,
482                    &source_meta,
483                    destination_data_len,
484                    minimum_delegation,
485                    is_active,
486                )?;
487
488                // split the stake, subtract rent_exempt_balance unless
489                // the destination account already has those lamports
490                // in place.
491                // this means that the new stake account will have a stake equivalent to
492                // lamports minus rent_exempt_reserve if it starts out with a zero balance
493                let (remaining_stake_delta, split_stake_amount) =
494                    if validated_split_info.source_remaining_balance == 0 {
495                        // If split amount equals the full source stake (as implied by 0
496                        // source_remaining_balance), the new split stake must equal the same
497                        // amount, regardless of any current lamport balance in the split account.
498                        // Since split accounts retain the state of their source account, this
499                        // prevents any magic activation of stake by prefunding the split account.
500                        //
501                        // The new split stake also needs to ignore any positive delta between the
502                        // original rent_exempt_reserve and the split_rent_exempt_reserve, in order
503                        // to prevent magic activation of stake by splitting between accounts of
504                        // different sizes.
505                        let remaining_stake_delta =
506                            split_lamports.saturating_sub(source_meta.rent_exempt_reserve);
507                        (remaining_stake_delta, remaining_stake_delta)
508                    } else {
509                        // Otherwise, the new split stake should reflect the entire split
510                        // requested, less any lamports needed to cover the
511                        // split_rent_exempt_reserve.
512                        if source_stake.delegation.stake.saturating_sub(split_lamports)
513                            < minimum_delegation
514                        {
515                            return Err(StakeError::InsufficientDelegation.into());
516                        }
517
518                        (
519                            split_lamports,
520                            split_lamports.saturating_sub(
521                                validated_split_info
522                                    .destination_rent_exempt_reserve
523                                    .saturating_sub(destination_lamport_balance),
524                            ),
525                        )
526                    };
527
528                if split_stake_amount < minimum_delegation {
529                    return Err(StakeError::InsufficientDelegation.into());
530                }
531
532                let destination_stake =
533                    source_stake.split(remaining_stake_delta, split_stake_amount)?;
534
535                let mut destination_meta = source_meta;
536                destination_meta.rent_exempt_reserve =
537                    validated_split_info.destination_rent_exempt_reserve;
538
539                set_stake_state(
540                    source_stake_account_info,
541                    &StakeStateV2::Stake(source_meta, source_stake, stake_flags),
542                )?;
543
544                set_stake_state(
545                    destination_stake_account_info,
546                    &StakeStateV2::Stake(destination_meta, destination_stake, stake_flags),
547                )?;
548            }
549            StakeStateV2::Initialized(source_meta) => {
550                source_meta
551                    .authorized
552                    .check(&signers, StakeAuthorize::Staker)
553                    .map_err(to_program_error)?;
554
555                // NOTE this function also internally summons Rent via syscall
556                let validated_split_info = validate_split_amount(
557                    source_lamport_balance,
558                    destination_lamport_balance,
559                    split_lamports,
560                    &source_meta,
561                    destination_data_len,
562                    0,     // additional_required_lamports
563                    false, // is_active
564                )?;
565
566                let mut destination_meta = source_meta;
567                destination_meta.rent_exempt_reserve =
568                    validated_split_info.destination_rent_exempt_reserve;
569
570                set_stake_state(
571                    destination_stake_account_info,
572                    &StakeStateV2::Initialized(destination_meta),
573                )?;
574            }
575            StakeStateV2::Uninitialized => {
576                if !source_stake_account_info.is_signer {
577                    return Err(ProgramError::MissingRequiredSignature);
578                }
579            }
580            _ => return Err(ProgramError::InvalidAccountData),
581        }
582
583        // Deinitialize state upon zero balance
584        if split_lamports == source_lamport_balance {
585            set_stake_state(source_stake_account_info, &StakeStateV2::Uninitialized)?;
586        }
587
588        relocate_lamports(
589            source_stake_account_info,
590            destination_stake_account_info,
591            split_lamports,
592        )?;
593
594        Ok(())
595    }
596
597    fn process_withdraw(accounts: &[AccountInfo], withdraw_lamports: u64) -> ProgramResult {
598        let account_info_iter = &mut accounts.iter();
599
600        // native asserts: 5 accounts (2 sysvars)
601        let source_stake_account_info = next_account_info(account_info_iter)?;
602        let destination_info = next_account_info(account_info_iter)?;
603        let clock_info = next_account_info(account_info_iter)?;
604        let _stake_history_info = next_account_info(account_info_iter)?;
605        let withdraw_authority_info = next_account_info(account_info_iter)?;
606
607        // other accounts
608        let option_lockup_authority_info = next_account_info(account_info_iter).ok();
609
610        let clock = &Clock::from_account_info(clock_info)?;
611        let stake_history = &StakeHistorySysvar(clock.epoch);
612
613        // this is somewhat subtle. for Initialized and Stake, there is a real authority
614        // but for Uninitialized, the source account is passed twice, and signed for
615        let (signers, custodian) =
616            collect_signers_checked(Some(withdraw_authority_info), option_lockup_authority_info)?;
617
618        let (lockup, reserve, is_staked) = match get_stake_state(source_stake_account_info)? {
619            StakeStateV2::Stake(meta, stake, _stake_flag) => {
620                meta.authorized
621                    .check(&signers, StakeAuthorize::Withdrawer)
622                    .map_err(to_program_error)?;
623                // if we have a deactivation epoch and we're in cooldown
624                let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
625                    stake.delegation.stake(
626                        clock.epoch,
627                        stake_history,
628                        PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
629                    )
630                } else {
631                    // Assume full stake if the stake account hasn't been
632                    //  de-activated, because in the future the exposed stake
633                    //  might be higher than stake.stake() due to warmup
634                    stake.delegation.stake
635                };
636
637                let staked_and_reserve = checked_add(staked, meta.rent_exempt_reserve)?;
638                (meta.lockup, staked_and_reserve, staked != 0)
639            }
640            StakeStateV2::Initialized(meta) => {
641                meta.authorized
642                    .check(&signers, StakeAuthorize::Withdrawer)
643                    .map_err(to_program_error)?;
644                // stake accounts must have a balance >= rent_exempt_reserve
645                (meta.lockup, meta.rent_exempt_reserve, false)
646            }
647            StakeStateV2::Uninitialized => {
648                if !signers.contains(source_stake_account_info.key) {
649                    return Err(ProgramError::MissingRequiredSignature);
650                }
651                (Lockup::default(), 0, false) // no lockup, no restrictions
652            }
653            _ => return Err(ProgramError::InvalidAccountData),
654        };
655
656        // verify that lockup has expired or that the withdrawal is signed by the
657        // custodian both epoch and unix_timestamp must have passed
658        if lockup.is_in_force(clock, custodian) {
659            return Err(StakeError::LockupInForce.into());
660        }
661
662        let stake_account_lamports = source_stake_account_info.lamports();
663        if withdraw_lamports == stake_account_lamports {
664            // if the stake is active, we mustn't allow the account to go away
665            if is_staked {
666                return Err(ProgramError::InsufficientFunds);
667            }
668
669            // Deinitialize state upon zero balance
670            set_stake_state(source_stake_account_info, &StakeStateV2::Uninitialized)?;
671        } else {
672            // a partial withdrawal must not deplete the reserve
673            let withdraw_lamports_and_reserve = checked_add(withdraw_lamports, reserve)?;
674            if withdraw_lamports_and_reserve > stake_account_lamports {
675                return Err(ProgramError::InsufficientFunds);
676            }
677        }
678
679        relocate_lamports(
680            source_stake_account_info,
681            destination_info,
682            withdraw_lamports,
683        )?;
684
685        Ok(())
686    }
687
688    fn process_deactivate(accounts: &[AccountInfo]) -> ProgramResult {
689        let signers = collect_signers(accounts);
690        let account_info_iter = &mut accounts.iter();
691
692        // native asserts: 2 accounts (1 sysvar)
693        let stake_account_info = next_account_info(account_info_iter)?;
694        let clock_info = next_account_info(account_info_iter)?;
695
696        // other accounts
697        // let _stake_authority_info = next_account_info(account_info_iter);
698
699        let clock = &Clock::from_account_info(clock_info)?;
700
701        match get_stake_state(stake_account_info)? {
702            StakeStateV2::Stake(meta, mut stake, stake_flags) => {
703                meta.authorized
704                    .check(&signers, StakeAuthorize::Staker)
705                    .map_err(to_program_error)?;
706
707                stake.deactivate(clock.epoch)?;
708
709                set_stake_state(
710                    stake_account_info,
711                    &StakeStateV2::Stake(meta, stake, stake_flags),
712                )
713            }
714            _ => Err(ProgramError::InvalidAccountData),
715        }?;
716
717        Ok(())
718    }
719
720    fn process_set_lockup(accounts: &[AccountInfo], lockup: LockupArgs) -> ProgramResult {
721        let signers = collect_signers(accounts);
722        let account_info_iter = &mut accounts.iter();
723
724        // native asserts: 1 account
725        let stake_account_info = next_account_info(account_info_iter)?;
726
727        // other accounts
728        // let _old_withdraw_or_lockup_authority_info = next_account_info(account_info_iter);
729
730        let clock = Clock::get()?;
731
732        // `get_stake_state()` is called unconditionally, which checks owner
733        do_set_lockup(stake_account_info, &signers, &lockup, &clock)?;
734
735        Ok(())
736    }
737
738    fn process_merge(accounts: &[AccountInfo]) -> ProgramResult {
739        let signers = collect_signers(accounts);
740        let account_info_iter = &mut accounts.iter();
741
742        // native asserts: 4 accounts (2 sysvars)
743        let destination_stake_account_info = next_account_info(account_info_iter)?;
744        let source_stake_account_info = next_account_info(account_info_iter)?;
745        let clock_info = next_account_info(account_info_iter)?;
746        let _stake_history_info = next_account_info(account_info_iter)?;
747
748        // other accounts
749        // let _stake_authority_info = next_account_info(account_info_iter);
750
751        let clock = &Clock::from_account_info(clock_info)?;
752        let stake_history = &StakeHistorySysvar(clock.epoch);
753
754        if source_stake_account_info.key == destination_stake_account_info.key {
755            return Err(ProgramError::InvalidArgument);
756        }
757
758        msg!("Checking if destination stake is mergeable");
759        let destination_merge_kind = MergeKind::get_if_mergeable(
760            &get_stake_state(destination_stake_account_info)?,
761            destination_stake_account_info.lamports(),
762            clock,
763            stake_history,
764        )?;
765
766        // Authorized staker is allowed to split/merge accounts
767        destination_merge_kind
768            .meta()
769            .authorized
770            .check(&signers, StakeAuthorize::Staker)
771            .map_err(|_| ProgramError::MissingRequiredSignature)?;
772
773        msg!("Checking if source stake is mergeable");
774        let source_merge_kind = MergeKind::get_if_mergeable(
775            &get_stake_state(source_stake_account_info)?,
776            source_stake_account_info.lamports(),
777            clock,
778            stake_history,
779        )?;
780
781        msg!("Merging stake accounts");
782        if let Some(merged_state) = destination_merge_kind.merge(source_merge_kind, clock)? {
783            set_stake_state(destination_stake_account_info, &merged_state)?;
784        }
785
786        // Source is about to be drained, deinitialize its state
787        set_stake_state(source_stake_account_info, &StakeStateV2::Uninitialized)?;
788
789        // Drain the source stake account
790        relocate_lamports(
791            source_stake_account_info,
792            destination_stake_account_info,
793            source_stake_account_info.lamports(),
794        )?;
795
796        Ok(())
797    }
798
799    fn process_authorize_with_seed(
800        accounts: &[AccountInfo],
801        authorize_args: AuthorizeWithSeedArgs,
802    ) -> ProgramResult {
803        let account_info_iter = &mut accounts.iter();
804
805        // native asserts: 3 accounts (1 sysvar)
806        let stake_account_info = next_account_info(account_info_iter)?;
807        let stake_or_withdraw_authority_base_info = next_account_info(account_info_iter)?;
808        let clock_info = next_account_info(account_info_iter)?;
809
810        // other accounts
811        let option_lockup_authority_info = next_account_info(account_info_iter).ok();
812
813        let clock = &Clock::from_account_info(clock_info)?;
814
815        let (mut signers, custodian) = collect_signers_checked(None, option_lockup_authority_info)?;
816
817        if stake_or_withdraw_authority_base_info.is_signer {
818            signers.insert(Pubkey::create_with_seed(
819                stake_or_withdraw_authority_base_info.key,
820                &authorize_args.authority_seed,
821                &authorize_args.authority_owner,
822            )?);
823        }
824
825        // `get_stake_state()` is called unconditionally, which checks owner
826        do_authorize(
827            stake_account_info,
828            &signers,
829            &authorize_args.new_authorized_pubkey,
830            authorize_args.stake_authorize,
831            custodian,
832            clock,
833        )?;
834
835        Ok(())
836    }
837
838    fn process_initialize_checked(accounts: &[AccountInfo]) -> ProgramResult {
839        let account_info_iter = &mut accounts.iter();
840
841        // native asserts: 4 accounts (1 sysvar)
842        let stake_account_info = next_account_info(account_info_iter)?;
843        let rent_info = next_account_info(account_info_iter)?;
844        let stake_authority_info = next_account_info(account_info_iter)?;
845        let withdraw_authority_info = next_account_info(account_info_iter)?;
846
847        let rent = &Rent::from_account_info(rent_info)?;
848
849        if !withdraw_authority_info.is_signer {
850            return Err(ProgramError::MissingRequiredSignature);
851        }
852
853        let authorized = Authorized {
854            staker: *stake_authority_info.key,
855            withdrawer: *withdraw_authority_info.key,
856        };
857
858        // `get_stake_state()` is called unconditionally, which checks owner
859        do_initialize(stake_account_info, authorized, Lockup::default(), rent)?;
860
861        Ok(())
862    }
863
864    fn process_authorize_checked(
865        accounts: &[AccountInfo],
866        authority_type: StakeAuthorize,
867    ) -> ProgramResult {
868        let signers = collect_signers(accounts);
869        let account_info_iter = &mut accounts.iter();
870
871        // native asserts: 4 accounts (1 sysvar)
872        let stake_account_info = next_account_info(account_info_iter)?;
873        let clock_info = next_account_info(account_info_iter)?;
874        let _old_stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
875        let new_stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
876
877        // other accounts
878        let option_lockup_authority_info = next_account_info(account_info_iter).ok();
879
880        let clock = &Clock::from_account_info(clock_info)?;
881
882        if !new_stake_or_withdraw_authority_info.is_signer {
883            return Err(ProgramError::MissingRequiredSignature);
884        }
885
886        let custodian = option_lockup_authority_info
887            .filter(|a| a.is_signer)
888            .map(|a| a.key);
889
890        // `get_stake_state()` is called unconditionally, which checks owner
891        do_authorize(
892            stake_account_info,
893            &signers,
894            new_stake_or_withdraw_authority_info.key,
895            authority_type,
896            custodian,
897            clock,
898        )?;
899
900        Ok(())
901    }
902
903    fn process_authorize_checked_with_seed(
904        accounts: &[AccountInfo],
905        authorize_args: AuthorizeCheckedWithSeedArgs,
906    ) -> ProgramResult {
907        let account_info_iter = &mut accounts.iter();
908
909        // native asserts: 4 accounts (1 sysvar)
910        let stake_account_info = next_account_info(account_info_iter)?;
911        let old_stake_or_withdraw_authority_base_info = next_account_info(account_info_iter)?;
912        let clock_info = next_account_info(account_info_iter)?;
913        let new_stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
914
915        // other accounts
916        let option_lockup_authority_info = next_account_info(account_info_iter).ok();
917
918        let clock = &Clock::from_account_info(clock_info)?;
919
920        let (mut signers, custodian) = collect_signers_checked(
921            Some(new_stake_or_withdraw_authority_info),
922            option_lockup_authority_info,
923        )?;
924
925        if old_stake_or_withdraw_authority_base_info.is_signer {
926            signers.insert(Pubkey::create_with_seed(
927                old_stake_or_withdraw_authority_base_info.key,
928                &authorize_args.authority_seed,
929                &authorize_args.authority_owner,
930            )?);
931        }
932
933        // `get_stake_state()` is called unconditionally, which checks owner
934        do_authorize(
935            stake_account_info,
936            &signers,
937            new_stake_or_withdraw_authority_info.key,
938            authorize_args.stake_authorize,
939            custodian,
940            clock,
941        )?;
942
943        Ok(())
944    }
945
946    fn process_set_lockup_checked(
947        accounts: &[AccountInfo],
948        lockup_checked: LockupCheckedArgs,
949    ) -> ProgramResult {
950        let signers = collect_signers(accounts);
951        let account_info_iter = &mut accounts.iter();
952
953        // native asserts: 1 account
954        let stake_account_info = next_account_info(account_info_iter)?;
955
956        // other accounts
957        let _old_withdraw_or_lockup_authority_info = next_account_info(account_info_iter);
958        let option_new_lockup_authority_info = next_account_info(account_info_iter).ok();
959
960        let clock = Clock::get()?;
961
962        let custodian = match option_new_lockup_authority_info {
963            Some(new_lockup_authority_info) if new_lockup_authority_info.is_signer => {
964                Some(new_lockup_authority_info.key)
965            }
966            Some(_) => return Err(ProgramError::MissingRequiredSignature),
967            None => None,
968        };
969
970        let lockup = LockupArgs {
971            unix_timestamp: lockup_checked.unix_timestamp,
972            epoch: lockup_checked.epoch,
973            custodian: custodian.copied(),
974        };
975
976        // `get_stake_state()` is called unconditionally, which checks owner
977        do_set_lockup(stake_account_info, &signers, &lockup, &clock)?;
978
979        Ok(())
980    }
981
982    fn process_deactivate_delinquent(accounts: &[AccountInfo]) -> ProgramResult {
983        let account_info_iter = &mut accounts.iter();
984
985        // native asserts: 3 accounts
986        let stake_account_info = next_account_info(account_info_iter)?;
987        let delinquent_vote_account_info = next_account_info(account_info_iter)?;
988        let reference_vote_account_info = next_account_info(account_info_iter)?;
989
990        let clock = Clock::get()?;
991
992        let delinquent_vote_state = get_vote_state(delinquent_vote_account_info)?;
993        let reference_vote_state = get_vote_state(reference_vote_account_info)?;
994
995        if !acceptable_reference_epoch_credits(&reference_vote_state.epoch_credits, clock.epoch) {
996            return Err(StakeError::InsufficientReferenceVotes.into());
997        }
998
999        if let StakeStateV2::Stake(meta, mut stake, stake_flags) =
1000            get_stake_state(stake_account_info)?
1001        {
1002            if stake.delegation.voter_pubkey != *delinquent_vote_account_info.key {
1003                return Err(StakeError::VoteAddressMismatch.into());
1004            }
1005
1006            // Deactivate the stake account if its delegated vote account has never voted or
1007            // has not voted in the last
1008            // `MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION`
1009            if eligible_for_deactivate_delinquent(&delinquent_vote_state.epoch_credits, clock.epoch)
1010            {
1011                stake.deactivate(clock.epoch)?;
1012
1013                set_stake_state(
1014                    stake_account_info,
1015                    &StakeStateV2::Stake(meta, stake, stake_flags),
1016                )
1017            } else {
1018                Err(StakeError::MinimumDelinquentEpochsForDeactivationNotMet.into())
1019            }
1020        } else {
1021            Err(ProgramError::InvalidAccountData)
1022        }?;
1023
1024        Ok(())
1025    }
1026
1027    fn process_move_stake(accounts: &[AccountInfo], lamports: u64) -> ProgramResult {
1028        let account_info_iter = &mut accounts.iter();
1029
1030        // native asserts: 3 accounts
1031        let source_stake_account_info = next_account_info(account_info_iter)?;
1032        let destination_stake_account_info = next_account_info(account_info_iter)?;
1033        let stake_authority_info = next_account_info(account_info_iter)?;
1034
1035        let (source_merge_kind, destination_merge_kind) = move_stake_or_lamports_shared_checks(
1036            source_stake_account_info,
1037            lamports,
1038            destination_stake_account_info,
1039            stake_authority_info,
1040        )?;
1041
1042        // ensure source and destination are the right size for the current version of
1043        // StakeState this a safeguard in case there is a new version of the
1044        // struct that cannot fit into an old account
1045        if source_stake_account_info.data_len() != StakeStateV2::size_of()
1046            || destination_stake_account_info.data_len() != StakeStateV2::size_of()
1047        {
1048            return Err(ProgramError::InvalidAccountData);
1049        }
1050
1051        // source must be fully active
1052        let MergeKind::FullyActive(source_meta, mut source_stake) = source_merge_kind else {
1053            return Err(ProgramError::InvalidAccountData);
1054        };
1055
1056        let minimum_delegation = crate::get_minimum_delegation();
1057        let source_effective_stake = source_stake.delegation.stake;
1058
1059        // source cannot move more stake than it has, regardless of how many lamports it
1060        // has
1061        let source_final_stake = source_effective_stake
1062            .checked_sub(lamports)
1063            .ok_or(ProgramError::InvalidArgument)?;
1064
1065        // unless all stake is being moved, source must retain at least the minimum
1066        // delegation
1067        if source_final_stake != 0 && source_final_stake < minimum_delegation {
1068            return Err(ProgramError::InvalidArgument);
1069        }
1070
1071        // destination must be fully active or fully inactive
1072        let destination_meta = match destination_merge_kind {
1073            MergeKind::FullyActive(destination_meta, mut destination_stake) => {
1074                // if active, destination must be delegated to the same vote account as source
1075                if source_stake.delegation.voter_pubkey != destination_stake.delegation.voter_pubkey
1076                {
1077                    return Err(StakeError::VoteAddressMismatch.into());
1078                }
1079
1080                let destination_effective_stake = destination_stake.delegation.stake;
1081                let destination_final_stake = destination_effective_stake
1082                    .checked_add(lamports)
1083                    .ok_or(ProgramError::ArithmeticOverflow)?;
1084
1085                // ensure destination meets miniumum delegation
1086                // since it is already active, this only really applies if the minimum is raised
1087                if destination_final_stake < minimum_delegation {
1088                    return Err(ProgramError::InvalidArgument);
1089                }
1090
1091                merge_delegation_stake_and_credits_observed(
1092                    &mut destination_stake,
1093                    lamports,
1094                    source_stake.credits_observed,
1095                )?;
1096
1097                // StakeFlags::empty() is valid here because the only existing stake flag,
1098                // MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED, does not apply to
1099                // active stakes
1100                set_stake_state(
1101                    destination_stake_account_info,
1102                    &StakeStateV2::Stake(destination_meta, destination_stake, StakeFlags::empty()),
1103                )?;
1104
1105                destination_meta
1106            }
1107            MergeKind::Inactive(destination_meta, _, _) => {
1108                // if destination is inactive, it must be given at least the minimum delegation
1109                if lamports < minimum_delegation {
1110                    return Err(ProgramError::InvalidArgument);
1111                }
1112
1113                let mut destination_stake = source_stake;
1114                destination_stake.delegation.stake = lamports;
1115
1116                // StakeFlags::empty() is valid here because the only existing stake flag,
1117                // MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED, is cleared when a stake
1118                // is activated
1119                set_stake_state(
1120                    destination_stake_account_info,
1121                    &StakeStateV2::Stake(destination_meta, destination_stake, StakeFlags::empty()),
1122                )?;
1123
1124                destination_meta
1125            }
1126            _ => return Err(ProgramError::InvalidAccountData),
1127        };
1128
1129        if source_final_stake == 0 {
1130            set_stake_state(
1131                source_stake_account_info,
1132                &StakeStateV2::Initialized(source_meta),
1133            )?;
1134        } else {
1135            source_stake.delegation.stake = source_final_stake;
1136
1137            // StakeFlags::empty() is valid here because the only existing stake flag,
1138            // MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED, does not apply to
1139            // active stakes
1140            set_stake_state(
1141                source_stake_account_info,
1142                &StakeStateV2::Stake(source_meta, source_stake, StakeFlags::empty()),
1143            )?;
1144        }
1145
1146        relocate_lamports(
1147            source_stake_account_info,
1148            destination_stake_account_info,
1149            lamports,
1150        )?;
1151
1152        // this should be impossible, but because we do all our math with delegations,
1153        // best to guard it
1154        if source_stake_account_info.lamports() < source_meta.rent_exempt_reserve
1155            || destination_stake_account_info.lamports() < destination_meta.rent_exempt_reserve
1156        {
1157            msg!("Delegation calculations violated lamport balance assumptions");
1158            return Err(ProgramError::InvalidArgument);
1159        }
1160
1161        Ok(())
1162    }
1163
1164    fn process_move_lamports(accounts: &[AccountInfo], lamports: u64) -> ProgramResult {
1165        let account_info_iter = &mut accounts.iter();
1166
1167        // native asserts: 3 accounts
1168        let source_stake_account_info = next_account_info(account_info_iter)?;
1169        let destination_stake_account_info = next_account_info(account_info_iter)?;
1170        let stake_authority_info = next_account_info(account_info_iter)?;
1171
1172        let (source_merge_kind, _) = move_stake_or_lamports_shared_checks(
1173            source_stake_account_info,
1174            lamports,
1175            destination_stake_account_info,
1176            stake_authority_info,
1177        )?;
1178
1179        let source_free_lamports = match source_merge_kind {
1180            MergeKind::FullyActive(source_meta, source_stake) => source_stake_account_info
1181                .lamports()
1182                .saturating_sub(source_stake.delegation.stake)
1183                .saturating_sub(source_meta.rent_exempt_reserve),
1184            MergeKind::Inactive(source_meta, source_lamports, _) => {
1185                source_lamports.saturating_sub(source_meta.rent_exempt_reserve)
1186            }
1187            _ => return Err(ProgramError::InvalidAccountData),
1188        };
1189
1190        if lamports > source_free_lamports {
1191            return Err(ProgramError::InvalidArgument);
1192        }
1193
1194        relocate_lamports(
1195            source_stake_account_info,
1196            destination_stake_account_info,
1197            lamports,
1198        )?;
1199
1200        Ok(())
1201    }
1202
1203    /// Processes [Instruction](enum.Instruction.html).
1204    pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
1205        // convenience so we can safely use id() everywhere
1206        if *program_id != id() {
1207            return Err(ProgramError::IncorrectProgramId);
1208        }
1209
1210        let epoch_rewards_active = EpochRewards::get()
1211            .map(|epoch_rewards| epoch_rewards.active)
1212            .unwrap_or(false);
1213
1214        let instruction =
1215            bincode::deserialize(data).map_err(|_| ProgramError::InvalidInstructionData)?;
1216
1217        if epoch_rewards_active && !matches!(instruction, StakeInstruction::GetMinimumDelegation) {
1218            return Err(StakeError::EpochRewardsActive.into());
1219        }
1220
1221        match instruction {
1222            StakeInstruction::Initialize(authorize, lockup) => {
1223                msg!("Instruction: Initialize");
1224                Self::process_initialize(accounts, authorize, lockup)
1225            }
1226            StakeInstruction::Authorize(new_authority, authority_type) => {
1227                msg!("Instruction: Authorize");
1228                Self::process_authorize(accounts, new_authority, authority_type)
1229            }
1230            StakeInstruction::DelegateStake => {
1231                msg!("Instruction: DelegateStake");
1232                Self::process_delegate(accounts)
1233            }
1234            StakeInstruction::Split(lamports) => {
1235                msg!("Instruction: Split");
1236                Self::process_split(accounts, lamports)
1237            }
1238            StakeInstruction::Withdraw(lamports) => {
1239                msg!("Instruction: Withdraw");
1240                Self::process_withdraw(accounts, lamports)
1241            }
1242            StakeInstruction::Deactivate => {
1243                msg!("Instruction: Deactivate");
1244                Self::process_deactivate(accounts)
1245            }
1246            StakeInstruction::SetLockup(lockup) => {
1247                msg!("Instruction: SetLockup");
1248                Self::process_set_lockup(accounts, lockup)
1249            }
1250            StakeInstruction::Merge => {
1251                msg!("Instruction: Merge");
1252                Self::process_merge(accounts)
1253            }
1254            StakeInstruction::AuthorizeWithSeed(args) => {
1255                msg!("Instruction: AuthorizeWithSeed");
1256                Self::process_authorize_with_seed(accounts, args)
1257            }
1258            StakeInstruction::InitializeChecked => {
1259                msg!("Instruction: InitializeChecked");
1260                Self::process_initialize_checked(accounts)
1261            }
1262            StakeInstruction::AuthorizeChecked(authority_type) => {
1263                msg!("Instruction: AuthorizeChecked");
1264                Self::process_authorize_checked(accounts, authority_type)
1265            }
1266            StakeInstruction::AuthorizeCheckedWithSeed(args) => {
1267                msg!("Instruction: AuthorizeCheckedWithSeed");
1268                Self::process_authorize_checked_with_seed(accounts, args)
1269            }
1270            StakeInstruction::SetLockupChecked(lockup_checked) => {
1271                msg!("Instruction: SetLockupChecked");
1272                Self::process_set_lockup_checked(accounts, lockup_checked)
1273            }
1274            StakeInstruction::GetMinimumDelegation => {
1275                msg!("Instruction: GetMinimumDelegation");
1276                let minimum_delegation = crate::get_minimum_delegation();
1277                set_return_data(&minimum_delegation.to_le_bytes());
1278                Ok(())
1279            }
1280            StakeInstruction::DeactivateDelinquent => {
1281                msg!("Instruction: DeactivateDelinquent");
1282                Self::process_deactivate_delinquent(accounts)
1283            }
1284            #[allow(deprecated)]
1285            StakeInstruction::Redelegate => Err(ProgramError::InvalidInstructionData),
1286            // NOTE we assume the program is going live after `move_stake_and_move_lamports_ixs` is
1287            // activated
1288            StakeInstruction::MoveStake(lamports) => {
1289                msg!("Instruction: MoveStake");
1290                Self::process_move_stake(accounts, lamports)
1291            }
1292            StakeInstruction::MoveLamports(lamports) => {
1293                msg!("Instruction: MoveLamports");
1294                Self::process_move_lamports(accounts, lamports)
1295            }
1296        }
1297    }
1298}