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