Skip to main content

solana_stake_program/
processor.rs

1use {
2    crate::{helpers::*, id, PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH, PSEUDO_RENT_EXEMPT_RESERVE},
3    solana_account_info::{next_account_info, AccountInfo},
4    solana_clock::Clock,
5    solana_cpi::set_return_data,
6    solana_msg::msg,
7    solana_program_error::{ProgramError, ProgramResult},
8    solana_pubkey::Pubkey,
9    solana_rent::Rent,
10    solana_stake_interface::{
11        error::StakeError,
12        instruction::{
13            AuthorizeCheckedWithSeedArgs, AuthorizeWithSeedArgs, LockupArgs, LockupCheckedArgs,
14            StakeInstruction,
15        },
16        stake_flags::StakeFlags,
17        state::{Authorized, Lockup, Meta, StakeAuthorize, StakeStateV2},
18        sysvar::stake_history::StakeHistorySysvar,
19        tools::{acceptable_reference_epoch_credits, eligible_for_deactivate_delinquent},
20    },
21    solana_sysvar::{epoch_rewards::EpochRewards, Sysvar},
22    solana_sysvar_id::SysvarId,
23    solana_vote_interface::{program as solana_vote_program, state::VoteStateV4},
24    std::{collections::HashSet, mem::MaybeUninit},
25};
26
27fn get_vote_state(vote_account_info: &AccountInfo) -> Result<Box<VoteStateV4>, ProgramError> {
28    if *vote_account_info.owner != solana_vote_program::id() {
29        return Err(ProgramError::IncorrectProgramId);
30    }
31
32    let mut vote_state = Box::new(MaybeUninit::uninit());
33    VoteStateV4::deserialize_into_uninit(
34        &vote_account_info.try_borrow_data()?,
35        vote_state.as_mut(),
36        vote_account_info.key,
37    )
38    .map_err(|_| ProgramError::InvalidAccountData)?;
39    let vote_state = unsafe { vote_state.assume_init() };
40
41    Ok(vote_state)
42}
43
44fn get_stake_state(stake_account_info: &AccountInfo) -> Result<StakeStateV2, ProgramError> {
45    if *stake_account_info.owner != id() {
46        return Err(ProgramError::InvalidAccountOwner);
47    }
48
49    stake_account_info
50        .deserialize_data()
51        .map_err(|_| ProgramError::InvalidAccountData)
52}
53
54fn set_stake_state(stake_account_info: &AccountInfo, new_state: &StakeStateV2) -> ProgramResult {
55    let serialized_size =
56        bincode::serialized_size(new_state).map_err(|_| ProgramError::InvalidAccountData)?;
57    if serialized_size > stake_account_info.data_len() as u64 {
58        return Err(ProgramError::AccountDataTooSmall);
59    }
60
61    bincode::serialize_into(&mut stake_account_info.data.borrow_mut()[..], new_state)
62        .map_err(|_| ProgramError::InvalidAccountData)
63}
64
65// dont call this "move" because we have an instruction MoveLamports
66fn relocate_lamports(
67    source_account_info: &AccountInfo,
68    destination_account_info: &AccountInfo,
69    lamports: u64,
70) -> ProgramResult {
71    {
72        let mut source_lamports = source_account_info.try_borrow_mut_lamports()?;
73        **source_lamports = source_lamports
74            .checked_sub(lamports)
75            .ok_or(ProgramError::InsufficientFunds)?;
76    }
77
78    {
79        let mut destination_lamports = destination_account_info.try_borrow_mut_lamports()?;
80        **destination_lamports = destination_lamports
81            .checked_add(lamports)
82            .ok_or(ProgramError::ArithmeticOverflow)?;
83    }
84
85    Ok(())
86}
87
88// almost all native stake program processors accumulate every account signer
89// they then defer all signer validation to functions on Meta or Authorized
90// this results in an instruction interface that is much looser than the one documented
91// to avoid breaking backwards compatibility, we do the same here
92// in the future, we may decide to tighten the interface and break badly formed transactions
93fn collect_signers(accounts: &[AccountInfo]) -> HashSet<Pubkey> {
94    let mut signers = HashSet::new();
95
96    for account in accounts {
97        if account.is_signer {
98            signers.insert(*account.key);
99        }
100    }
101
102    signers
103}
104
105// MoveStake, MoveLamports, Withdraw, and AuthorizeWithSeed assemble signers explicitly
106fn collect_signers_checked<'a>(
107    authority_info: Option<&'a AccountInfo>,
108    custodian_info: Option<&'a AccountInfo>,
109) -> Result<(HashSet<Pubkey>, Option<&'a Pubkey>), ProgramError> {
110    let mut signers = HashSet::new();
111
112    if let Some(authority_info) = authority_info {
113        if authority_info.is_signer {
114            signers.insert(*authority_info.key);
115        } else {
116            return Err(ProgramError::MissingRequiredSignature);
117        }
118    }
119
120    let custodian = if let Some(custodian_info) = custodian_info {
121        if custodian_info.is_signer {
122            signers.insert(*custodian_info.key);
123            Some(custodian_info.key)
124        } else {
125            return Err(ProgramError::MissingRequiredSignature);
126        }
127    } else {
128        None
129    };
130
131    Ok((signers, custodian))
132}
133
134fn do_initialize(
135    stake_account_info: &AccountInfo,
136    authorized: Authorized,
137    lockup: Lockup,
138) -> ProgramResult {
139    if stake_account_info.data_len() != StakeStateV2::size_of() {
140        return Err(ProgramError::InvalidAccountData);
141    }
142
143    if let StakeStateV2::Uninitialized = get_stake_state(stake_account_info)? {
144        let rent = Rent::get()?;
145        let rent_exempt_reserve = rent.minimum_balance(stake_account_info.data_len());
146        if stake_account_info.lamports() >= rent_exempt_reserve {
147            let stake_state = StakeStateV2::Initialized(Meta {
148                authorized,
149                lockup,
150                rent_exempt_reserve: PSEUDO_RENT_EXEMPT_RESERVE,
151            });
152
153            set_stake_state(stake_account_info, &stake_state)
154        } else {
155            Err(ProgramError::InsufficientFunds)
156        }
157    } else {
158        Err(ProgramError::InvalidAccountData)
159    }
160}
161
162fn do_authorize(
163    stake_account_info: &AccountInfo,
164    signers: &HashSet<Pubkey>,
165    new_authority: &Pubkey,
166    authority_type: StakeAuthorize,
167    custodian: Option<&Pubkey>,
168) -> ProgramResult {
169    let clock = &Clock::get()?;
170
171    match get_stake_state(stake_account_info)? {
172        StakeStateV2::Initialized(mut meta) => {
173            meta.authorized
174                .authorize(
175                    signers,
176                    new_authority,
177                    authority_type,
178                    Some((&meta.lockup, clock, custodian)),
179                )
180                .map_err(to_program_error)?;
181
182            set_stake_state(stake_account_info, &StakeStateV2::Initialized(meta))
183        }
184        StakeStateV2::Stake(mut meta, stake, stake_flags) => {
185            meta.authorized
186                .authorize(
187                    signers,
188                    new_authority,
189                    authority_type,
190                    Some((&meta.lockup, clock, custodian)),
191                )
192                .map_err(to_program_error)?;
193
194            set_stake_state(
195                stake_account_info,
196                &StakeStateV2::Stake(meta, stake, stake_flags),
197            )
198        }
199        _ => Err(ProgramError::InvalidAccountData),
200    }
201}
202
203fn do_set_lockup(
204    stake_account_info: &AccountInfo,
205    signers: &HashSet<Pubkey>,
206    lockup: &LockupArgs,
207    clock: &Clock,
208) -> ProgramResult {
209    match get_stake_state(stake_account_info)? {
210        StakeStateV2::Initialized(mut meta) => {
211            meta.set_lockup(lockup, signers, clock)
212                .map_err(to_program_error)?;
213
214            set_stake_state(stake_account_info, &StakeStateV2::Initialized(meta))
215        }
216        StakeStateV2::Stake(mut meta, stake, stake_flags) => {
217            meta.set_lockup(lockup, signers, clock)
218                .map_err(to_program_error)?;
219
220            set_stake_state(
221                stake_account_info,
222                &StakeStateV2::Stake(meta, stake, stake_flags),
223            )
224        }
225        _ => Err(ProgramError::InvalidAccountData),
226    }
227}
228
229fn move_stake_or_lamports_shared_checks(
230    source_stake_account_info: &AccountInfo,
231    move_amount: u64,
232    destination_stake_account_info: &AccountInfo,
233    stake_authority_info: &AccountInfo,
234) -> Result<(MergeKind, MergeKind), ProgramError> {
235    // authority must sign
236    let (signers, _) = collect_signers_checked(Some(stake_authority_info), None)?;
237
238    // confirm not the same account
239    if *source_stake_account_info.key == *destination_stake_account_info.key {
240        return Err(ProgramError::InvalidInstructionData);
241    }
242
243    // source and destination must be writable
244    // runtime guards against unowned writes, but MoveStake and MoveLamports are defined by SIMD
245    // we check explicitly to avoid any possibility of a successful no-op that never attempts to write
246    if !source_stake_account_info.is_writable || !destination_stake_account_info.is_writable {
247        return Err(ProgramError::InvalidInstructionData);
248    }
249
250    // must move something
251    if move_amount == 0 {
252        return Err(ProgramError::InvalidArgument);
253    }
254
255    let clock = Clock::get()?;
256    let stake_history = StakeHistorySysvar(clock.epoch);
257
258    // get_if_mergeable ensures accounts are not partly activated or in any form of deactivating
259    // we still need to exclude activating state ourselves
260    let source_merge_kind = MergeKind::get_if_mergeable(
261        &get_stake_state(source_stake_account_info)?,
262        source_stake_account_info.lamports(),
263        &clock,
264        &stake_history,
265    )?;
266
267    // Authorized staker is allowed to move stake
268    source_merge_kind
269        .meta()
270        .authorized
271        .check(&signers, StakeAuthorize::Staker)
272        .map_err(to_program_error)?;
273
274    // same transient assurance as with source
275    let destination_merge_kind = MergeKind::get_if_mergeable(
276        &get_stake_state(destination_stake_account_info)?,
277        destination_stake_account_info.lamports(),
278        &clock,
279        &stake_history,
280    )?;
281
282    // ensure all authorities match and lockups match if lockup is in force
283    MergeKind::metas_can_merge(
284        source_merge_kind.meta(),
285        destination_merge_kind.meta(),
286        &clock,
287    )?;
288
289    Ok((source_merge_kind, destination_merge_kind))
290}
291
292// NOTE Our usage of the accounts iter is nonstandard, in imitation of the Native Stake Program.
293// Native Stake typically, but not always, accumulated signers from the accounts array indiscriminately.
294// This essentially allowed any account to act as a stake account signing authority.
295// Instruction processors also asserted a required number of instruction accounts, often fewer than the actual number.
296// When lengths were asserted in setup, accounts were retrieved via hardcoded index from `InstructionContext`,
297// but after control was passed to main processing functions, they were pulled from the `TransactionContext`.
298//
299// When porting to BPF, we reimplemented this behavior exactly, such that both programs would be consensus-compatible:
300// * All transactions that would fail on one program also fail on the other.
301// * All transactions that would succeed on one program also succeed on the other.
302// * For successful transactions, all account state transitions are identical.
303// Error codes and log output sometimes differed.
304//
305// Native Stake also accepted some sysvars as input accounts, but pulled others from `InvokeContext`.
306// This was done for backwards compatibility, but the end result was highly inconsistent.
307//
308// BPF Stake implements a new, stricter, interface, and supports both by branching when necessary.
309// This new interface asserts that authorities are present in expected positions, and that they are signers.
310// Self-signed stake accounts are still supported; the key simply must be passed in twice.
311// The new interface also requires no sysvar accounts, retrieving all sysvars by syscall.
312// Thus, we can fall back to the old interface if we encounter the first old-interface sysvar.
313// Each processor has its own special logic, but we annotate "invariant," "diverge," and "converge" to make the flow obvious.
314//
315// We do not modify `Split`, `SetLockup`, and `SetLockupChecked`, as it would be a breaking change.
316// These instructions never accepted sysvar accounts, so there is no way to distinguish "old" from "new."
317// However, we may make this change if we determine there are no legitimate mainnet users of the lax constraints.
318// Eventually, we may be able to remove the old interface and move to standard positional accounts for all instructions.
319//
320// New interface signer checks may duplicate later signer hashset checks. This is intended and harmless.
321// `ok()` account retrievals (lockup custodians) were, are, and will always be optional by design.
322pub struct Processor {}
323impl Processor {
324    fn process_initialize(
325        accounts: &[AccountInfo],
326        authorized: Authorized,
327        lockup: Lockup,
328    ) -> ProgramResult {
329        let account_info_iter = &mut accounts.iter();
330
331        // invariant
332        let stake_account_info = next_account_info(account_info_iter)?;
333
334        // `get_stake_state()` is called unconditionally, which checks owner
335        do_initialize(stake_account_info, authorized, lockup)?;
336
337        Ok(())
338    }
339
340    fn process_authorize(
341        accounts: &[AccountInfo],
342        new_authority: Pubkey,
343        authority_type: StakeAuthorize,
344    ) -> ProgramResult {
345        let signers = collect_signers(accounts);
346        let account_info_iter = &mut accounts.iter();
347
348        // invariant
349        let stake_account_info = next_account_info(account_info_iter)?;
350
351        // diverge
352        {
353            let branch_account = next_account_info(account_info_iter)?;
354            if Clock::check_id(branch_account.key) {
355                let _stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
356            } else {
357                let stake_or_withdraw_authority_info = branch_account;
358                if !stake_or_withdraw_authority_info.is_signer {
359                    return Err(ProgramError::MissingRequiredSignature);
360                }
361            }
362        }
363
364        // converge
365        let option_lockup_authority_info = next_account_info(account_info_iter).ok();
366
367        let custodian = option_lockup_authority_info
368            .filter(|a| a.is_signer)
369            .map(|a| a.key);
370
371        // `get_stake_state()` is called unconditionally, which checks owner
372        do_authorize(
373            stake_account_info,
374            &signers,
375            &new_authority,
376            authority_type,
377            custodian,
378        )?;
379
380        Ok(())
381    }
382
383    fn process_delegate(accounts: &[AccountInfo]) -> ProgramResult {
384        let signers = collect_signers(accounts);
385        let account_info_iter = &mut accounts.iter();
386
387        // invariant
388        let stake_account_info = next_account_info(account_info_iter)?;
389        let vote_account_info = next_account_info(account_info_iter)?;
390
391        // diverge
392        {
393            let branch_account = next_account_info(account_info_iter)?;
394            if Clock::check_id(branch_account.key) {
395                let _stake_history_info = next_account_info(account_info_iter)?;
396                let _stake_config_info = next_account_info(account_info_iter)?;
397                // let _stake_authority_info = next_account_info(account_info_iter);
398            } else {
399                let stake_authority_info = branch_account;
400                if !stake_authority_info.is_signer {
401                    return Err(ProgramError::MissingRequiredSignature);
402                }
403            }
404        };
405
406        let rent = &Rent::get()?;
407        let clock = &Clock::get()?;
408        let stake_history = &StakeHistorySysvar(clock.epoch);
409
410        let vote_state = get_vote_state(vote_account_info)?;
411
412        let rent_exempt_reserve = rent.minimum_balance(stake_account_info.data_len());
413
414        match get_stake_state(stake_account_info)? {
415            StakeStateV2::Initialized(meta) => {
416                meta.authorized
417                    .check(&signers, StakeAuthorize::Staker)
418                    .map_err(to_program_error)?;
419
420                let ValidatedDelegatedInfo { stake_amount } =
421                    validate_delegated_amount(stake_account_info, rent_exempt_reserve)?;
422
423                let stake = new_stake(
424                    stake_amount,
425                    vote_account_info.key,
426                    vote_state.credits(),
427                    clock.epoch,
428                );
429
430                set_stake_state(
431                    stake_account_info,
432                    &StakeStateV2::Stake(meta, stake, StakeFlags::empty()),
433                )
434            }
435            StakeStateV2::Stake(meta, mut stake, flags) => {
436                // Only the staker may (re)delegate
437                meta.authorized
438                    .check(&signers, StakeAuthorize::Staker)
439                    .map_err(to_program_error)?;
440
441                // Compute the maximum stake allowed to (re)delegate
442                let ValidatedDelegatedInfo { stake_amount } =
443                    validate_delegated_amount(stake_account_info, rent_exempt_reserve)?;
444
445                // Get current activation status at this epoch
446                let effective_stake = stake.delegation.stake(
447                    clock.epoch,
448                    stake_history,
449                    PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
450                );
451
452                if effective_stake == 0 {
453                    // The stake has no effective voting power this epoch. This means it is either:
454                    //   1. Inactive (fully cooled down after a previous deactivation)
455                    //   2. Still activating (was delegated for the first time this epoch)
456                    stake = new_stake(
457                        stake_amount,
458                        vote_account_info.key,
459                        vote_state.credits(),
460                        clock.epoch,
461                    );
462                } else if clock.epoch == stake.delegation.deactivation_epoch
463                    && stake.delegation.voter_pubkey == *vote_account_info.key
464                {
465                    if stake_amount < stake.delegation.stake {
466                        return Err(StakeError::InsufficientDelegation.into());
467                    }
468                    stake.delegation.deactivation_epoch = u64::MAX;
469                } else {
470                    // Not a valid state for redelegation
471                    return Err(StakeError::TooSoonToRedelegate.into());
472                }
473
474                // Persist the updated stake state back to the account.
475                set_stake_state(stake_account_info, &StakeStateV2::Stake(meta, stake, flags))
476            }
477            _ => Err(ProgramError::InvalidAccountData),
478        }?;
479
480        Ok(())
481    }
482
483    fn process_split(accounts: &[AccountInfo], split_lamports: u64) -> ProgramResult {
484        let signers = collect_signers(accounts);
485        let account_info_iter = &mut accounts.iter();
486
487        // native asserts: 2 accounts
488        let source_stake_account_info = next_account_info(account_info_iter)?;
489        let destination_stake_account_info = next_account_info(account_info_iter)?;
490
491        // NOTE we cannot check this account without a breaking change
492        // we may decide to enforce this if the pattern is not used on mainnet
493        // let _stake_authority_info = next_account_info(account_info_iter);
494
495        let rent = Rent::get()?;
496        let clock = Clock::get()?;
497        let stake_history = &StakeHistorySysvar(clock.epoch);
498        let minimum_delegation = crate::get_minimum_delegation();
499
500        if source_stake_account_info.key == destination_stake_account_info.key {
501            return Err(ProgramError::InvalidArgument);
502        }
503
504        if let StakeStateV2::Uninitialized = get_stake_state(destination_stake_account_info)? {
505            // we can split into this
506        } else {
507            return Err(ProgramError::InvalidAccountData);
508        }
509
510        let source_lamport_balance = source_stake_account_info.lamports();
511        let destination_lamport_balance = destination_stake_account_info.lamports();
512
513        if split_lamports > source_lamport_balance {
514            return Err(ProgramError::InsufficientFunds);
515        }
516
517        if split_lamports == 0 {
518            return Err(ProgramError::InsufficientFunds);
519        }
520
521        let source_rent_exempt_reserve = rent.minimum_balance(source_stake_account_info.data_len());
522
523        let destination_data_len = destination_stake_account_info.data_len();
524        if destination_data_len != StakeStateV2::size_of() {
525            return Err(ProgramError::InvalidAccountData);
526        }
527        let destination_rent_exempt_reserve = rent.minimum_balance(destination_data_len);
528
529        // check signers and get delegation status along with a destination meta
530        let source_stake_state = get_stake_state(source_stake_account_info)?;
531        let (is_active_or_activating, option_dest_meta) = match source_stake_state {
532            StakeStateV2::Stake(source_meta, source_stake, _) => {
533                source_meta
534                    .authorized
535                    .check(&signers, StakeAuthorize::Staker)
536                    .map_err(to_program_error)?;
537
538                let source_status = source_stake.delegation.stake_activating_and_deactivating(
539                    clock.epoch,
540                    stake_history,
541                    PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
542                );
543                let is_active_or_activating =
544                    source_status.effective > 0 || source_status.activating > 0;
545
546                let mut dest_meta = source_meta;
547                dest_meta.rent_exempt_reserve = PSEUDO_RENT_EXEMPT_RESERVE;
548
549                (is_active_or_activating, Some(dest_meta))
550            }
551            StakeStateV2::Initialized(source_meta) => {
552                source_meta
553                    .authorized
554                    .check(&signers, StakeAuthorize::Staker)
555                    .map_err(to_program_error)?;
556
557                let mut dest_meta = source_meta;
558                dest_meta.rent_exempt_reserve = PSEUDO_RENT_EXEMPT_RESERVE;
559
560                (false, Some(dest_meta))
561            }
562            StakeStateV2::Uninitialized => {
563                if !source_stake_account_info.is_signer {
564                    return Err(ProgramError::MissingRequiredSignature);
565                }
566
567                (false, None)
568            }
569            StakeStateV2::RewardsPool => return Err(ProgramError::InvalidAccountData),
570        };
571
572        // special case: for a full split, we only care that the destination becomes a valid stake account
573        // this prevents state changes in exceptional cases where a once-valid source has become invalid
574        // relocate lamports, copy data, and close the original account
575        if split_lamports == source_lamport_balance {
576            let mut destination_stake_state = source_stake_state;
577            let delegation = match (&mut destination_stake_state, option_dest_meta) {
578                (StakeStateV2::Stake(meta, stake, _), Some(dest_meta)) => {
579                    *meta = dest_meta;
580
581                    if is_active_or_activating {
582                        stake.delegation.stake
583                    } else {
584                        0
585                    }
586                }
587                (StakeStateV2::Initialized(meta), Some(dest_meta)) => {
588                    *meta = dest_meta;
589
590                    0
591                }
592                (StakeStateV2::Uninitialized, None) => 0,
593                _ => unreachable!(),
594            };
595
596            if destination_lamport_balance
597                .saturating_add(split_lamports)
598                .saturating_sub(delegation)
599                < destination_rent_exempt_reserve
600            {
601                return Err(ProgramError::InsufficientFunds);
602            }
603
604            if is_active_or_activating && delegation < minimum_delegation {
605                return Err(StakeError::InsufficientDelegation.into());
606            }
607
608            set_stake_state(destination_stake_account_info, &destination_stake_state)?;
609            source_stake_account_info.resize(0)?;
610
611            relocate_lamports(
612                source_stake_account_info,
613                destination_stake_account_info,
614                split_lamports,
615            )?;
616
617            return Ok(());
618        }
619
620        // special case: if stake is fully inactive, we only care that both accounts meet rent-exemption
621        if !is_active_or_activating {
622            let mut destination_stake_state = source_stake_state;
623            match (&mut destination_stake_state, option_dest_meta) {
624                (StakeStateV2::Stake(meta, _, _), Some(dest_meta))
625                | (StakeStateV2::Initialized(meta), Some(dest_meta)) => {
626                    *meta = dest_meta;
627                }
628                (StakeStateV2::Uninitialized, None) => (),
629                _ => unreachable!(),
630            }
631
632            let post_source_lamports = source_lamport_balance
633                .checked_sub(split_lamports)
634                .ok_or(ProgramError::InsufficientFunds)?;
635
636            let post_destination_lamports = destination_lamport_balance
637                .checked_add(split_lamports)
638                .ok_or(ProgramError::ArithmeticOverflow)?;
639
640            if post_source_lamports < source_rent_exempt_reserve
641                || post_destination_lamports < destination_rent_exempt_reserve
642            {
643                return Err(ProgramError::InsufficientFunds);
644            }
645
646            set_stake_state(destination_stake_account_info, &destination_stake_state)?;
647
648            relocate_lamports(
649                source_stake_account_info,
650                destination_stake_account_info,
651                split_lamports,
652            )?;
653
654            return Ok(());
655        }
656
657        // at this point, we know we have a StakeStateV2::Stake source that is either activating or has nonzero effective
658        // this means we must redistribute the delegation across both accounts and enforce:
659        // * destination has a pre-funded rent exemption
660        // * source meets rent exemption less its remaining delegation
661        // * source and destination both meet the minimum delegation
662        // destination delegation is matched 1:1 by split lamports. in other words, free source lamports are never split
663        match (source_stake_state, option_dest_meta) {
664            (StakeStateV2::Stake(source_meta, mut source_stake, stake_flags), Some(dest_meta)) => {
665                if destination_lamport_balance < destination_rent_exempt_reserve {
666                    return Err(ProgramError::InsufficientFunds);
667                }
668
669                let mut dest_stake = source_stake;
670
671                source_stake.delegation.stake = source_stake
672                    .delegation
673                    .stake
674                    .checked_sub(split_lamports)
675                    .ok_or::<ProgramError>(StakeError::InsufficientDelegation.into())?;
676
677                if source_stake.delegation.stake < minimum_delegation {
678                    return Err(StakeError::InsufficientDelegation.into());
679                }
680
681                // sanity check on prior math; this branch is unreachable
682                // minimum delegation is by definition nonzero, and we remove one delegated lamport per split lamport
683                // since the remaining source delegation > 0, it is impossible that we took from its rent-exempt reserve
684                if source_lamport_balance
685                    .saturating_sub(split_lamports)
686                    .saturating_sub(source_stake.delegation.stake)
687                    < source_rent_exempt_reserve
688                {
689                    return Err(ProgramError::InsufficientFunds);
690                }
691
692                dest_stake.delegation.stake = split_lamports;
693                if dest_stake.delegation.stake < minimum_delegation {
694                    return Err(StakeError::InsufficientDelegation.into());
695                }
696
697                set_stake_state(
698                    source_stake_account_info,
699                    &StakeStateV2::Stake(source_meta, source_stake, stake_flags),
700                )?;
701
702                set_stake_state(
703                    destination_stake_account_info,
704                    &StakeStateV2::Stake(dest_meta, dest_stake, stake_flags),
705                )?;
706
707                relocate_lamports(
708                    source_stake_account_info,
709                    destination_stake_account_info,
710                    split_lamports,
711                )?;
712            }
713            _ => unreachable!(),
714        }
715
716        Ok(())
717    }
718
719    fn process_withdraw(accounts: &[AccountInfo], withdraw_lamports: u64) -> ProgramResult {
720        let account_info_iter = &mut accounts.iter();
721
722        // invariant
723        let source_stake_account_info = next_account_info(account_info_iter)?;
724        let destination_info = next_account_info(account_info_iter)?;
725
726        // diverge
727        let withdraw_authority_info = {
728            let branch_account = next_account_info(account_info_iter)?;
729            if Clock::check_id(branch_account.key) {
730                let _stake_history_info = next_account_info(account_info_iter)?;
731                next_account_info(account_info_iter)?
732            } else {
733                let withdraw_authority_info = branch_account;
734                if !withdraw_authority_info.is_signer {
735                    return Err(ProgramError::MissingRequiredSignature);
736                }
737                withdraw_authority_info
738            }
739        };
740
741        // converge
742        let option_lockup_authority_info = next_account_info(account_info_iter).ok();
743
744        let rent = &Rent::get()?;
745        let clock = &Clock::get()?;
746        let stake_history = &StakeHistorySysvar(clock.epoch);
747
748        if source_stake_account_info.key == destination_info.key {
749            return Err(ProgramError::InvalidArgument);
750        }
751
752        let source_rent_exempt_reserve = rent.minimum_balance(source_stake_account_info.data_len());
753
754        // this is somewhat subtle. for Initialized and Stake, there is a real authority
755        // but for Uninitialized, the source account is passed twice, and signed for
756        let (signers, custodian) =
757            collect_signers_checked(Some(withdraw_authority_info), option_lockup_authority_info)?;
758
759        let (lockup, reserve, is_staked) = match get_stake_state(source_stake_account_info) {
760            Ok(StakeStateV2::Stake(meta, stake, _stake_flag)) => {
761                meta.authorized
762                    .check(&signers, StakeAuthorize::Withdrawer)
763                    .map_err(to_program_error)?;
764                // if we have a deactivation epoch and we're in cooldown
765                let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
766                    stake.delegation.stake(
767                        clock.epoch,
768                        stake_history,
769                        PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
770                    )
771                } else {
772                    // Assume full stake if the stake account hasn't been
773                    //  de-activated, because in the future the exposed stake
774                    //  might be higher than stake.stake() due to warmup
775                    stake.delegation.stake
776                };
777
778                let staked_and_reserve = checked_add(staked, source_rent_exempt_reserve)?;
779                (meta.lockup, staked_and_reserve, staked != 0)
780            }
781            Ok(StakeStateV2::Initialized(meta)) => {
782                meta.authorized
783                    .check(&signers, StakeAuthorize::Withdrawer)
784                    .map_err(to_program_error)?;
785                // stake accounts must have a balance >= rent_exempt_reserve
786                (meta.lockup, source_rent_exempt_reserve, false)
787            }
788            Ok(StakeStateV2::Uninitialized) => {
789                if !signers.contains(source_stake_account_info.key) {
790                    return Err(ProgramError::MissingRequiredSignature);
791                }
792                (Lockup::default(), 0, false) // no lockup, no restrictions
793            }
794            Err(e)
795                if e == ProgramError::InvalidAccountData
796                    && source_stake_account_info.data_len() == 0 =>
797            {
798                if !signers.contains(source_stake_account_info.key) {
799                    return Err(ProgramError::MissingRequiredSignature);
800                }
801                (Lockup::default(), 0, false) // no lockup, no restrictions
802            }
803            Ok(StakeStateV2::RewardsPool) => return Err(ProgramError::InvalidAccountData),
804            Err(e) => return Err(e),
805        };
806
807        // verify that lockup has expired or that the withdrawal is signed by the
808        // custodian both epoch and unix_timestamp must have passed
809        if lockup.is_in_force(clock, custodian) {
810            return Err(StakeError::LockupInForce.into());
811        }
812
813        let stake_account_lamports = source_stake_account_info.lamports();
814        if withdraw_lamports == stake_account_lamports {
815            // if the stake is active, we mustn't allow the account to go away
816            if is_staked {
817                return Err(ProgramError::InsufficientFunds);
818            }
819
820            // Truncate state upon zero balance
821            source_stake_account_info.resize(0)?;
822        } else {
823            // a partial withdrawal must not deplete the reserve
824            let withdraw_lamports_and_reserve = checked_add(withdraw_lamports, reserve)?;
825            if withdraw_lamports_and_reserve > stake_account_lamports {
826                return Err(ProgramError::InsufficientFunds);
827            }
828        }
829
830        relocate_lamports(
831            source_stake_account_info,
832            destination_info,
833            withdraw_lamports,
834        )?;
835
836        Ok(())
837    }
838
839    fn process_deactivate(accounts: &[AccountInfo]) -> ProgramResult {
840        let signers = collect_signers(accounts);
841        let account_info_iter = &mut accounts.iter();
842
843        // invariant
844        let stake_account_info = next_account_info(account_info_iter)?;
845
846        // diverge
847        {
848            let branch_account = next_account_info(account_info_iter)?;
849            if Clock::check_id(branch_account.key) {
850                // let _stake_authority_info = next_account_info(account_info_iter);
851            } else {
852                let stake_authority_info = branch_account;
853                if !stake_authority_info.is_signer {
854                    return Err(ProgramError::MissingRequiredSignature);
855                }
856            }
857        }
858
859        let clock = &Clock::get()?;
860
861        match get_stake_state(stake_account_info)? {
862            StakeStateV2::Stake(meta, mut stake, stake_flags) => {
863                meta.authorized
864                    .check(&signers, StakeAuthorize::Staker)
865                    .map_err(to_program_error)?;
866
867                stake.deactivate(clock.epoch)?;
868
869                set_stake_state(
870                    stake_account_info,
871                    &StakeStateV2::Stake(meta, stake, stake_flags),
872                )
873            }
874            _ => Err(ProgramError::InvalidAccountData),
875        }?;
876
877        Ok(())
878    }
879
880    fn process_set_lockup(accounts: &[AccountInfo], lockup: LockupArgs) -> ProgramResult {
881        let signers = collect_signers(accounts);
882        let account_info_iter = &mut accounts.iter();
883
884        // native asserts: 1 account
885        let stake_account_info = next_account_info(account_info_iter)?;
886
887        // NOTE we cannot check this account without a breaking change
888        // we may decide to enforce this if the pattern is not used on mainnet
889        // let _old_withdraw_or_lockup_authority_info = next_account_info(account_info_iter);
890
891        let clock = Clock::get()?;
892
893        // `get_stake_state()` is called unconditionally, which checks owner
894        do_set_lockup(stake_account_info, &signers, &lockup, &clock)?;
895
896        Ok(())
897    }
898
899    fn process_merge(accounts: &[AccountInfo]) -> ProgramResult {
900        let signers = collect_signers(accounts);
901        let account_info_iter = &mut accounts.iter();
902
903        // invariant
904        let destination_stake_account_info = next_account_info(account_info_iter)?;
905        let source_stake_account_info = next_account_info(account_info_iter)?;
906
907        // diverge
908        {
909            let branch_account = next_account_info(account_info_iter)?;
910            if Clock::check_id(branch_account.key) {
911                let _stake_history_info = next_account_info(account_info_iter)?;
912                // let _stake_authority_info = next_account_info(account_info_iter);
913            } else {
914                let stake_authority_info = branch_account;
915                if !stake_authority_info.is_signer {
916                    return Err(ProgramError::MissingRequiredSignature);
917                }
918            }
919        }
920
921        let clock = &Clock::get()?;
922        let stake_history = &StakeHistorySysvar(clock.epoch);
923
924        if source_stake_account_info.key == destination_stake_account_info.key {
925            return Err(ProgramError::InvalidArgument);
926        }
927
928        let source_lamports = source_stake_account_info.lamports();
929
930        msg!("Checking if destination stake is mergeable");
931        let destination_merge_kind = MergeKind::get_if_mergeable(
932            &get_stake_state(destination_stake_account_info)?,
933            destination_stake_account_info.lamports(),
934            clock,
935            stake_history,
936        )?;
937
938        // Authorized staker is allowed to split/merge accounts
939        destination_merge_kind
940            .meta()
941            .authorized
942            .check(&signers, StakeAuthorize::Staker)
943            .map_err(|_| ProgramError::MissingRequiredSignature)?;
944
945        msg!("Checking if source stake is mergeable");
946        let source_merge_kind = MergeKind::get_if_mergeable(
947            &get_stake_state(source_stake_account_info)?,
948            source_lamports,
949            clock,
950            stake_history,
951        )?;
952
953        msg!("Merging stake accounts");
954        if let Some(merged_state) = destination_merge_kind.merge(source_merge_kind, clock)? {
955            set_stake_state(destination_stake_account_info, &merged_state)?;
956        }
957
958        // Source is about to be drained, truncate its state
959        source_stake_account_info.resize(0)?;
960
961        // Drain the source stake account
962        relocate_lamports(
963            source_stake_account_info,
964            destination_stake_account_info,
965            source_lamports,
966        )?;
967
968        Ok(())
969    }
970
971    fn process_authorize_with_seed(
972        accounts: &[AccountInfo],
973        authorize_args: AuthorizeWithSeedArgs,
974    ) -> ProgramResult {
975        let account_info_iter = &mut accounts.iter();
976
977        // invariant
978        let stake_account_info = next_account_info(account_info_iter)?;
979        let stake_or_withdraw_authority_base_info = next_account_info(account_info_iter)?;
980
981        // diverge
982        let option_lockup_authority_info = {
983            let branch_account = next_account_info(account_info_iter).ok();
984            if branch_account.is_some_and(|account| Clock::check_id(account.key)) {
985                next_account_info(account_info_iter).ok()
986            } else {
987                branch_account
988            }
989        };
990
991        let (mut signers, custodian) = collect_signers_checked(None, option_lockup_authority_info)?;
992
993        if stake_or_withdraw_authority_base_info.is_signer {
994            signers.insert(Pubkey::create_with_seed(
995                stake_or_withdraw_authority_base_info.key,
996                &authorize_args.authority_seed,
997                &authorize_args.authority_owner,
998            )?);
999        }
1000
1001        // `get_stake_state()` is called unconditionally, which checks owner
1002        do_authorize(
1003            stake_account_info,
1004            &signers,
1005            &authorize_args.new_authorized_pubkey,
1006            authorize_args.stake_authorize,
1007            custodian,
1008        )?;
1009
1010        Ok(())
1011    }
1012
1013    fn process_initialize_checked(accounts: &[AccountInfo]) -> ProgramResult {
1014        let account_info_iter = &mut accounts.iter();
1015
1016        // invariant
1017        let stake_account_info = next_account_info(account_info_iter)?;
1018
1019        // diverge
1020        let stake_authority_info = {
1021            let branch_account = next_account_info(account_info_iter)?;
1022            if Rent::check_id(branch_account.key) {
1023                next_account_info(account_info_iter)?
1024            } else {
1025                // we do not need to check this, withdraw_authority is the only signer
1026                branch_account
1027            }
1028        };
1029
1030        // converge
1031        let withdraw_authority_info = next_account_info(account_info_iter)?;
1032
1033        if !withdraw_authority_info.is_signer {
1034            return Err(ProgramError::MissingRequiredSignature);
1035        }
1036
1037        let authorized = Authorized {
1038            staker: *stake_authority_info.key,
1039            withdrawer: *withdraw_authority_info.key,
1040        };
1041
1042        // `get_stake_state()` is called unconditionally, which checks owner
1043        do_initialize(stake_account_info, authorized, Lockup::default())?;
1044
1045        Ok(())
1046    }
1047
1048    fn process_authorize_checked(
1049        accounts: &[AccountInfo],
1050        authority_type: StakeAuthorize,
1051    ) -> ProgramResult {
1052        let signers = collect_signers(accounts);
1053        let account_info_iter = &mut accounts.iter();
1054
1055        // invariant
1056        let stake_account_info = next_account_info(account_info_iter)?;
1057
1058        // diverge
1059        {
1060            let branch_account = next_account_info(account_info_iter)?;
1061            if Clock::check_id(branch_account.key) {
1062                let _old_stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
1063            } else {
1064                let old_stake_or_withdraw_authority_info = branch_account;
1065                if !old_stake_or_withdraw_authority_info.is_signer {
1066                    return Err(ProgramError::MissingRequiredSignature);
1067                }
1068            }
1069        }
1070
1071        // converge
1072        let new_stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
1073        let option_lockup_authority_info = next_account_info(account_info_iter).ok();
1074
1075        if !new_stake_or_withdraw_authority_info.is_signer {
1076            return Err(ProgramError::MissingRequiredSignature);
1077        }
1078
1079        let custodian = option_lockup_authority_info
1080            .filter(|a| a.is_signer)
1081            .map(|a| a.key);
1082
1083        // `get_stake_state()` is called unconditionally, which checks owner
1084        do_authorize(
1085            stake_account_info,
1086            &signers,
1087            new_stake_or_withdraw_authority_info.key,
1088            authority_type,
1089            custodian,
1090        )?;
1091
1092        Ok(())
1093    }
1094
1095    fn process_authorize_checked_with_seed(
1096        accounts: &[AccountInfo],
1097        authorize_args: AuthorizeCheckedWithSeedArgs,
1098    ) -> ProgramResult {
1099        let account_info_iter = &mut accounts.iter();
1100
1101        // invariant
1102        let stake_account_info = next_account_info(account_info_iter)?;
1103        let old_stake_or_withdraw_authority_base_info = next_account_info(account_info_iter)?;
1104
1105        // diverge
1106        let new_stake_or_withdraw_authority_info = {
1107            let branch_account = next_account_info(account_info_iter)?;
1108            if Clock::check_id(branch_account.key) {
1109                next_account_info(account_info_iter)?
1110            } else {
1111                let new_stake_or_withdraw_authority_info = branch_account;
1112                if !new_stake_or_withdraw_authority_info.is_signer {
1113                    return Err(ProgramError::MissingRequiredSignature);
1114                }
1115                new_stake_or_withdraw_authority_info
1116            }
1117        };
1118
1119        // converge
1120        let option_lockup_authority_info = next_account_info(account_info_iter).ok();
1121
1122        let (mut signers, custodian) = collect_signers_checked(
1123            Some(new_stake_or_withdraw_authority_info),
1124            option_lockup_authority_info,
1125        )?;
1126
1127        if old_stake_or_withdraw_authority_base_info.is_signer {
1128            signers.insert(Pubkey::create_with_seed(
1129                old_stake_or_withdraw_authority_base_info.key,
1130                &authorize_args.authority_seed,
1131                &authorize_args.authority_owner,
1132            )?);
1133        }
1134
1135        // `get_stake_state()` is called unconditionally, which checks owner
1136        do_authorize(
1137            stake_account_info,
1138            &signers,
1139            new_stake_or_withdraw_authority_info.key,
1140            authorize_args.stake_authorize,
1141            custodian,
1142        )?;
1143
1144        Ok(())
1145    }
1146
1147    fn process_set_lockup_checked(
1148        accounts: &[AccountInfo],
1149        lockup_checked: LockupCheckedArgs,
1150    ) -> ProgramResult {
1151        let signers = collect_signers(accounts);
1152        let account_info_iter = &mut accounts.iter();
1153
1154        // native asserts: 1 account
1155        let stake_account_info = next_account_info(account_info_iter)?;
1156
1157        // NOTE we cannot check this account without a breaking change
1158        // we may decide to enforce this if the pattern is not used on mainnet
1159        let _old_withdraw_or_lockup_authority_info = next_account_info(account_info_iter);
1160        let option_new_lockup_authority_info = next_account_info(account_info_iter).ok();
1161
1162        let clock = Clock::get()?;
1163
1164        let custodian = match option_new_lockup_authority_info {
1165            Some(new_lockup_authority_info) if new_lockup_authority_info.is_signer => {
1166                Some(new_lockup_authority_info.key)
1167            }
1168            Some(_) => return Err(ProgramError::MissingRequiredSignature),
1169            None => None,
1170        };
1171
1172        let lockup = LockupArgs {
1173            unix_timestamp: lockup_checked.unix_timestamp,
1174            epoch: lockup_checked.epoch,
1175            custodian: custodian.copied(),
1176        };
1177
1178        // `get_stake_state()` is called unconditionally, which checks owner
1179        do_set_lockup(stake_account_info, &signers, &lockup, &clock)?;
1180
1181        Ok(())
1182    }
1183
1184    fn process_deactivate_delinquent(accounts: &[AccountInfo]) -> ProgramResult {
1185        let account_info_iter = &mut accounts.iter();
1186
1187        // invariant
1188        let stake_account_info = next_account_info(account_info_iter)?;
1189        let delinquent_vote_account_info = next_account_info(account_info_iter)?;
1190        let reference_vote_account_info = next_account_info(account_info_iter)?;
1191
1192        let clock = Clock::get()?;
1193
1194        let delinquent_vote_state = get_vote_state(delinquent_vote_account_info)?;
1195        let reference_vote_state = get_vote_state(reference_vote_account_info)?;
1196
1197        if !acceptable_reference_epoch_credits(&reference_vote_state.epoch_credits, clock.epoch) {
1198            return Err(StakeError::InsufficientReferenceVotes.into());
1199        }
1200
1201        if let StakeStateV2::Stake(meta, mut stake, stake_flags) =
1202            get_stake_state(stake_account_info)?
1203        {
1204            if stake.delegation.voter_pubkey != *delinquent_vote_account_info.key {
1205                return Err(StakeError::VoteAddressMismatch.into());
1206            }
1207
1208            // Deactivate the stake account if its delegated vote account has never voted or
1209            // has not voted in the last
1210            // `MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION`
1211            if eligible_for_deactivate_delinquent(&delinquent_vote_state.epoch_credits, clock.epoch)
1212            {
1213                stake.deactivate(clock.epoch)?;
1214
1215                set_stake_state(
1216                    stake_account_info,
1217                    &StakeStateV2::Stake(meta, stake, stake_flags),
1218                )
1219            } else {
1220                Err(StakeError::MinimumDelinquentEpochsForDeactivationNotMet.into())
1221            }
1222        } else {
1223            Err(ProgramError::InvalidAccountData)
1224        }?;
1225
1226        Ok(())
1227    }
1228
1229    fn process_move_stake(accounts: &[AccountInfo], move_amount: u64) -> ProgramResult {
1230        let account_info_iter = &mut accounts.iter();
1231
1232        // invariant
1233        let source_stake_account_info = next_account_info(account_info_iter)?;
1234        let destination_stake_account_info = next_account_info(account_info_iter)?;
1235        let stake_authority_info = next_account_info(account_info_iter)?;
1236
1237        let rent = &Rent::get()?;
1238
1239        let (source_merge_kind, destination_merge_kind) = move_stake_or_lamports_shared_checks(
1240            source_stake_account_info,
1241            move_amount,
1242            destination_stake_account_info,
1243            stake_authority_info,
1244        )?;
1245
1246        let source_rent_exempt_reserve = rent.minimum_balance(source_stake_account_info.data_len());
1247
1248        let destination_rent_exempt_reserve =
1249            rent.minimum_balance(destination_stake_account_info.data_len());
1250
1251        // ensure source and destination are the right size for the current version of StakeState.
1252        // this a safeguard in case there is a new version of the struct that cannot fit into an old account
1253        if source_stake_account_info.data_len() != StakeStateV2::size_of()
1254            || destination_stake_account_info.data_len() != StakeStateV2::size_of()
1255        {
1256            return Err(ProgramError::InvalidAccountData);
1257        }
1258
1259        // source must be fully active
1260        let MergeKind::FullyActive(source_meta, mut source_stake) = source_merge_kind else {
1261            return Err(ProgramError::InvalidAccountData);
1262        };
1263
1264        let minimum_delegation = crate::get_minimum_delegation();
1265        let source_effective_stake = source_stake.delegation.stake;
1266
1267        // source cannot move more stake than it has, regardless of how many lamports it has
1268        let source_final_stake = source_effective_stake
1269            .checked_sub(move_amount)
1270            .ok_or(ProgramError::InvalidArgument)?;
1271
1272        // unless all stake is being moved, source must retain at least the minimum delegation
1273        if source_final_stake != 0 && source_final_stake < minimum_delegation {
1274            return Err(ProgramError::InvalidArgument);
1275        }
1276
1277        // destination must be fully active or fully inactive
1278        match destination_merge_kind {
1279            MergeKind::FullyActive(destination_meta, mut destination_stake) => {
1280                // if active, destination must be delegated to the same vote account as source
1281                if source_stake.delegation.voter_pubkey != destination_stake.delegation.voter_pubkey
1282                {
1283                    return Err(StakeError::VoteAddressMismatch.into());
1284                }
1285
1286                let destination_effective_stake = destination_stake.delegation.stake;
1287                let destination_final_stake = destination_effective_stake
1288                    .checked_add(move_amount)
1289                    .ok_or(ProgramError::ArithmeticOverflow)?;
1290
1291                // ensure destination meets miniumum delegation.
1292                // since it is already active, this only really applies if the minimum is raised
1293                if destination_final_stake < minimum_delegation {
1294                    return Err(ProgramError::InvalidArgument);
1295                }
1296
1297                merge_delegation_stake_and_credits_observed(
1298                    &mut destination_stake,
1299                    move_amount,
1300                    source_stake.credits_observed,
1301                )?;
1302
1303                // StakeFlags::empty() is valid here because the only existing stake flag,
1304                // MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED, does not apply to
1305                // active stakes
1306                set_stake_state(
1307                    destination_stake_account_info,
1308                    &StakeStateV2::Stake(destination_meta, destination_stake, StakeFlags::empty()),
1309                )?;
1310            }
1311            MergeKind::Inactive(destination_meta, _, _) => {
1312                // if destination is inactive, it must be given at least the minimum delegation
1313                if move_amount < minimum_delegation {
1314                    return Err(ProgramError::InvalidArgument);
1315                }
1316
1317                let mut destination_stake = source_stake;
1318                destination_stake.delegation.stake = move_amount;
1319
1320                // StakeFlags::empty() is valid here because the only existing stake flag,
1321                // MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED, is cleared when a stake
1322                // is activated
1323                set_stake_state(
1324                    destination_stake_account_info,
1325                    &StakeStateV2::Stake(destination_meta, destination_stake, StakeFlags::empty()),
1326                )?;
1327            }
1328            _ => return Err(ProgramError::InvalidAccountData),
1329        }
1330
1331        if source_final_stake == 0 {
1332            set_stake_state(
1333                source_stake_account_info,
1334                &StakeStateV2::Initialized(source_meta),
1335            )?;
1336        } else {
1337            source_stake.delegation.stake = source_final_stake;
1338
1339            // StakeFlags::empty() is valid here because the only existing stake flag,
1340            // MUST_FULLY_ACTIVATE_BEFORE_DEACTIVATION_IS_PERMITTED, does not apply to
1341            // active stakes
1342            set_stake_state(
1343                source_stake_account_info,
1344                &StakeStateV2::Stake(source_meta, source_stake, StakeFlags::empty()),
1345            )?;
1346        }
1347
1348        relocate_lamports(
1349            source_stake_account_info,
1350            destination_stake_account_info,
1351            move_amount,
1352        )?;
1353
1354        // this should be impossible, but because we do all our math with delegations,
1355        // best to guard it
1356        if source_stake_account_info.lamports() < source_rent_exempt_reserve
1357            || destination_stake_account_info.lamports() < destination_rent_exempt_reserve
1358        {
1359            msg!("Delegation calculations violated lamport balance assumptions");
1360            return Err(ProgramError::InvalidArgument);
1361        }
1362
1363        Ok(())
1364    }
1365
1366    fn process_move_lamports(accounts: &[AccountInfo], move_amount: u64) -> ProgramResult {
1367        let account_info_iter = &mut accounts.iter();
1368
1369        // invariant
1370        let source_stake_account_info = next_account_info(account_info_iter)?;
1371        let destination_stake_account_info = next_account_info(account_info_iter)?;
1372        let stake_authority_info = next_account_info(account_info_iter)?;
1373
1374        let rent = &Rent::get()?;
1375
1376        let (source_merge_kind, _) = move_stake_or_lamports_shared_checks(
1377            source_stake_account_info,
1378            move_amount,
1379            destination_stake_account_info,
1380            stake_authority_info,
1381        )?;
1382
1383        let source_rent_exempt_reserve = rent.minimum_balance(source_stake_account_info.data_len());
1384
1385        let source_free_lamports = match source_merge_kind {
1386            MergeKind::FullyActive(_, source_stake) => source_stake_account_info
1387                .lamports()
1388                .saturating_sub(source_stake.delegation.stake)
1389                .saturating_sub(source_rent_exempt_reserve),
1390            MergeKind::Inactive(_, source_lamports, _) => {
1391                source_lamports.saturating_sub(source_rent_exempt_reserve)
1392            }
1393            _ => return Err(ProgramError::InvalidAccountData),
1394        };
1395
1396        if move_amount > source_free_lamports {
1397            return Err(ProgramError::InvalidArgument);
1398        }
1399
1400        relocate_lamports(
1401            source_stake_account_info,
1402            destination_stake_account_info,
1403            move_amount,
1404        )?;
1405
1406        Ok(())
1407    }
1408
1409    /// Processes [Instruction](enum.Instruction.html).
1410    pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
1411        // convenience so we can safely use id() everywhere
1412        if *program_id != id() {
1413            return Err(ProgramError::IncorrectProgramId);
1414        }
1415
1416        let epoch_rewards_active = EpochRewards::get()
1417            .map(|epoch_rewards| epoch_rewards.active)
1418            .unwrap_or(false);
1419
1420        let instruction =
1421            bincode::deserialize(data).map_err(|_| ProgramError::InvalidInstructionData)?;
1422
1423        if epoch_rewards_active && !matches!(instruction, StakeInstruction::GetMinimumDelegation) {
1424            return Err(StakeError::EpochRewardsActive.into());
1425        }
1426
1427        match instruction {
1428            StakeInstruction::Initialize(authorize, lockup) => {
1429                msg!("Instruction: Initialize");
1430                Self::process_initialize(accounts, authorize, lockup)
1431            }
1432            StakeInstruction::Authorize(new_authority, authority_type) => {
1433                msg!("Instruction: Authorize");
1434                Self::process_authorize(accounts, new_authority, authority_type)
1435            }
1436            StakeInstruction::DelegateStake => {
1437                msg!("Instruction: DelegateStake");
1438                Self::process_delegate(accounts)
1439            }
1440            StakeInstruction::Split(lamports) => {
1441                msg!("Instruction: Split");
1442                Self::process_split(accounts, lamports)
1443            }
1444            StakeInstruction::Withdraw(lamports) => {
1445                msg!("Instruction: Withdraw");
1446                Self::process_withdraw(accounts, lamports)
1447            }
1448            StakeInstruction::Deactivate => {
1449                msg!("Instruction: Deactivate");
1450                Self::process_deactivate(accounts)
1451            }
1452            StakeInstruction::SetLockup(lockup) => {
1453                msg!("Instruction: SetLockup");
1454                Self::process_set_lockup(accounts, lockup)
1455            }
1456            StakeInstruction::Merge => {
1457                msg!("Instruction: Merge");
1458                Self::process_merge(accounts)
1459            }
1460            StakeInstruction::AuthorizeWithSeed(args) => {
1461                msg!("Instruction: AuthorizeWithSeed");
1462                Self::process_authorize_with_seed(accounts, args)
1463            }
1464            StakeInstruction::InitializeChecked => {
1465                msg!("Instruction: InitializeChecked");
1466                Self::process_initialize_checked(accounts)
1467            }
1468            StakeInstruction::AuthorizeChecked(authority_type) => {
1469                msg!("Instruction: AuthorizeChecked");
1470                Self::process_authorize_checked(accounts, authority_type)
1471            }
1472            StakeInstruction::AuthorizeCheckedWithSeed(args) => {
1473                msg!("Instruction: AuthorizeCheckedWithSeed");
1474                Self::process_authorize_checked_with_seed(accounts, args)
1475            }
1476            StakeInstruction::SetLockupChecked(lockup_checked) => {
1477                msg!("Instruction: SetLockupChecked");
1478                Self::process_set_lockup_checked(accounts, lockup_checked)
1479            }
1480            StakeInstruction::GetMinimumDelegation => {
1481                msg!("Instruction: GetMinimumDelegation");
1482                let minimum_delegation = crate::get_minimum_delegation();
1483                set_return_data(&minimum_delegation.to_le_bytes());
1484                Ok(())
1485            }
1486            StakeInstruction::DeactivateDelinquent => {
1487                msg!("Instruction: DeactivateDelinquent");
1488                Self::process_deactivate_delinquent(accounts)
1489            }
1490            #[allow(deprecated)]
1491            StakeInstruction::Redelegate => Err(ProgramError::InvalidInstructionData),
1492            StakeInstruction::MoveStake(lamports) => {
1493                msg!("Instruction: MoveStake");
1494                Self::process_move_stake(accounts, lamports)
1495            }
1496            StakeInstruction::MoveLamports(lamports) => {
1497                msg!("Instruction: MoveLamports");
1498                Self::process_move_lamports(accounts, lamports)
1499            }
1500        }
1501    }
1502}