Skip to main content

spl_stake_pool/
processor.rs

1//! Program state processor
2
3use {
4    crate::{
5        error::StakePoolError,
6        find_deposit_authority_program_address,
7        inline_mpl_token_metadata::{
8            self,
9            instruction::{create_metadata_accounts_v3, update_metadata_accounts_v2},
10            pda::find_metadata_account,
11            state::DataV2,
12        },
13        instruction::{FundingType, PreferredValidatorType, StakePoolInstruction},
14        minimum_delegation, minimum_reserve_lamports, minimum_stake_lamports,
15        state::{
16            is_extension_supported_for_mint, AccountType, Fee, FeeType, FutureEpoch, StakePool,
17            StakeStatus, StakeWithdrawSource, ValidatorList, ValidatorListHeader,
18            ValidatorStakeInfo,
19        },
20        AUTHORITY_DEPOSIT, AUTHORITY_WITHDRAW, EPHEMERAL_STAKE_SEED_PREFIX, MAX_VALIDATORS_IN_POOL,
21        TRANSIENT_STAKE_SEED_PREFIX,
22    },
23    borsh::BorshDeserialize,
24    num_traits::FromPrimitive,
25    solana_program::{
26        account_info::{next_account_info, AccountInfo},
27        borsh1::try_from_slice_unchecked,
28        clock::{Clock, Epoch},
29        decode_error::DecodeError,
30        entrypoint::ProgramResult,
31        epoch_rewards::EpochRewards,
32        msg,
33        program::{invoke, invoke_signed},
34        program_error::{PrintProgramError, ProgramError},
35        pubkey::Pubkey,
36        rent::Rent,
37        sysvar::Sysvar,
38    },
39    solana_stake_interface as stake,
40    solana_system_interface::{instruction as system_instruction, program as system_program},
41    spl_token_2022::{
42        check_spl_token_program_account,
43        extension::{BaseStateWithExtensions, StateWithExtensions},
44        native_mint,
45        state::Mint,
46    },
47    std::num::NonZeroU32,
48};
49
50/// Deserialize the stake state from `AccountInfo`
51fn get_stake_state(
52    stake_account_info: &AccountInfo,
53) -> Result<(stake::state::Meta, stake::state::Stake), ProgramError> {
54    let stake_state =
55        try_from_slice_unchecked::<stake::state::StakeStateV2>(&stake_account_info.data.borrow())?;
56    match stake_state {
57        stake::state::StakeStateV2::Stake(meta, stake, _) => Ok((meta, stake)),
58        _ => Err(StakePoolError::WrongStakeStake.into()),
59    }
60}
61
62/// Check validity of vote address for a particular stake account
63fn check_validator_stake_address(
64    program_id: &Pubkey,
65    stake_pool_address: &Pubkey,
66    stake_account_address: &Pubkey,
67    vote_address: &Pubkey,
68    seed: Option<NonZeroU32>,
69) -> Result<(), ProgramError> {
70    // Check stake account address validity
71    let (validator_stake_address, _) =
72        crate::find_stake_program_address(program_id, vote_address, stake_pool_address, seed);
73    if validator_stake_address != *stake_account_address {
74        msg!(
75            "Incorrect stake account address for vote {}, expected {}, received {}",
76            vote_address,
77            validator_stake_address,
78            stake_account_address
79        );
80        Err(StakePoolError::InvalidStakeAccountAddress.into())
81    } else {
82        Ok(())
83    }
84}
85
86/// Check validity of vote address for a particular stake account
87fn check_transient_stake_address(
88    program_id: &Pubkey,
89    stake_pool_address: &Pubkey,
90    stake_account_address: &Pubkey,
91    vote_address: &Pubkey,
92    seed: u64,
93) -> Result<u8, ProgramError> {
94    // Check stake account address validity
95    let (transient_stake_address, bump_seed) = crate::find_transient_stake_program_address(
96        program_id,
97        vote_address,
98        stake_pool_address,
99        seed,
100    );
101    if transient_stake_address != *stake_account_address {
102        Err(StakePoolError::InvalidStakeAccountAddress.into())
103    } else {
104        Ok(bump_seed)
105    }
106}
107
108/// Check address validity for an ephemeral stake account
109fn check_ephemeral_stake_address(
110    program_id: &Pubkey,
111    stake_pool_address: &Pubkey,
112    stake_account_address: &Pubkey,
113    seed: u64,
114) -> Result<u8, ProgramError> {
115    // Check stake account address validity
116    let (ephemeral_stake_address, bump_seed) =
117        crate::find_ephemeral_stake_program_address(program_id, stake_pool_address, seed);
118    if ephemeral_stake_address != *stake_account_address {
119        Err(StakePoolError::InvalidStakeAccountAddress.into())
120    } else {
121        Ok(bump_seed)
122    }
123}
124
125/// Check mpl metadata account address for the pool mint
126fn check_mpl_metadata_account_address(
127    metadata_address: &Pubkey,
128    pool_mint: &Pubkey,
129) -> Result<(), ProgramError> {
130    let (metadata_account_pubkey, _) = find_metadata_account(pool_mint);
131    if metadata_account_pubkey != *metadata_address {
132        Err(StakePoolError::InvalidMetadataAccount.into())
133    } else {
134        Ok(())
135    }
136}
137
138/// Check system program address
139fn check_system_program(program_id: &Pubkey) -> Result<(), ProgramError> {
140    if *program_id != system_program::id() {
141        msg!(
142            "Expected system program {}, received {}",
143            system_program::id(),
144            program_id
145        );
146        Err(ProgramError::IncorrectProgramId)
147    } else {
148        Ok(())
149    }
150}
151
152/// Check stake program address
153fn check_stake_program(program_id: &Pubkey) -> Result<(), ProgramError> {
154    if *program_id != stake::program::id() {
155        msg!(
156            "Expected stake program {}, received {}",
157            stake::program::id(),
158            program_id
159        );
160        Err(ProgramError::IncorrectProgramId)
161    } else {
162        Ok(())
163    }
164}
165
166/// Check mpl metadata program
167fn check_mpl_metadata_program(program_id: &Pubkey) -> Result<(), ProgramError> {
168    if *program_id != inline_mpl_token_metadata::id() {
169        msg!(
170            "Expected mpl metadata program {}, received {}",
171            inline_mpl_token_metadata::id(),
172            program_id
173        );
174        Err(ProgramError::IncorrectProgramId)
175    } else {
176        Ok(())
177    }
178}
179
180/// Check account owner is the given program
181fn check_account_owner(
182    account_info: &AccountInfo,
183    program_id: &Pubkey,
184) -> Result<(), ProgramError> {
185    if *program_id != *account_info.owner {
186        msg!(
187            "Expected account to be owned by program {}, received {}",
188            program_id,
189            account_info.owner
190        );
191        Err(ProgramError::IncorrectProgramId)
192    } else {
193        Ok(())
194    }
195}
196
197/// Checks if a stake account can be managed by the pool
198fn stake_is_usable_by_pool(
199    meta: &stake::state::Meta,
200    expected_authority: &Pubkey,
201    expected_lockup: &stake::state::Lockup,
202) -> bool {
203    meta.authorized.staker == *expected_authority
204        && meta.authorized.withdrawer == *expected_authority
205        && meta.lockup == *expected_lockup
206}
207
208/// Checks if a stake account is active, without taking into account cool down
209fn stake_is_inactive_without_history(stake: &stake::state::Stake, epoch: Epoch) -> bool {
210    stake.delegation.deactivation_epoch < epoch
211        || (stake.delegation.activation_epoch == epoch
212            && stake.delegation.deactivation_epoch == epoch)
213}
214
215/// Roughly checks if a stake account is deactivating
216fn check_if_stake_deactivating(
217    account_info: &AccountInfo,
218    vote_account_address: &Pubkey,
219    epoch: Epoch,
220) -> Result<(), ProgramError> {
221    let (_, stake) = get_stake_state(account_info)?;
222    if stake.delegation.deactivation_epoch != epoch {
223        msg!(
224            "Existing stake {} delegated to {} not deactivated in epoch {}",
225            account_info.key,
226            vote_account_address,
227            epoch,
228        );
229        Err(StakePoolError::WrongStakeStake.into())
230    } else {
231        Ok(())
232    }
233}
234
235/// Roughly checks if a stake account is activating
236fn check_if_stake_activating(
237    account_info: &AccountInfo,
238    vote_account_address: &Pubkey,
239    epoch: Epoch,
240) -> Result<(), ProgramError> {
241    let (_, stake) = get_stake_state(account_info)?;
242    if stake.delegation.deactivation_epoch != Epoch::MAX
243        || stake.delegation.activation_epoch != epoch
244    {
245        msg!(
246            "Existing stake {} delegated to {} not activated in epoch {}",
247            account_info.key,
248            vote_account_address,
249            epoch,
250        );
251        Err(StakePoolError::WrongStakeStake.into())
252    } else {
253        Ok(())
254    }
255}
256
257/// Check that the stake state is correct: usable by the pool and delegated to
258/// the expected validator
259fn check_stake_state(
260    stake_account_info: &AccountInfo,
261    withdraw_authority: &Pubkey,
262    vote_account_address: &Pubkey,
263    lockup: &stake::state::Lockup,
264) -> Result<(), ProgramError> {
265    let (meta, stake) = get_stake_state(stake_account_info)?;
266    if !stake_is_usable_by_pool(&meta, withdraw_authority, lockup) {
267        msg!(
268            "Validator stake for {} not usable by pool, must be owned by withdraw authority",
269            vote_account_address
270        );
271        return Err(StakePoolError::WrongStakeStake.into());
272    }
273    if stake.delegation.voter_pubkey != *vote_account_address {
274        msg!(
275            "Validator stake {} not delegated to {}",
276            stake_account_info.key,
277            vote_account_address
278        );
279        return Err(StakePoolError::WrongStakeStake.into());
280    }
281    Ok(())
282}
283
284/// Checks if a validator stake account is valid, which means that it's usable
285/// by the pool and delegated to the expected validator. These conditions can be
286/// violated if a validator was force destaked during a cluster restart.
287fn check_validator_stake_account(
288    stake_account_info: &AccountInfo,
289    program_id: &Pubkey,
290    stake_pool: &Pubkey,
291    withdraw_authority: &Pubkey,
292    vote_account_address: &Pubkey,
293    seed: u32,
294    lockup: &stake::state::Lockup,
295) -> Result<(), ProgramError> {
296    check_account_owner(stake_account_info, &stake::program::id())?;
297    check_validator_stake_address(
298        program_id,
299        stake_pool,
300        stake_account_info.key,
301        vote_account_address,
302        NonZeroU32::new(seed),
303    )?;
304    check_stake_state(
305        stake_account_info,
306        withdraw_authority,
307        vote_account_address,
308        lockup,
309    )?;
310    Ok(())
311}
312
313/// Create a stake account on a PDA without transferring lamports
314fn create_stake_account(
315    stake_account_info: AccountInfo<'_>,
316    stake_account_signer_seeds: &[&[u8]],
317    stake_space: usize,
318) -> Result<(), ProgramError> {
319    invoke_signed(
320        &system_instruction::allocate(stake_account_info.key, stake_space as u64),
321        core::slice::from_ref(&stake_account_info),
322        &[stake_account_signer_seeds],
323    )?;
324    invoke_signed(
325        &system_instruction::assign(stake_account_info.key, &stake::program::id()),
326        &[stake_account_info],
327        &[stake_account_signer_seeds],
328    )
329}
330
331/// Program state handler.
332pub struct Processor {}
333impl Processor {
334    /// Issue a `delegate_stake` instruction.
335    #[allow(clippy::too_many_arguments)]
336    fn stake_delegate<'a>(
337        stake_info: AccountInfo<'a>,
338        vote_account_info: AccountInfo<'a>,
339        clock_info: AccountInfo<'a>,
340        stake_history_info: AccountInfo<'a>,
341        stake_config_info: AccountInfo<'a>,
342        authority_info: AccountInfo<'a>,
343        stake_pool: &Pubkey,
344        authority_type: &[u8],
345        bump_seed: u8,
346    ) -> Result<(), ProgramError> {
347        let authority_signature_seeds = [stake_pool.as_ref(), authority_type, &[bump_seed]];
348        let signers = &[&authority_signature_seeds[..]];
349
350        let ix = stake::instruction::delegate_stake(
351            stake_info.key,
352            authority_info.key,
353            vote_account_info.key,
354        );
355
356        invoke_signed(
357            &ix,
358            &[
359                stake_info,
360                vote_account_info,
361                clock_info,
362                stake_history_info,
363                stake_config_info,
364                authority_info,
365            ],
366            signers,
367        )
368    }
369
370    /// Issue a `stake_deactivate` instruction.
371    fn stake_deactivate<'a>(
372        stake_info: AccountInfo<'a>,
373        clock_info: AccountInfo<'a>,
374        authority_info: AccountInfo<'a>,
375        stake_pool: &Pubkey,
376        authority_type: &[u8],
377        bump_seed: u8,
378    ) -> Result<(), ProgramError> {
379        let authority_signature_seeds = [stake_pool.as_ref(), authority_type, &[bump_seed]];
380        let signers = &[&authority_signature_seeds[..]];
381
382        let ix = stake::instruction::deactivate_stake(stake_info.key, authority_info.key);
383
384        invoke_signed(&ix, &[stake_info, clock_info, authority_info], signers)
385    }
386
387    /// Issue a `stake_split` instruction.
388    fn stake_split<'a>(
389        stake_pool: &Pubkey,
390        stake_account: AccountInfo<'a>,
391        authority: AccountInfo<'a>,
392        authority_type: &[u8],
393        bump_seed: u8,
394        amount: u64,
395        split_stake: AccountInfo<'a>,
396    ) -> Result<(), ProgramError> {
397        let authority_signature_seeds = [stake_pool.as_ref(), authority_type, &[bump_seed]];
398        let signers = &[&authority_signature_seeds[..]];
399
400        let split_instruction =
401            stake::instruction::split(stake_account.key, authority.key, amount, split_stake.key);
402
403        invoke_signed(
404            split_instruction
405                .last()
406                .ok_or(ProgramError::InvalidInstructionData)?,
407            &[stake_account, split_stake, authority],
408            signers,
409        )
410    }
411
412    /// Issue a `stake_merge` instruction.
413    #[allow(clippy::too_many_arguments)]
414    fn stake_merge<'a>(
415        stake_pool: &Pubkey,
416        source_account: AccountInfo<'a>,
417        authority: AccountInfo<'a>,
418        authority_type: &[u8],
419        bump_seed: u8,
420        destination_account: AccountInfo<'a>,
421        clock: AccountInfo<'a>,
422        stake_history: AccountInfo<'a>,
423    ) -> Result<(), ProgramError> {
424        let authority_signature_seeds = [stake_pool.as_ref(), authority_type, &[bump_seed]];
425        let signers = &[&authority_signature_seeds[..]];
426
427        let merge_instruction =
428            stake::instruction::merge(destination_account.key, source_account.key, authority.key);
429
430        invoke_signed(
431            &merge_instruction[0],
432            &[
433                destination_account,
434                source_account,
435                clock,
436                stake_history,
437                authority,
438            ],
439            signers,
440        )
441    }
442
443    /// Issue stake::instruction::authorize instructions to update both
444    /// authorities
445    fn stake_authorize<'a>(
446        stake_account: AccountInfo<'a>,
447        stake_authority: AccountInfo<'a>,
448        new_stake_authority: &Pubkey,
449        clock: AccountInfo<'a>,
450    ) -> Result<(), ProgramError> {
451        let authorize_instruction = stake::instruction::authorize(
452            stake_account.key,
453            stake_authority.key,
454            new_stake_authority,
455            stake::state::StakeAuthorize::Staker,
456            None,
457        );
458
459        invoke(
460            &authorize_instruction,
461            &[
462                stake_account.clone(),
463                clock.clone(),
464                stake_authority.clone(),
465            ],
466        )?;
467
468        let authorize_instruction = stake::instruction::authorize(
469            stake_account.key,
470            stake_authority.key,
471            new_stake_authority,
472            stake::state::StakeAuthorize::Withdrawer,
473            None,
474        );
475
476        invoke(
477            &authorize_instruction,
478            &[stake_account, clock, stake_authority],
479        )
480    }
481
482    /// Issue stake::instruction::authorize instructions to update both
483    /// authorities
484    #[allow(clippy::too_many_arguments)]
485    fn stake_authorize_signed<'a>(
486        stake_pool: &Pubkey,
487        stake_account: AccountInfo<'a>,
488        stake_authority: AccountInfo<'a>,
489        authority_type: &[u8],
490        bump_seed: u8,
491        new_stake_authority: &Pubkey,
492        clock: AccountInfo<'a>,
493    ) -> Result<(), ProgramError> {
494        let authority_signature_seeds = [stake_pool.as_ref(), authority_type, &[bump_seed]];
495        let signers = &[&authority_signature_seeds[..]];
496
497        let authorize_instruction = stake::instruction::authorize(
498            stake_account.key,
499            stake_authority.key,
500            new_stake_authority,
501            stake::state::StakeAuthorize::Staker,
502            None,
503        );
504
505        invoke_signed(
506            &authorize_instruction,
507            &[
508                stake_account.clone(),
509                clock.clone(),
510                stake_authority.clone(),
511            ],
512            signers,
513        )?;
514
515        let authorize_instruction = stake::instruction::authorize(
516            stake_account.key,
517            stake_authority.key,
518            new_stake_authority,
519            stake::state::StakeAuthorize::Withdrawer,
520            None,
521        );
522        invoke_signed(
523            &authorize_instruction,
524            &[stake_account, clock, stake_authority],
525            signers,
526        )
527    }
528
529    /// Issue stake::instruction::withdraw instruction to move additional
530    /// lamports
531    #[allow(clippy::too_many_arguments)]
532    fn stake_withdraw<'a>(
533        stake_pool: &Pubkey,
534        source_account: AccountInfo<'a>,
535        authority: AccountInfo<'a>,
536        authority_type: &[u8],
537        bump_seed: u8,
538        destination_account: AccountInfo<'a>,
539        clock: AccountInfo<'a>,
540        stake_history: AccountInfo<'a>,
541        lamports: u64,
542    ) -> Result<(), ProgramError> {
543        let authority_signature_seeds = [stake_pool.as_ref(), authority_type, &[bump_seed]];
544        let signers = &[&authority_signature_seeds[..]];
545        let custodian_pubkey = None;
546
547        let withdraw_instruction = stake::instruction::withdraw(
548            source_account.key,
549            authority.key,
550            destination_account.key,
551            lamports,
552            custodian_pubkey,
553        );
554
555        invoke_signed(
556            &withdraw_instruction,
557            &[
558                source_account,
559                destination_account,
560                clock,
561                stake_history,
562                authority,
563            ],
564            signers,
565        )
566    }
567
568    /// Issue a SPL Token `Burn` instruction.
569    #[allow(clippy::too_many_arguments)]
570    fn token_burn<'a>(
571        token_program: AccountInfo<'a>,
572        burn_account: AccountInfo<'a>,
573        mint: AccountInfo<'a>,
574        authority: AccountInfo<'a>,
575        amount: u64,
576    ) -> Result<(), ProgramError> {
577        let ix = spl_token_2022::instruction::burn(
578            token_program.key,
579            burn_account.key,
580            mint.key,
581            authority.key,
582            &[],
583            amount,
584        )?;
585
586        invoke(&ix, &[burn_account, mint, authority])
587    }
588
589    /// Issue a SPL Token `MintTo` instruction.
590    #[allow(clippy::too_many_arguments)]
591    fn token_mint_to<'a>(
592        stake_pool: &Pubkey,
593        token_program: AccountInfo<'a>,
594        mint: AccountInfo<'a>,
595        destination: AccountInfo<'a>,
596        authority: AccountInfo<'a>,
597        authority_type: &[u8],
598        bump_seed: u8,
599        amount: u64,
600    ) -> Result<(), ProgramError> {
601        let authority_signature_seeds = [stake_pool.as_ref(), authority_type, &[bump_seed]];
602        let signers = &[&authority_signature_seeds[..]];
603
604        let ix = spl_token_2022::instruction::mint_to(
605            token_program.key,
606            mint.key,
607            destination.key,
608            authority.key,
609            &[],
610            amount,
611        )?;
612
613        invoke_signed(&ix, &[mint, destination, authority], signers)
614    }
615
616    /// Issue a SPL Token `Transfer` instruction.
617    #[allow(clippy::too_many_arguments)]
618    fn token_transfer<'a>(
619        token_program: AccountInfo<'a>,
620        source: AccountInfo<'a>,
621        mint: AccountInfo<'a>,
622        destination: AccountInfo<'a>,
623        authority: AccountInfo<'a>,
624        amount: u64,
625        decimals: u8,
626    ) -> Result<(), ProgramError> {
627        let ix = spl_token_2022::instruction::transfer_checked(
628            token_program.key,
629            source.key,
630            mint.key,
631            destination.key,
632            authority.key,
633            &[],
634            amount,
635            decimals,
636        )?;
637        invoke(&ix, &[source, mint, destination, authority])
638    }
639
640    fn sol_transfer<'a>(
641        source: AccountInfo<'a>,
642        destination: AccountInfo<'a>,
643        amount: u64,
644    ) -> Result<(), ProgramError> {
645        let ix = solana_program::system_instruction::transfer(source.key, destination.key, amount);
646        invoke(&ix, &[source, destination])
647    }
648
649    /// Processes `Initialize` instruction.
650    #[inline(never)] // needed due to stack size violation
651    fn process_initialize(
652        program_id: &Pubkey,
653        accounts: &[AccountInfo],
654        epoch_fee: Fee,
655        withdrawal_fee: Fee,
656        deposit_fee: Fee,
657        referral_fee: u8,
658        max_validators: u32,
659    ) -> ProgramResult {
660        let account_info_iter = &mut accounts.iter();
661        let stake_pool_info = next_account_info(account_info_iter)?;
662        let manager_info = next_account_info(account_info_iter)?;
663        let staker_info = next_account_info(account_info_iter)?;
664        let withdraw_authority_info = next_account_info(account_info_iter)?;
665        let validator_list_info = next_account_info(account_info_iter)?;
666        let reserve_stake_info = next_account_info(account_info_iter)?;
667        let pool_mint_info = next_account_info(account_info_iter)?;
668        let manager_fee_info = next_account_info(account_info_iter)?;
669        let token_program_info = next_account_info(account_info_iter)?;
670
671        let rent = Rent::get()?;
672
673        if !manager_info.is_signer {
674            msg!("Manager did not sign initialization");
675            return Err(StakePoolError::SignatureMissing.into());
676        }
677
678        if stake_pool_info.key == validator_list_info.key {
679            msg!("Cannot use same account for stake pool and validator list");
680            return Err(StakePoolError::AlreadyInUse.into());
681        }
682
683        // This check is unnecessary since the runtime will check the ownership,
684        // but provides clarity that the parameter is in fact checked.
685        check_account_owner(stake_pool_info, program_id)?;
686        let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
687        if !stake_pool.is_uninitialized() {
688            msg!("Provided stake pool already in use");
689            return Err(StakePoolError::AlreadyInUse.into());
690        }
691
692        // This check is unnecessary since the runtime will check the ownership,
693        // but provides clarity that the parameter is in fact checked.
694        check_account_owner(validator_list_info, program_id)?;
695        let mut validator_list =
696            try_from_slice_unchecked::<ValidatorList>(&validator_list_info.data.borrow())?;
697        if !validator_list.header.is_uninitialized() {
698            msg!("Provided validator list already in use");
699            return Err(StakePoolError::AlreadyInUse.into());
700        }
701
702        let data_length = validator_list_info.data_len();
703        let expected_max_validators = ValidatorList::calculate_max_validators(data_length);
704        if expected_max_validators != max_validators as usize || max_validators == 0 {
705            msg!(
706                "Incorrect validator list size provided, expected {}, provided {}",
707                expected_max_validators,
708                max_validators
709            );
710            return Err(StakePoolError::UnexpectedValidatorListAccountSize.into());
711        }
712        if max_validators > MAX_VALIDATORS_IN_POOL {
713            return Err(StakePoolError::TooManyValidatorsInPool.into());
714        }
715        validator_list.header.account_type = AccountType::ValidatorList;
716        validator_list.header.max_validators = max_validators;
717        validator_list.validators.clear();
718
719        if !rent.is_exempt(stake_pool_info.lamports(), stake_pool_info.data_len()) {
720            msg!("Stake pool not rent-exempt");
721            return Err(ProgramError::AccountNotRentExempt);
722        }
723
724        if !rent.is_exempt(
725            validator_list_info.lamports(),
726            validator_list_info.data_len(),
727        ) {
728            msg!("Validator stake list not rent-exempt");
729            return Err(ProgramError::AccountNotRentExempt);
730        }
731
732        // Numerator should be smaller than or equal to denominator (fee <= 1)
733        if epoch_fee.numerator > epoch_fee.denominator
734            || withdrawal_fee.numerator > withdrawal_fee.denominator
735            || deposit_fee.numerator > deposit_fee.denominator
736            || referral_fee > 100u8
737        {
738            return Err(StakePoolError::FeeTooHigh.into());
739        }
740
741        check_spl_token_program_account(token_program_info.key)?;
742
743        if pool_mint_info.owner != token_program_info.key {
744            return Err(ProgramError::IncorrectProgramId);
745        }
746
747        stake_pool.token_program_id = *token_program_info.key;
748        stake_pool.pool_mint = *pool_mint_info.key;
749
750        let (stake_deposit_authority, sol_deposit_authority) =
751            match next_account_info(account_info_iter) {
752                Ok(deposit_authority_info) => (
753                    *deposit_authority_info.key,
754                    Some(*deposit_authority_info.key),
755                ),
756                Err(_) => (
757                    find_deposit_authority_program_address(program_id, stake_pool_info.key).0,
758                    None,
759                ),
760            };
761        let (withdraw_authority_key, stake_withdraw_bump_seed) =
762            crate::find_withdraw_authority_program_address(program_id, stake_pool_info.key);
763        if withdraw_authority_key != *withdraw_authority_info.key {
764            msg!(
765                "Incorrect withdraw authority provided, expected {}, received {}",
766                withdraw_authority_key,
767                withdraw_authority_info.key
768            );
769            return Err(StakePoolError::InvalidProgramAddress.into());
770        }
771
772        {
773            let pool_mint_data = pool_mint_info.try_borrow_data()?;
774            let pool_mint = StateWithExtensions::<Mint>::unpack(&pool_mint_data)?;
775
776            if pool_mint.base.supply != 0 {
777                return Err(StakePoolError::NonZeroPoolTokenSupply.into());
778            }
779
780            if pool_mint.base.decimals != native_mint::DECIMALS {
781                return Err(StakePoolError::IncorrectMintDecimals.into());
782            }
783
784            if !pool_mint
785                .base
786                .mint_authority
787                .contains(&withdraw_authority_key)
788            {
789                return Err(StakePoolError::WrongMintingAuthority.into());
790            }
791
792            if pool_mint.base.freeze_authority.is_some() {
793                return Err(StakePoolError::InvalidMintFreezeAuthority.into());
794            }
795
796            let extensions = pool_mint.get_extension_types()?;
797            if extensions
798                .iter()
799                .any(|x| !is_extension_supported_for_mint(x))
800            {
801                return Err(StakePoolError::UnsupportedMintExtension.into());
802            }
803        }
804        stake_pool.check_manager_fee_info(manager_fee_info)?;
805
806        if *reserve_stake_info.owner != stake::program::id() {
807            msg!("Reserve stake account not owned by stake program");
808            return Err(ProgramError::IncorrectProgramId);
809        }
810        let stake_state = try_from_slice_unchecked::<stake::state::StakeStateV2>(
811            &reserve_stake_info.data.borrow(),
812        )?;
813        let total_lamports = if let stake::state::StakeStateV2::Initialized(meta) = stake_state {
814            if meta.lockup != stake::state::Lockup::default() {
815                msg!("Reserve stake account has some lockup");
816                return Err(StakePoolError::WrongStakeStake.into());
817            }
818
819            if meta.authorized.staker != withdraw_authority_key {
820                msg!(
821                    "Reserve stake account has incorrect staker {}, should be {}",
822                    meta.authorized.staker,
823                    withdraw_authority_key
824                );
825                return Err(StakePoolError::WrongStakeStake.into());
826            }
827
828            if meta.authorized.withdrawer != withdraw_authority_key {
829                msg!(
830                    "Reserve stake account has incorrect withdrawer {}, should be {}",
831                    meta.authorized.staker,
832                    withdraw_authority_key
833                );
834                return Err(StakePoolError::WrongStakeStake.into());
835            }
836            reserve_stake_info
837                .lamports()
838                .checked_sub(minimum_reserve_lamports(&meta))
839                .ok_or(StakePoolError::CalculationFailure)?
840        } else {
841            msg!("Reserve stake account not in initialized state");
842            return Err(StakePoolError::WrongStakeStake.into());
843        };
844
845        if total_lamports > 0 {
846            Self::token_mint_to(
847                stake_pool_info.key,
848                token_program_info.clone(),
849                pool_mint_info.clone(),
850                manager_fee_info.clone(),
851                withdraw_authority_info.clone(),
852                AUTHORITY_WITHDRAW,
853                stake_withdraw_bump_seed,
854                total_lamports,
855            )?;
856        }
857
858        borsh::to_writer(
859            &mut validator_list_info.data.borrow_mut()[..],
860            &validator_list,
861        )?;
862
863        stake_pool.account_type = AccountType::StakePool;
864        stake_pool.manager = *manager_info.key;
865        stake_pool.staker = *staker_info.key;
866        stake_pool.stake_deposit_authority = stake_deposit_authority;
867        stake_pool.stake_withdraw_bump_seed = stake_withdraw_bump_seed;
868        stake_pool.validator_list = *validator_list_info.key;
869        stake_pool.reserve_stake = *reserve_stake_info.key;
870        stake_pool.manager_fee_account = *manager_fee_info.key;
871        stake_pool.total_lamports = total_lamports;
872        stake_pool.pool_token_supply = total_lamports;
873        stake_pool.last_update_epoch = Clock::get()?.epoch;
874        stake_pool.lockup = stake::state::Lockup::default();
875        stake_pool.epoch_fee = epoch_fee;
876        stake_pool.next_epoch_fee = FutureEpoch::None;
877        stake_pool.preferred_deposit_validator_vote_address = None;
878        stake_pool.preferred_withdraw_validator_vote_address = None;
879        stake_pool.stake_deposit_fee = deposit_fee;
880        stake_pool.stake_withdrawal_fee = withdrawal_fee;
881        stake_pool.next_stake_withdrawal_fee = FutureEpoch::None;
882        stake_pool.stake_referral_fee = referral_fee;
883        stake_pool.sol_deposit_authority = sol_deposit_authority;
884        stake_pool.sol_deposit_fee = deposit_fee;
885        stake_pool.sol_referral_fee = referral_fee;
886        stake_pool.sol_withdraw_authority = None;
887        stake_pool.sol_withdrawal_fee = withdrawal_fee;
888        stake_pool.next_sol_withdrawal_fee = FutureEpoch::None;
889        stake_pool.last_epoch_pool_token_supply = 0;
890        stake_pool.last_epoch_total_lamports = 0;
891
892        borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)
893            .map_err(|e| e.into())
894    }
895
896    /// Processes `AddValidatorToPool` instruction.
897    #[inline(never)] // needed due to stack size violation
898    fn process_add_validator_to_pool(
899        program_id: &Pubkey,
900        accounts: &[AccountInfo],
901        raw_validator_seed: u32,
902    ) -> ProgramResult {
903        let account_info_iter = &mut accounts.iter();
904        let stake_pool_info = next_account_info(account_info_iter)?;
905        let staker_info = next_account_info(account_info_iter)?;
906        let reserve_stake_info = next_account_info(account_info_iter)?;
907        let withdraw_authority_info = next_account_info(account_info_iter)?;
908        let validator_list_info = next_account_info(account_info_iter)?;
909        let stake_info = next_account_info(account_info_iter)?;
910        let validator_vote_info = next_account_info(account_info_iter)?;
911        let rent_info = next_account_info(account_info_iter)?;
912        let rent = &Rent::from_account_info(rent_info)?;
913        let clock_info = next_account_info(account_info_iter)?;
914        let clock = &Clock::from_account_info(clock_info)?;
915        let stake_history_info = next_account_info(account_info_iter)?;
916        let stake_config_info = next_account_info(account_info_iter)?;
917        let system_program_info = next_account_info(account_info_iter)?;
918        let stake_program_info = next_account_info(account_info_iter)?;
919
920        check_system_program(system_program_info.key)?;
921        check_stake_program(stake_program_info.key)?;
922
923        check_account_owner(stake_pool_info, program_id)?;
924        let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
925        if !stake_pool.is_valid() {
926            return Err(StakePoolError::InvalidState.into());
927        }
928
929        stake_pool.check_authority_withdraw(
930            withdraw_authority_info.key,
931            program_id,
932            stake_pool_info.key,
933        )?;
934
935        stake_pool.check_staker(staker_info)?;
936        stake_pool.check_reserve_stake(reserve_stake_info)?;
937        stake_pool.check_validator_list(validator_list_info)?;
938
939        if stake_pool.last_update_epoch < clock.epoch {
940            return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
941        }
942
943        check_account_owner(validator_list_info, program_id)?;
944        let mut validator_list_data = validator_list_info.data.borrow_mut();
945        let (header, mut validator_list) =
946            ValidatorListHeader::deserialize_vec(&mut validator_list_data)?;
947        if !header.is_valid() {
948            return Err(StakePoolError::InvalidState.into());
949        }
950        if header.max_validators == validator_list.len() {
951            return Err(ProgramError::AccountDataTooSmall);
952        }
953        if validator_list.len() >= MAX_VALIDATORS_IN_POOL {
954            return Err(StakePoolError::TooManyValidatorsInPool.into());
955        }
956        let maybe_validator_stake_info = validator_list.find::<ValidatorStakeInfo, _>(|x| {
957            ValidatorStakeInfo::memcmp_pubkey(x, validator_vote_info.key)
958        });
959        if maybe_validator_stake_info.is_some() {
960            return Err(StakePoolError::ValidatorAlreadyAdded.into());
961        }
962
963        let validator_seed = NonZeroU32::new(raw_validator_seed);
964        let (stake_address, bump_seed) = crate::find_stake_program_address(
965            program_id,
966            validator_vote_info.key,
967            stake_pool_info.key,
968            validator_seed,
969        );
970        if stake_address != *stake_info.key {
971            return Err(StakePoolError::InvalidStakeAccountAddress.into());
972        }
973
974        let validator_seed_bytes = validator_seed.map(|s| s.get().to_le_bytes());
975        let stake_account_signer_seeds: &[&[_]] = &[
976            validator_vote_info.key.as_ref(),
977            stake_pool_info.key.as_ref(),
978            validator_seed_bytes
979                .as_ref()
980                .map(|s| s.as_slice())
981                .unwrap_or(&[]),
982            &[bump_seed],
983        ];
984
985        // Fund the stake account with the minimum + rent-exempt balance
986        let stake_space = std::mem::size_of::<stake::state::StakeStateV2>();
987        let stake_minimum_delegation = stake::tools::get_minimum_delegation()?;
988        let required_lamports = minimum_delegation(stake_minimum_delegation)
989            .saturating_add(rent.minimum_balance(stake_space));
990
991        // Check that we're not draining the reserve totally
992        let reserve_stake = try_from_slice_unchecked::<stake::state::StakeStateV2>(
993            &reserve_stake_info.data.borrow(),
994        )?;
995        let reserve_meta = reserve_stake
996            .meta()
997            .ok_or(StakePoolError::WrongStakeStake)?;
998        let minimum_lamports = minimum_reserve_lamports(&reserve_meta);
999        let reserve_lamports = reserve_stake_info.lamports();
1000        if reserve_lamports.saturating_sub(required_lamports) < minimum_lamports {
1001            msg!(
1002                "Need to add {} lamports for the reserve stake to be rent-exempt after adding a validator, reserve currently has {} lamports",
1003                required_lamports.saturating_add(minimum_lamports).saturating_sub(reserve_lamports),
1004                reserve_lamports
1005            );
1006            return Err(ProgramError::InsufficientFunds);
1007        }
1008
1009        // Create new stake account
1010        create_stake_account(stake_info.clone(), stake_account_signer_seeds, stake_space)?;
1011        // split into validator stake account
1012        Self::stake_split(
1013            stake_pool_info.key,
1014            reserve_stake_info.clone(),
1015            withdraw_authority_info.clone(),
1016            AUTHORITY_WITHDRAW,
1017            stake_pool.stake_withdraw_bump_seed,
1018            required_lamports,
1019            stake_info.clone(),
1020        )?;
1021
1022        Self::stake_delegate(
1023            stake_info.clone(),
1024            validator_vote_info.clone(),
1025            clock_info.clone(),
1026            stake_history_info.clone(),
1027            stake_config_info.clone(),
1028            withdraw_authority_info.clone(),
1029            stake_pool_info.key,
1030            AUTHORITY_WITHDRAW,
1031            stake_pool.stake_withdraw_bump_seed,
1032        )?;
1033
1034        validator_list.push(ValidatorStakeInfo {
1035            status: StakeStatus::Active.into(),
1036            vote_account_address: *validator_vote_info.key,
1037            active_stake_lamports: required_lamports.into(),
1038            transient_stake_lamports: 0.into(),
1039            last_update_epoch: clock.epoch.into(),
1040            transient_seed_suffix: 0.into(),
1041            unused: 0.into(),
1042            validator_seed_suffix: raw_validator_seed.into(),
1043        })?;
1044
1045        Ok(())
1046    }
1047
1048    /// Processes `RemoveValidatorFromPool` instruction.
1049    #[inline(never)] // needed due to stack size violation
1050    fn process_remove_validator_from_pool(
1051        program_id: &Pubkey,
1052        accounts: &[AccountInfo],
1053    ) -> ProgramResult {
1054        let account_info_iter = &mut accounts.iter();
1055        let stake_pool_info = next_account_info(account_info_iter)?;
1056        let staker_info = next_account_info(account_info_iter)?;
1057        let withdraw_authority_info = next_account_info(account_info_iter)?;
1058        let validator_list_info = next_account_info(account_info_iter)?;
1059        let stake_account_info = next_account_info(account_info_iter)?;
1060        let transient_stake_account_info = next_account_info(account_info_iter)?;
1061        let clock_info = next_account_info(account_info_iter)?;
1062        let clock = &Clock::from_account_info(clock_info)?;
1063        let stake_program_info = next_account_info(account_info_iter)?;
1064
1065        check_stake_program(stake_program_info.key)?;
1066        check_account_owner(stake_pool_info, program_id)?;
1067
1068        let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
1069        if !stake_pool.is_valid() {
1070            return Err(StakePoolError::InvalidState.into());
1071        }
1072
1073        stake_pool.check_authority_withdraw(
1074            withdraw_authority_info.key,
1075            program_id,
1076            stake_pool_info.key,
1077        )?;
1078        stake_pool.check_staker(staker_info)?;
1079
1080        if stake_pool.last_update_epoch < clock.epoch {
1081            msg!(
1082                "clock {} pool {}",
1083                clock.epoch,
1084                stake_pool.last_update_epoch
1085            );
1086            return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
1087        }
1088
1089        stake_pool.check_validator_list(validator_list_info)?;
1090
1091        check_account_owner(validator_list_info, program_id)?;
1092        let mut validator_list_data = validator_list_info.data.borrow_mut();
1093        let (header, mut validator_list) =
1094            ValidatorListHeader::deserialize_vec(&mut validator_list_data)?;
1095        if !header.is_valid() {
1096            return Err(StakePoolError::InvalidState.into());
1097        }
1098
1099        let (_, stake) = get_stake_state(stake_account_info)?;
1100        let vote_account_address = stake.delegation.voter_pubkey;
1101        let maybe_validator_stake_info = validator_list.find_mut::<ValidatorStakeInfo, _>(|x| {
1102            ValidatorStakeInfo::memcmp_pubkey(x, &vote_account_address)
1103        });
1104        if maybe_validator_stake_info.is_none() {
1105            msg!(
1106                "Vote account {} not found in stake pool",
1107                vote_account_address
1108            );
1109            return Err(StakePoolError::ValidatorNotFound.into());
1110        }
1111        let validator_stake_info = maybe_validator_stake_info.unwrap();
1112        check_validator_stake_address(
1113            program_id,
1114            stake_pool_info.key,
1115            stake_account_info.key,
1116            &vote_account_address,
1117            NonZeroU32::new(validator_stake_info.validator_seed_suffix.into()),
1118        )?;
1119
1120        if validator_stake_info.status != StakeStatus::Active.into() {
1121            msg!("Validator is already marked for removal");
1122            return Err(StakePoolError::ValidatorNotFound.into());
1123        }
1124
1125        let new_status = if u64::from(validator_stake_info.transient_stake_lamports) > 0 {
1126            check_transient_stake_address(
1127                program_id,
1128                stake_pool_info.key,
1129                transient_stake_account_info.key,
1130                &vote_account_address,
1131                validator_stake_info.transient_seed_suffix.into(),
1132            )?;
1133
1134            match get_stake_state(transient_stake_account_info) {
1135                Ok((meta, stake))
1136                    if stake_is_usable_by_pool(
1137                        &meta,
1138                        withdraw_authority_info.key,
1139                        &stake_pool.lockup,
1140                    ) =>
1141                {
1142                    if stake.delegation.deactivation_epoch == Epoch::MAX {
1143                        Self::stake_deactivate(
1144                            transient_stake_account_info.clone(),
1145                            clock_info.clone(),
1146                            withdraw_authority_info.clone(),
1147                            stake_pool_info.key,
1148                            AUTHORITY_WITHDRAW,
1149                            stake_pool.stake_withdraw_bump_seed,
1150                        )?;
1151                    }
1152                }
1153                _ => (),
1154            }
1155            StakeStatus::DeactivatingAll
1156        } else {
1157            StakeStatus::DeactivatingValidator
1158        };
1159
1160        // If the stake was force-deactivated through deactivate-delinquent or
1161        // some other means, we *do not* need to deactivate it again
1162        if stake.delegation.deactivation_epoch == Epoch::MAX {
1163            Self::stake_deactivate(
1164                stake_account_info.clone(),
1165                clock_info.clone(),
1166                withdraw_authority_info.clone(),
1167                stake_pool_info.key,
1168                AUTHORITY_WITHDRAW,
1169                stake_pool.stake_withdraw_bump_seed,
1170            )?;
1171        }
1172
1173        validator_stake_info.status = new_status.into();
1174
1175        if stake_pool.preferred_deposit_validator_vote_address == Some(vote_account_address) {
1176            stake_pool.preferred_deposit_validator_vote_address = None;
1177        }
1178        if stake_pool.preferred_withdraw_validator_vote_address == Some(vote_account_address) {
1179            stake_pool.preferred_withdraw_validator_vote_address = None;
1180        }
1181        borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
1182
1183        Ok(())
1184    }
1185
1186    /// Processes `DecreaseValidatorStake` instruction.
1187    #[inline(never)] // needed due to stack size violation
1188    fn process_decrease_validator_stake(
1189        program_id: &Pubkey,
1190        accounts: &[AccountInfo],
1191        lamports: u64,
1192        transient_stake_seed: u64,
1193        maybe_ephemeral_stake_seed: Option<u64>,
1194        fund_rent_exempt_reserve: bool,
1195    ) -> ProgramResult {
1196        let account_info_iter = &mut accounts.iter();
1197        let stake_pool_info = next_account_info(account_info_iter)?;
1198        let staker_info = next_account_info(account_info_iter)?;
1199        let withdraw_authority_info = next_account_info(account_info_iter)?;
1200        let validator_list_info = next_account_info(account_info_iter)?;
1201        let maybe_reserve_stake_info = fund_rent_exempt_reserve
1202            .then(|| next_account_info(account_info_iter))
1203            .transpose()?;
1204        let validator_stake_account_info = next_account_info(account_info_iter)?;
1205        let maybe_ephemeral_stake_account_info = maybe_ephemeral_stake_seed
1206            .map(|_| next_account_info(account_info_iter))
1207            .transpose()?;
1208        let transient_stake_account_info = next_account_info(account_info_iter)?;
1209        let clock_info = next_account_info(account_info_iter)?;
1210        let clock = &Clock::from_account_info(clock_info)?;
1211        let (rent, maybe_stake_history_info) =
1212            if maybe_ephemeral_stake_seed.is_some() || fund_rent_exempt_reserve {
1213                (Rent::get()?, Some(next_account_info(account_info_iter)?))
1214            } else {
1215                // legacy instruction takes the rent account
1216                let rent_info = next_account_info(account_info_iter)?;
1217                (Rent::from_account_info(rent_info)?, None)
1218            };
1219        let system_program_info = next_account_info(account_info_iter)?;
1220        let stake_program_info = next_account_info(account_info_iter)?;
1221
1222        check_system_program(system_program_info.key)?;
1223        check_stake_program(stake_program_info.key)?;
1224        check_account_owner(stake_pool_info, program_id)?;
1225
1226        let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
1227        if !stake_pool.is_valid() {
1228            msg!("Expected valid stake pool");
1229            return Err(StakePoolError::InvalidState.into());
1230        }
1231
1232        stake_pool.check_authority_withdraw(
1233            withdraw_authority_info.key,
1234            program_id,
1235            stake_pool_info.key,
1236        )?;
1237        stake_pool.check_staker(staker_info)?;
1238
1239        if stake_pool.last_update_epoch < clock.epoch {
1240            return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
1241        }
1242
1243        stake_pool.check_validator_list(validator_list_info)?;
1244        check_account_owner(validator_list_info, program_id)?;
1245        let validator_list_data = &mut *validator_list_info.data.borrow_mut();
1246        let (validator_list_header, mut validator_list) =
1247            ValidatorListHeader::deserialize_vec(validator_list_data)?;
1248        if !validator_list_header.is_valid() {
1249            return Err(StakePoolError::InvalidState.into());
1250        }
1251
1252        if let Some(reserve_stake_info) = maybe_reserve_stake_info {
1253            stake_pool.check_reserve_stake(reserve_stake_info)?;
1254        }
1255
1256        let (meta, stake) = get_stake_state(validator_stake_account_info)?;
1257        let vote_account_address = stake.delegation.voter_pubkey;
1258
1259        let maybe_validator_stake_info = validator_list.find_mut::<ValidatorStakeInfo, _>(|x| {
1260            ValidatorStakeInfo::memcmp_pubkey(x, &vote_account_address)
1261        });
1262        if maybe_validator_stake_info.is_none() {
1263            msg!(
1264                "Vote account {} not found in stake pool",
1265                vote_account_address
1266            );
1267            return Err(StakePoolError::ValidatorNotFound.into());
1268        }
1269        let validator_stake_info = maybe_validator_stake_info.unwrap();
1270        check_validator_stake_address(
1271            program_id,
1272            stake_pool_info.key,
1273            validator_stake_account_info.key,
1274            &vote_account_address,
1275            NonZeroU32::new(validator_stake_info.validator_seed_suffix.into()),
1276        )?;
1277        if u64::from(validator_stake_info.transient_stake_lamports) > 0 {
1278            if maybe_ephemeral_stake_seed.is_none() {
1279                msg!("Attempting to decrease stake on a validator with pending transient stake, use DecreaseAdditionalValidatorStake with the existing seed");
1280                return Err(StakePoolError::TransientAccountInUse.into());
1281            }
1282            if transient_stake_seed != u64::from(validator_stake_info.transient_seed_suffix) {
1283                msg!(
1284                    "Transient stake already exists with seed {}, you must use that one",
1285                    u64::from(validator_stake_info.transient_seed_suffix)
1286                );
1287                return Err(ProgramError::InvalidSeeds);
1288            }
1289            check_if_stake_deactivating(
1290                transient_stake_account_info,
1291                &vote_account_address,
1292                clock.epoch,
1293            )?;
1294        }
1295
1296        if validator_stake_info.status != StakeStatus::Active.into() {
1297            msg!("Validator is marked for removal and no longer allows decreases");
1298            return Err(StakePoolError::ValidatorNotFound.into());
1299        }
1300
1301        let stake_space = std::mem::size_of::<stake::state::StakeStateV2>();
1302        let stake_rent = rent.minimum_balance(stake_space);
1303
1304        let stake_minimum_delegation = stake::tools::get_minimum_delegation()?;
1305        let current_minimum_lamports = minimum_delegation(stake_minimum_delegation);
1306        if lamports < current_minimum_lamports {
1307            msg!(
1308                "Need at least {} lamports for transient stake to meet minimum delegation and rent-exempt requirements, {} provided",
1309                current_minimum_lamports,
1310                lamports
1311            );
1312            return Err(ProgramError::AccountNotRentExempt);
1313        }
1314
1315        let remaining_lamports = validator_stake_account_info
1316            .lamports()
1317            .checked_sub(lamports)
1318            .ok_or(ProgramError::InsufficientFunds)?;
1319        let required_lamports = minimum_stake_lamports(&meta, stake_minimum_delegation);
1320        if remaining_lamports < required_lamports {
1321            msg!("Need at least {} lamports in the stake account after decrease, {} requested, {} is the current possible maximum",
1322                required_lamports,
1323                lamports,
1324                validator_stake_account_info.lamports().checked_sub(required_lamports).ok_or(StakePoolError::CalculationFailure)?
1325            );
1326            return Err(ProgramError::InsufficientFunds);
1327        }
1328
1329        let (source_stake_account_info, split_lamports) =
1330            if let Some((ephemeral_stake_seed, ephemeral_stake_account_info)) =
1331                maybe_ephemeral_stake_seed.zip(maybe_ephemeral_stake_account_info)
1332            {
1333                let ephemeral_stake_bump_seed = check_ephemeral_stake_address(
1334                    program_id,
1335                    stake_pool_info.key,
1336                    ephemeral_stake_account_info.key,
1337                    ephemeral_stake_seed,
1338                )?;
1339                let ephemeral_stake_account_signer_seeds: &[&[_]] = &[
1340                    EPHEMERAL_STAKE_SEED_PREFIX,
1341                    stake_pool_info.key.as_ref(),
1342                    &ephemeral_stake_seed.to_le_bytes(),
1343                    &[ephemeral_stake_bump_seed],
1344                ];
1345                create_stake_account(
1346                    ephemeral_stake_account_info.clone(),
1347                    ephemeral_stake_account_signer_seeds,
1348                    stake_space,
1349                )?;
1350
1351                // if needed, withdraw rent-exempt reserve for ephemeral account
1352                if let Some(reserve_stake_info) = maybe_reserve_stake_info {
1353                    let required_lamports_for_rent_exemption =
1354                        stake_rent.saturating_sub(ephemeral_stake_account_info.lamports());
1355                    if required_lamports_for_rent_exemption > 0 {
1356                        if required_lamports_for_rent_exemption >= reserve_stake_info.lamports() {
1357                            return Err(StakePoolError::ReserveDepleted.into());
1358                        }
1359                        let stake_history_info = maybe_stake_history_info
1360                            .ok_or(StakePoolError::MissingRequiredSysvar)?;
1361                        Self::stake_withdraw(
1362                            stake_pool_info.key,
1363                            reserve_stake_info.clone(),
1364                            withdraw_authority_info.clone(),
1365                            AUTHORITY_WITHDRAW,
1366                            stake_pool.stake_withdraw_bump_seed,
1367                            ephemeral_stake_account_info.clone(),
1368                            clock_info.clone(),
1369                            stake_history_info.clone(),
1370                            required_lamports_for_rent_exemption,
1371                        )?;
1372                    }
1373                }
1374
1375                // split into ephemeral stake account
1376                Self::stake_split(
1377                    stake_pool_info.key,
1378                    validator_stake_account_info.clone(),
1379                    withdraw_authority_info.clone(),
1380                    AUTHORITY_WITHDRAW,
1381                    stake_pool.stake_withdraw_bump_seed,
1382                    lamports,
1383                    ephemeral_stake_account_info.clone(),
1384                )?;
1385
1386                Self::stake_deactivate(
1387                    ephemeral_stake_account_info.clone(),
1388                    clock_info.clone(),
1389                    withdraw_authority_info.clone(),
1390                    stake_pool_info.key,
1391                    AUTHORITY_WITHDRAW,
1392                    stake_pool.stake_withdraw_bump_seed,
1393                )?;
1394
1395                (
1396                    ephemeral_stake_account_info,
1397                    ephemeral_stake_account_info.lamports(),
1398                )
1399            } else {
1400                // if no ephemeral account is provided, split everything from the
1401                // validator stake account, into the transient stake account
1402                (validator_stake_account_info, lamports)
1403            };
1404
1405        let transient_stake_bump_seed = check_transient_stake_address(
1406            program_id,
1407            stake_pool_info.key,
1408            transient_stake_account_info.key,
1409            &vote_account_address,
1410            transient_stake_seed,
1411        )?;
1412
1413        if u64::from(validator_stake_info.transient_stake_lamports) > 0 {
1414            let stake_history_info = maybe_stake_history_info.unwrap();
1415            // transient stake exists, try to merge from the source account,
1416            // which is always an ephemeral account
1417            Self::stake_merge(
1418                stake_pool_info.key,
1419                source_stake_account_info.clone(),
1420                withdraw_authority_info.clone(),
1421                AUTHORITY_WITHDRAW,
1422                stake_pool.stake_withdraw_bump_seed,
1423                transient_stake_account_info.clone(),
1424                clock_info.clone(),
1425                stake_history_info.clone(),
1426            )?;
1427        } else {
1428            let transient_stake_account_signer_seeds: &[&[_]] = &[
1429                TRANSIENT_STAKE_SEED_PREFIX,
1430                vote_account_address.as_ref(),
1431                stake_pool_info.key.as_ref(),
1432                &transient_stake_seed.to_le_bytes(),
1433                &[transient_stake_bump_seed],
1434            ];
1435
1436            create_stake_account(
1437                transient_stake_account_info.clone(),
1438                transient_stake_account_signer_seeds,
1439                stake_space,
1440            )?;
1441
1442            // if needed, withdraw rent-exempt reserve for transient account
1443            if let Some(reserve_stake_info) = maybe_reserve_stake_info {
1444                let required_lamports =
1445                    stake_rent.saturating_sub(transient_stake_account_info.lamports());
1446                // in the case of doing a full split from an ephemeral account,
1447                // the rent-exempt reserve moves over, so no need to fund it from
1448                // the pool reserve
1449                if source_stake_account_info.lamports() != split_lamports {
1450                    let stake_history_info =
1451                        maybe_stake_history_info.ok_or(StakePoolError::MissingRequiredSysvar)?;
1452                    if required_lamports >= reserve_stake_info.lamports() {
1453                        return Err(StakePoolError::ReserveDepleted.into());
1454                    }
1455                    if required_lamports > 0 {
1456                        Self::stake_withdraw(
1457                            stake_pool_info.key,
1458                            reserve_stake_info.clone(),
1459                            withdraw_authority_info.clone(),
1460                            AUTHORITY_WITHDRAW,
1461                            stake_pool.stake_withdraw_bump_seed,
1462                            transient_stake_account_info.clone(),
1463                            clock_info.clone(),
1464                            stake_history_info.clone(),
1465                            required_lamports,
1466                        )?;
1467                    }
1468                }
1469            }
1470
1471            // split into transient stake account
1472            Self::stake_split(
1473                stake_pool_info.key,
1474                source_stake_account_info.clone(),
1475                withdraw_authority_info.clone(),
1476                AUTHORITY_WITHDRAW,
1477                stake_pool.stake_withdraw_bump_seed,
1478                split_lamports,
1479                transient_stake_account_info.clone(),
1480            )?;
1481
1482            // Deactivate transient stake if necessary
1483            let (_, stake) = get_stake_state(transient_stake_account_info)?;
1484            if stake.delegation.deactivation_epoch == Epoch::MAX {
1485                Self::stake_deactivate(
1486                    transient_stake_account_info.clone(),
1487                    clock_info.clone(),
1488                    withdraw_authority_info.clone(),
1489                    stake_pool_info.key,
1490                    AUTHORITY_WITHDRAW,
1491                    stake_pool.stake_withdraw_bump_seed,
1492                )?;
1493            }
1494        }
1495
1496        validator_stake_info.active_stake_lamports =
1497            u64::from(validator_stake_info.active_stake_lamports)
1498                .checked_sub(lamports)
1499                .ok_or(StakePoolError::CalculationFailure)?
1500                .into();
1501        validator_stake_info.transient_stake_lamports =
1502            transient_stake_account_info.lamports().into();
1503        validator_stake_info.transient_seed_suffix = transient_stake_seed.into();
1504
1505        Ok(())
1506    }
1507
1508    /// Processes `IncreaseValidatorStake` instruction.
1509    #[inline(never)] // needed due to stack size violation
1510    fn process_increase_validator_stake(
1511        program_id: &Pubkey,
1512        accounts: &[AccountInfo],
1513        lamports: u64,
1514        transient_stake_seed: u64,
1515        maybe_ephemeral_stake_seed: Option<u64>,
1516    ) -> ProgramResult {
1517        let account_info_iter = &mut accounts.iter();
1518        let stake_pool_info = next_account_info(account_info_iter)?;
1519        let staker_info = next_account_info(account_info_iter)?;
1520        let withdraw_authority_info = next_account_info(account_info_iter)?;
1521        let validator_list_info = next_account_info(account_info_iter)?;
1522        let reserve_stake_account_info = next_account_info(account_info_iter)?;
1523        let maybe_ephemeral_stake_account_info = maybe_ephemeral_stake_seed
1524            .map(|_| next_account_info(account_info_iter))
1525            .transpose()?;
1526        let transient_stake_account_info = next_account_info(account_info_iter)?;
1527        let validator_stake_account_info = next_account_info(account_info_iter)?;
1528        let validator_vote_account_info = next_account_info(account_info_iter)?;
1529        let clock_info = next_account_info(account_info_iter)?;
1530        let clock = &Clock::from_account_info(clock_info)?;
1531        let rent = if maybe_ephemeral_stake_seed.is_some() {
1532            // instruction with ephemeral account doesn't take the rent account
1533            Rent::get()?
1534        } else {
1535            // legacy instruction takes the rent account
1536            let rent_info = next_account_info(account_info_iter)?;
1537            Rent::from_account_info(rent_info)?
1538        };
1539        let stake_history_info = next_account_info(account_info_iter)?;
1540        let stake_config_info = next_account_info(account_info_iter)?;
1541        let system_program_info = next_account_info(account_info_iter)?;
1542        let stake_program_info = next_account_info(account_info_iter)?;
1543
1544        check_system_program(system_program_info.key)?;
1545        check_stake_program(stake_program_info.key)?;
1546        check_account_owner(stake_pool_info, program_id)?;
1547
1548        let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
1549        if !stake_pool.is_valid() {
1550            msg!("Expected valid stake pool");
1551            return Err(StakePoolError::InvalidState.into());
1552        }
1553
1554        stake_pool.check_authority_withdraw(
1555            withdraw_authority_info.key,
1556            program_id,
1557            stake_pool_info.key,
1558        )?;
1559        stake_pool.check_staker(staker_info)?;
1560
1561        if stake_pool.last_update_epoch < clock.epoch {
1562            return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
1563        }
1564
1565        stake_pool.check_validator_list(validator_list_info)?;
1566        stake_pool.check_reserve_stake(reserve_stake_account_info)?;
1567        check_account_owner(validator_list_info, program_id)?;
1568
1569        let mut validator_list_data = validator_list_info.data.borrow_mut();
1570        let (header, mut validator_list) =
1571            ValidatorListHeader::deserialize_vec(&mut validator_list_data)?;
1572        if !header.is_valid() {
1573            return Err(StakePoolError::InvalidState.into());
1574        }
1575
1576        let vote_account_address = validator_vote_account_info.key;
1577
1578        let maybe_validator_stake_info = validator_list.find_mut::<ValidatorStakeInfo, _>(|x| {
1579            ValidatorStakeInfo::memcmp_pubkey(x, vote_account_address)
1580        });
1581        if maybe_validator_stake_info.is_none() {
1582            msg!(
1583                "Vote account {} not found in stake pool",
1584                vote_account_address
1585            );
1586            return Err(StakePoolError::ValidatorNotFound.into());
1587        }
1588        let validator_stake_info = maybe_validator_stake_info.unwrap();
1589        if u64::from(validator_stake_info.transient_stake_lamports) > 0 {
1590            if maybe_ephemeral_stake_seed.is_none() {
1591                msg!("Attempting to increase stake on a validator with pending transient stake, use IncreaseAdditionalValidatorStake with the existing seed");
1592                return Err(StakePoolError::TransientAccountInUse.into());
1593            }
1594            if transient_stake_seed != u64::from(validator_stake_info.transient_seed_suffix) {
1595                msg!(
1596                    "Transient stake already exists with seed {}, you must use that one",
1597                    u64::from(validator_stake_info.transient_seed_suffix)
1598                );
1599                return Err(ProgramError::InvalidSeeds);
1600            }
1601            check_if_stake_activating(
1602                transient_stake_account_info,
1603                vote_account_address,
1604                clock.epoch,
1605            )?;
1606        }
1607
1608        check_validator_stake_account(
1609            validator_stake_account_info,
1610            program_id,
1611            stake_pool_info.key,
1612            withdraw_authority_info.key,
1613            vote_account_address,
1614            validator_stake_info.validator_seed_suffix.into(),
1615            &stake_pool.lockup,
1616        )?;
1617
1618        if validator_stake_info.status != StakeStatus::Active.into() {
1619            msg!("Validator is marked for removal and no longer allows increases");
1620            return Err(StakePoolError::ValidatorNotFound.into());
1621        }
1622
1623        let stake_space = std::mem::size_of::<stake::state::StakeStateV2>();
1624        let stake_rent = rent.minimum_balance(stake_space);
1625        let stake_minimum_delegation = stake::tools::get_minimum_delegation()?;
1626        let current_minimum_delegation = minimum_delegation(stake_minimum_delegation);
1627        if lamports < current_minimum_delegation {
1628            msg!(
1629                "Need more than {} lamports for transient stake to meet minimum delegation requirement, {} provided",
1630                current_minimum_delegation,
1631                lamports
1632            );
1633            return Err(ProgramError::Custom(
1634                stake::error::StakeError::InsufficientDelegation as u32,
1635            ));
1636        }
1637
1638        // the stake account rent exemption is withdrawn after the merge, so
1639        // to add `lamports` to a validator, we need to create a stake account
1640        // with `lamports + stake_rent`
1641        let total_lamports = lamports.saturating_add(stake_rent);
1642
1643        if reserve_stake_account_info
1644            .lamports()
1645            .saturating_sub(total_lamports)
1646            < stake_rent
1647        {
1648            let max_split_amount = reserve_stake_account_info
1649                .lamports()
1650                .saturating_sub(stake_rent.saturating_mul(2));
1651            msg!(
1652                "Reserve stake does not have enough lamports for increase, maximum amount {}, {} requested",
1653                max_split_amount,
1654                lamports
1655            );
1656            return Err(ProgramError::InsufficientFunds);
1657        }
1658
1659        let source_stake_account_info =
1660            if let Some((ephemeral_stake_seed, ephemeral_stake_account_info)) =
1661                maybe_ephemeral_stake_seed.zip(maybe_ephemeral_stake_account_info)
1662            {
1663                let ephemeral_stake_bump_seed = check_ephemeral_stake_address(
1664                    program_id,
1665                    stake_pool_info.key,
1666                    ephemeral_stake_account_info.key,
1667                    ephemeral_stake_seed,
1668                )?;
1669                let ephemeral_stake_account_signer_seeds: &[&[_]] = &[
1670                    EPHEMERAL_STAKE_SEED_PREFIX,
1671                    stake_pool_info.key.as_ref(),
1672                    &ephemeral_stake_seed.to_le_bytes(),
1673                    &[ephemeral_stake_bump_seed],
1674                ];
1675                create_stake_account(
1676                    ephemeral_stake_account_info.clone(),
1677                    ephemeral_stake_account_signer_seeds,
1678                    stake_space,
1679                )?;
1680
1681                // split into ephemeral stake account
1682                Self::stake_split(
1683                    stake_pool_info.key,
1684                    reserve_stake_account_info.clone(),
1685                    withdraw_authority_info.clone(),
1686                    AUTHORITY_WITHDRAW,
1687                    stake_pool.stake_withdraw_bump_seed,
1688                    total_lamports,
1689                    ephemeral_stake_account_info.clone(),
1690                )?;
1691
1692                // activate stake to validator
1693                Self::stake_delegate(
1694                    ephemeral_stake_account_info.clone(),
1695                    validator_vote_account_info.clone(),
1696                    clock_info.clone(),
1697                    stake_history_info.clone(),
1698                    stake_config_info.clone(),
1699                    withdraw_authority_info.clone(),
1700                    stake_pool_info.key,
1701                    AUTHORITY_WITHDRAW,
1702                    stake_pool.stake_withdraw_bump_seed,
1703                )?;
1704                ephemeral_stake_account_info
1705            } else {
1706                // if no ephemeral account is provided, split everything from the
1707                // reserve account, into the transient stake account
1708                reserve_stake_account_info
1709            };
1710
1711        let transient_stake_bump_seed = check_transient_stake_address(
1712            program_id,
1713            stake_pool_info.key,
1714            transient_stake_account_info.key,
1715            vote_account_address,
1716            transient_stake_seed,
1717        )?;
1718
1719        if u64::from(validator_stake_info.transient_stake_lamports) > 0 {
1720            // transient stake exists, try to merge from the source account,
1721            // which is always an ephemeral account
1722            Self::stake_merge(
1723                stake_pool_info.key,
1724                source_stake_account_info.clone(),
1725                withdraw_authority_info.clone(),
1726                AUTHORITY_WITHDRAW,
1727                stake_pool.stake_withdraw_bump_seed,
1728                transient_stake_account_info.clone(),
1729                clock_info.clone(),
1730                stake_history_info.clone(),
1731            )?;
1732        } else {
1733            // no transient stake, split
1734            let transient_stake_account_signer_seeds: &[&[_]] = &[
1735                TRANSIENT_STAKE_SEED_PREFIX,
1736                vote_account_address.as_ref(),
1737                stake_pool_info.key.as_ref(),
1738                &transient_stake_seed.to_le_bytes(),
1739                &[transient_stake_bump_seed],
1740            ];
1741
1742            create_stake_account(
1743                transient_stake_account_info.clone(),
1744                transient_stake_account_signer_seeds,
1745                stake_space,
1746            )?;
1747
1748            // split into transient stake account
1749            Self::stake_split(
1750                stake_pool_info.key,
1751                source_stake_account_info.clone(),
1752                withdraw_authority_info.clone(),
1753                AUTHORITY_WITHDRAW,
1754                stake_pool.stake_withdraw_bump_seed,
1755                total_lamports,
1756                transient_stake_account_info.clone(),
1757            )?;
1758
1759            // Activate transient stake to validator if necessary
1760            let stake_state = try_from_slice_unchecked::<stake::state::StakeStateV2>(
1761                &transient_stake_account_info.data.borrow(),
1762            )?;
1763            match stake_state {
1764                // if it was delegated on or before this epoch, we're good
1765                stake::state::StakeStateV2::Stake(_, stake, _)
1766                    if stake.delegation.activation_epoch <= clock.epoch => {}
1767                // all other situations, delegate!
1768                _ => {
1769                    Self::stake_delegate(
1770                        transient_stake_account_info.clone(),
1771                        validator_vote_account_info.clone(),
1772                        clock_info.clone(),
1773                        stake_history_info.clone(),
1774                        stake_config_info.clone(),
1775                        withdraw_authority_info.clone(),
1776                        stake_pool_info.key,
1777                        AUTHORITY_WITHDRAW,
1778                        stake_pool.stake_withdraw_bump_seed,
1779                    )?;
1780                }
1781            }
1782        }
1783
1784        validator_stake_info.transient_stake_lamports =
1785            u64::from(validator_stake_info.transient_stake_lamports)
1786                .checked_add(total_lamports)
1787                .ok_or(StakePoolError::CalculationFailure)?
1788                .into();
1789        validator_stake_info.transient_seed_suffix = transient_stake_seed.into();
1790
1791        Ok(())
1792    }
1793
1794    /// Process `SetPreferredValidator` instruction
1795    #[inline(never)] // needed due to stack size violation
1796    fn process_set_preferred_validator(
1797        program_id: &Pubkey,
1798        accounts: &[AccountInfo],
1799        validator_type: PreferredValidatorType,
1800        vote_account_address: Option<Pubkey>,
1801    ) -> ProgramResult {
1802        let account_info_iter = &mut accounts.iter();
1803        let stake_pool_info = next_account_info(account_info_iter)?;
1804        let staker_info = next_account_info(account_info_iter)?;
1805        let validator_list_info = next_account_info(account_info_iter)?;
1806
1807        check_account_owner(stake_pool_info, program_id)?;
1808        check_account_owner(validator_list_info, program_id)?;
1809
1810        let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
1811        if !stake_pool.is_valid() {
1812            msg!("Expected valid stake pool");
1813            return Err(StakePoolError::InvalidState.into());
1814        }
1815
1816        stake_pool.check_staker(staker_info)?;
1817        stake_pool.check_validator_list(validator_list_info)?;
1818
1819        let mut validator_list_data = validator_list_info.data.borrow_mut();
1820        let (header, validator_list) =
1821            ValidatorListHeader::deserialize_vec(&mut validator_list_data)?;
1822        if !header.is_valid() {
1823            return Err(StakePoolError::InvalidState.into());
1824        }
1825
1826        if let Some(vote_account_address) = vote_account_address {
1827            let maybe_validator_stake_info = validator_list.find::<ValidatorStakeInfo, _>(|x| {
1828                ValidatorStakeInfo::memcmp_pubkey(x, &vote_account_address)
1829            });
1830            match maybe_validator_stake_info {
1831                Some(vsi) => {
1832                    if vsi.status != StakeStatus::Active.into() {
1833                        msg!("Validator for {:?} about to be removed, cannot set as preferred deposit account", validator_type);
1834                        return Err(StakePoolError::InvalidPreferredValidator.into());
1835                    }
1836                }
1837                None => {
1838                    msg!("Validator for {:?} not present in the stake pool, cannot set as preferred deposit account", validator_type);
1839                    return Err(StakePoolError::ValidatorNotFound.into());
1840                }
1841            }
1842        }
1843
1844        match validator_type {
1845            PreferredValidatorType::Deposit => {
1846                stake_pool.preferred_deposit_validator_vote_address = vote_account_address
1847            }
1848            PreferredValidatorType::Withdraw => {
1849                stake_pool.preferred_withdraw_validator_vote_address = vote_account_address
1850            }
1851        };
1852        borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
1853        Ok(())
1854    }
1855
1856    /// Processes `UpdateValidatorListBalance` instruction.
1857    #[inline(always)] // needed to maximize number of validators
1858    fn process_update_validator_list_balance(
1859        program_id: &Pubkey,
1860        accounts: &[AccountInfo],
1861        start_index: u32,
1862        no_merge: bool,
1863    ) -> ProgramResult {
1864        let account_info_iter = &mut accounts.iter();
1865        let stake_pool_info = next_account_info(account_info_iter)?;
1866        let withdraw_authority_info = next_account_info(account_info_iter)?;
1867        let validator_list_info = next_account_info(account_info_iter)?;
1868        let reserve_stake_info = next_account_info(account_info_iter)?;
1869        let clock_info = next_account_info(account_info_iter)?;
1870        let clock = &Clock::from_account_info(clock_info)?;
1871        let stake_history_info = next_account_info(account_info_iter)?;
1872        let stake_program_info = next_account_info(account_info_iter)?;
1873        let validator_stake_accounts = account_info_iter.as_slice();
1874
1875        check_account_owner(stake_pool_info, program_id)?;
1876        let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
1877        if !stake_pool.is_valid() {
1878            return Err(StakePoolError::InvalidState.into());
1879        }
1880        stake_pool.check_validator_list(validator_list_info)?;
1881        stake_pool.check_authority_withdraw(
1882            withdraw_authority_info.key,
1883            program_id,
1884            stake_pool_info.key,
1885        )?;
1886        stake_pool.check_reserve_stake(reserve_stake_info)?;
1887        check_stake_program(stake_program_info.key)?;
1888
1889        // If rewards are being distributed, abort
1890        let epoch_rewards = EpochRewards::get()?;
1891        if epoch_rewards.active {
1892            return Err(StakePoolError::EpochRewardDistributionInProgress.into());
1893        }
1894
1895        if validator_stake_accounts
1896            .len()
1897            .checked_rem(2)
1898            .ok_or(StakePoolError::CalculationFailure)?
1899            != 0
1900        {
1901            msg!("Odd number of validator stake accounts passed in, should be pairs of validator stake and transient stake accounts");
1902            return Err(StakePoolError::UnexpectedValidatorListAccountSize.into());
1903        }
1904
1905        check_account_owner(validator_list_info, program_id)?;
1906        let mut validator_list_data = validator_list_info.data.borrow_mut();
1907        let (validator_list_header, mut big_vec) =
1908            ValidatorListHeader::deserialize_vec(&mut validator_list_data)?;
1909        let validator_slice = ValidatorListHeader::deserialize_mut_slice(
1910            &mut big_vec,
1911            start_index as usize,
1912            validator_stake_accounts.len() / 2,
1913        )?;
1914
1915        if !validator_list_header.is_valid() {
1916            return Err(StakePoolError::InvalidState.into());
1917        }
1918
1919        let validator_iter = &mut validator_slice
1920            .iter_mut()
1921            .zip(validator_stake_accounts.chunks_exact(2));
1922        for (validator_stake_record, validator_stakes) in validator_iter {
1923            // chunks_exact means that we always get 2 elements, making this safe
1924            let validator_stake_info = validator_stakes
1925                .first()
1926                .ok_or(ProgramError::InvalidInstructionData)?;
1927            let transient_stake_info = validator_stakes
1928                .last()
1929                .ok_or(ProgramError::InvalidInstructionData)?;
1930            if check_validator_stake_address(
1931                program_id,
1932                stake_pool_info.key,
1933                validator_stake_info.key,
1934                &validator_stake_record.vote_account_address,
1935                NonZeroU32::new(validator_stake_record.validator_seed_suffix.into()),
1936            )
1937            .is_err()
1938            {
1939                continue;
1940            };
1941            if check_transient_stake_address(
1942                program_id,
1943                stake_pool_info.key,
1944                transient_stake_info.key,
1945                &validator_stake_record.vote_account_address,
1946                validator_stake_record.transient_seed_suffix.into(),
1947            )
1948            .is_err()
1949            {
1950                continue;
1951            };
1952
1953            let mut active_stake_lamports = 0;
1954            let mut transient_stake_lamports = 0;
1955            let validator_stake_state = try_from_slice_unchecked::<stake::state::StakeStateV2>(
1956                &validator_stake_info.data.borrow(),
1957            )
1958            .ok();
1959            let transient_stake_state = try_from_slice_unchecked::<stake::state::StakeStateV2>(
1960                &transient_stake_info.data.borrow(),
1961            )
1962            .ok();
1963
1964            // Possible merge situations for transient stake
1965            //  * active -> merge into validator stake
1966            //  * activating -> nothing, just account its lamports
1967            //  * deactivating -> nothing, just account its lamports
1968            //  * inactive -> merge into reserve stake
1969            //  * not a stake -> ignore
1970            if validator_stake_record.transient_stake_lamports != 0.into() {
1971                match transient_stake_state {
1972                    Some(stake::state::StakeStateV2::Initialized(meta)) => {
1973                        if stake_is_usable_by_pool(
1974                            &meta,
1975                            withdraw_authority_info.key,
1976                            &stake_pool.lockup,
1977                        ) {
1978                            if no_merge {
1979                                transient_stake_lamports = transient_stake_info.lamports();
1980                            } else {
1981                                // merge into reserve
1982                                Self::stake_merge(
1983                                    stake_pool_info.key,
1984                                    transient_stake_info.clone(),
1985                                    withdraw_authority_info.clone(),
1986                                    AUTHORITY_WITHDRAW,
1987                                    stake_pool.stake_withdraw_bump_seed,
1988                                    reserve_stake_info.clone(),
1989                                    clock_info.clone(),
1990                                    stake_history_info.clone(),
1991                                )?;
1992                                validator_stake_record.status.remove_transient_stake()?;
1993                            }
1994                        }
1995                    }
1996                    Some(stake::state::StakeStateV2::Stake(meta, stake, _)) => {
1997                        if stake_is_usable_by_pool(
1998                            &meta,
1999                            withdraw_authority_info.key,
2000                            &stake_pool.lockup,
2001                        ) {
2002                            if !no_merge {
2003                                if stake_is_inactive_without_history(&stake, clock.epoch) {
2004                                    // deactivated, merge into reserve
2005                                    Self::stake_merge(
2006                                        stake_pool_info.key,
2007                                        transient_stake_info.clone(),
2008                                        withdraw_authority_info.clone(),
2009                                        AUTHORITY_WITHDRAW,
2010                                        stake_pool.stake_withdraw_bump_seed,
2011                                        reserve_stake_info.clone(),
2012                                        clock_info.clone(),
2013                                        stake_history_info.clone(),
2014                                    )?;
2015                                    validator_stake_record.status.remove_transient_stake()?;
2016                                } else if validator_stake_record.status.try_into()
2017                                    == Ok(StakeStatus::Active)
2018                                {
2019                                    if stake.delegation.activation_epoch < clock.epoch {
2020                                        if let Some(stake::state::StakeStateV2::Stake(
2021                                            _,
2022                                            validator_stake,
2023                                            _,
2024                                        )) = validator_stake_state
2025                                        {
2026                                            if validator_stake.delegation.activation_epoch
2027                                                < clock.epoch
2028                                            {
2029                                                Self::stake_merge(
2030                                                    stake_pool_info.key,
2031                                                    transient_stake_info.clone(),
2032                                                    withdraw_authority_info.clone(),
2033                                                    AUTHORITY_WITHDRAW,
2034                                                    stake_pool.stake_withdraw_bump_seed,
2035                                                    validator_stake_info.clone(),
2036                                                    clock_info.clone(),
2037                                                    stake_history_info.clone(),
2038                                                )?;
2039                                            } else {
2040                                                msg!("Stake activating or just active, not ready to merge");
2041                                            }
2042                                        } else {
2043                                            msg!("Transient stake is activating or active, but validator stake is not, need to add the validator stake account on {} back into the stake pool", stake.delegation.voter_pubkey);
2044                                        }
2045                                    } else {
2046                                        msg!("Transient stake not ready to be merged anywhere");
2047                                    }
2048                                } else if stake.delegation.deactivation_epoch == Epoch::MAX {
2049                                    msg!("Transient stake is activating or active, deactivating.");
2050                                    Self::stake_deactivate(
2051                                        transient_stake_info.clone(),
2052                                        clock_info.clone(),
2053                                        withdraw_authority_info.clone(),
2054                                        stake_pool_info.key,
2055                                        AUTHORITY_WITHDRAW,
2056                                        stake_pool.stake_withdraw_bump_seed,
2057                                    )?;
2058                                }
2059                            }
2060                            transient_stake_lamports = transient_stake_info.lamports();
2061                        }
2062                    }
2063                    None
2064                    | Some(stake::state::StakeStateV2::Uninitialized)
2065                    | Some(stake::state::StakeStateV2::RewardsPool) => {} // do nothing
2066                }
2067            }
2068            // Status for validator stake
2069            //  * active -> do everything
2070            //  * any other state / not a stake -> error state, but account for transient
2071            //    stake
2072            let validator_stake_state = try_from_slice_unchecked::<stake::state::StakeStateV2>(
2073                &validator_stake_info.data.borrow(),
2074            )
2075            .ok();
2076            match validator_stake_state {
2077                Some(stake::state::StakeStateV2::Stake(meta, stake, _))
2078                    if stake_is_usable_by_pool(
2079                        &meta,
2080                        withdraw_authority_info.key,
2081                        &stake_pool.lockup,
2082                    ) =>
2083                {
2084                    let additional_lamports = validator_stake_info
2085                        .lamports()
2086                        .saturating_sub(stake.delegation.stake)
2087                        .saturating_sub(meta.rent_exempt_reserve);
2088                    // withdraw any extra lamports back to the reserve
2089                    if additional_lamports > 0 {
2090                        Self::stake_withdraw(
2091                            stake_pool_info.key,
2092                            validator_stake_info.clone(),
2093                            withdraw_authority_info.clone(),
2094                            AUTHORITY_WITHDRAW,
2095                            stake_pool.stake_withdraw_bump_seed,
2096                            reserve_stake_info.clone(),
2097                            clock_info.clone(),
2098                            stake_history_info.clone(),
2099                            additional_lamports,
2100                        )?;
2101                    }
2102                    match validator_stake_record.status.try_into()? {
2103                        StakeStatus::Active => {
2104                            active_stake_lamports = validator_stake_info.lamports();
2105                        }
2106                        StakeStatus::DeactivatingValidator | StakeStatus::DeactivatingAll => {
2107                            if no_merge {
2108                                active_stake_lamports = validator_stake_info.lamports();
2109                            } else if stake_is_inactive_without_history(&stake, clock.epoch) {
2110                                // Validator was removed through normal means.
2111                                // Absorb the lamports into the reserve.
2112                                Self::stake_merge(
2113                                    stake_pool_info.key,
2114                                    validator_stake_info.clone(),
2115                                    withdraw_authority_info.clone(),
2116                                    AUTHORITY_WITHDRAW,
2117                                    stake_pool.stake_withdraw_bump_seed,
2118                                    reserve_stake_info.clone(),
2119                                    clock_info.clone(),
2120                                    stake_history_info.clone(),
2121                                )?;
2122                                validator_stake_record.status.remove_validator_stake()?;
2123                            } else {
2124                                active_stake_lamports = validator_stake_info.lamports();
2125                            }
2126                        }
2127                        StakeStatus::DeactivatingTransient | StakeStatus::ReadyForRemoval => {
2128                            msg!("Validator stake account no longer part of the pool, ignoring");
2129                        }
2130                    }
2131                }
2132                Some(stake::state::StakeStateV2::Initialized(meta))
2133                    if stake_is_usable_by_pool(
2134                        &meta,
2135                        withdraw_authority_info.key,
2136                        &stake_pool.lockup,
2137                    ) =>
2138                {
2139                    // If a validator stake is `Initialized`, the validator could
2140                    // have been destaked during a cluster restart or removed through
2141                    // normal means. Either way, absorb those lamports into the reserve.
2142                    // The transient stake was likely absorbed into the reserve earlier.
2143                    Self::stake_merge(
2144                        stake_pool_info.key,
2145                        validator_stake_info.clone(),
2146                        withdraw_authority_info.clone(),
2147                        AUTHORITY_WITHDRAW,
2148                        stake_pool.stake_withdraw_bump_seed,
2149                        reserve_stake_info.clone(),
2150                        clock_info.clone(),
2151                        stake_history_info.clone(),
2152                    )?;
2153                    if transient_stake_lamports != 0 {
2154                        validator_stake_record.status = StakeStatus::DeactivatingTransient.into();
2155                    } else {
2156                        validator_stake_record.status = StakeStatus::ReadyForRemoval.into();
2157                    }
2158                }
2159                Some(stake::state::StakeStateV2::Stake(_, _, _))
2160                | Some(stake::state::StakeStateV2::Initialized(_))
2161                | Some(stake::state::StakeStateV2::Uninitialized)
2162                | Some(stake::state::StakeStateV2::RewardsPool)
2163                | None => {
2164                    msg!("Validator stake account no longer part of the pool, ignoring");
2165                }
2166            }
2167
2168            validator_stake_record.last_update_epoch = clock.epoch.into();
2169            validator_stake_record.active_stake_lamports = active_stake_lamports.into();
2170            validator_stake_record.transient_stake_lamports = transient_stake_lamports.into();
2171        }
2172
2173        Ok(())
2174    }
2175
2176    /// Processes `UpdateStakePoolBalance` instruction.
2177    #[inline(always)] // needed to optimize number of validators
2178    fn process_update_stake_pool_balance(
2179        program_id: &Pubkey,
2180        accounts: &[AccountInfo],
2181    ) -> ProgramResult {
2182        let account_info_iter = &mut accounts.iter();
2183        let stake_pool_info = next_account_info(account_info_iter)?;
2184        let withdraw_info = next_account_info(account_info_iter)?;
2185        let validator_list_info = next_account_info(account_info_iter)?;
2186        let reserve_stake_info = next_account_info(account_info_iter)?;
2187        let manager_fee_info = next_account_info(account_info_iter)?;
2188        let pool_mint_info = next_account_info(account_info_iter)?;
2189        let token_program_info = next_account_info(account_info_iter)?;
2190        let clock = Clock::get()?;
2191
2192        check_account_owner(stake_pool_info, program_id)?;
2193        let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
2194        if !stake_pool.is_valid() {
2195            return Err(StakePoolError::InvalidState.into());
2196        }
2197        stake_pool.check_mint(pool_mint_info)?;
2198        stake_pool.check_authority_withdraw(withdraw_info.key, program_id, stake_pool_info.key)?;
2199        stake_pool.check_reserve_stake(reserve_stake_info)?;
2200        if stake_pool.manager_fee_account != *manager_fee_info.key {
2201            return Err(StakePoolError::InvalidFeeAccount.into());
2202        }
2203
2204        if *validator_list_info.key != stake_pool.validator_list {
2205            return Err(StakePoolError::InvalidValidatorStakeList.into());
2206        }
2207        if stake_pool.token_program_id != *token_program_info.key {
2208            return Err(ProgramError::IncorrectProgramId);
2209        }
2210
2211        check_account_owner(validator_list_info, program_id)?;
2212        let mut validator_list_data = validator_list_info.data.borrow_mut();
2213        let (header, validator_list) =
2214            ValidatorListHeader::deserialize_vec(&mut validator_list_data)?;
2215        if !header.is_valid() {
2216            return Err(StakePoolError::InvalidState.into());
2217        }
2218
2219        let previous_lamports = stake_pool.total_lamports;
2220        let previous_pool_token_supply = stake_pool.pool_token_supply;
2221        let reserve_stake = try_from_slice_unchecked::<stake::state::StakeStateV2>(
2222            &reserve_stake_info.data.borrow(),
2223        )?;
2224        let mut total_lamports =
2225            if let stake::state::StakeStateV2::Initialized(meta) = reserve_stake {
2226                reserve_stake_info
2227                    .lamports()
2228                    .checked_sub(minimum_reserve_lamports(&meta))
2229                    .ok_or(StakePoolError::CalculationFailure)?
2230            } else {
2231                msg!("Reserve stake account in unknown state, aborting");
2232                return Err(StakePoolError::WrongStakeStake.into());
2233            };
2234        for validator_stake_record in validator_list
2235            .deserialize_slice::<ValidatorStakeInfo>(0, validator_list.len() as usize)?
2236        {
2237            if u64::from(validator_stake_record.last_update_epoch) < clock.epoch {
2238                return Err(StakePoolError::StakeListOutOfDate.into());
2239            }
2240            total_lamports = total_lamports
2241                .checked_add(validator_stake_record.stake_lamports()?)
2242                .ok_or(StakePoolError::CalculationFailure)?;
2243        }
2244
2245        let reward_lamports = total_lamports.saturating_sub(previous_lamports);
2246
2247        // If the manager fee info is invalid, they don't deserve to receive the fee.
2248        let fee = if stake_pool.check_manager_fee_info(manager_fee_info).is_ok() {
2249            stake_pool
2250                .calc_epoch_fee_amount(reward_lamports)
2251                .ok_or(StakePoolError::CalculationFailure)?
2252        } else {
2253            0
2254        };
2255
2256        if fee > 0 {
2257            Self::token_mint_to(
2258                stake_pool_info.key,
2259                token_program_info.clone(),
2260                pool_mint_info.clone(),
2261                manager_fee_info.clone(),
2262                withdraw_info.clone(),
2263                AUTHORITY_WITHDRAW,
2264                stake_pool.stake_withdraw_bump_seed,
2265                fee,
2266            )?;
2267        }
2268
2269        if stake_pool.last_update_epoch < clock.epoch {
2270            if let Some(fee) = stake_pool.next_epoch_fee.get() {
2271                stake_pool.epoch_fee = *fee;
2272            }
2273            stake_pool.next_epoch_fee.update_epoch();
2274
2275            if let Some(fee) = stake_pool.next_stake_withdrawal_fee.get() {
2276                stake_pool.stake_withdrawal_fee = *fee;
2277            }
2278            stake_pool.next_stake_withdrawal_fee.update_epoch();
2279
2280            if let Some(fee) = stake_pool.next_sol_withdrawal_fee.get() {
2281                stake_pool.sol_withdrawal_fee = *fee;
2282            }
2283            stake_pool.next_sol_withdrawal_fee.update_epoch();
2284
2285            stake_pool.last_update_epoch = clock.epoch;
2286            stake_pool.last_epoch_total_lamports = previous_lamports;
2287            stake_pool.last_epoch_pool_token_supply = previous_pool_token_supply;
2288        }
2289        stake_pool.total_lamports = total_lamports;
2290
2291        let pool_mint_data = pool_mint_info.try_borrow_data()?;
2292        let pool_mint = StateWithExtensions::<Mint>::unpack(&pool_mint_data)?;
2293        stake_pool.pool_token_supply = pool_mint.base.supply;
2294
2295        borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
2296
2297        Ok(())
2298    }
2299
2300    /// Processes the `CleanupRemovedValidatorEntries` instruction
2301    #[inline(never)] // needed to avoid stack size violation
2302    fn process_cleanup_removed_validator_entries(
2303        program_id: &Pubkey,
2304        accounts: &[AccountInfo],
2305    ) -> ProgramResult {
2306        let account_info_iter = &mut accounts.iter();
2307        let stake_pool_info = next_account_info(account_info_iter)?;
2308        let validator_list_info = next_account_info(account_info_iter)?;
2309
2310        check_account_owner(stake_pool_info, program_id)?;
2311        let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
2312        if !stake_pool.is_valid() {
2313            return Err(StakePoolError::InvalidState.into());
2314        }
2315        stake_pool.check_validator_list(validator_list_info)?;
2316
2317        check_account_owner(validator_list_info, program_id)?;
2318        let mut validator_list_data = validator_list_info.data.borrow_mut();
2319        let (header, mut validator_list) =
2320            ValidatorListHeader::deserialize_vec(&mut validator_list_data)?;
2321        if !header.is_valid() {
2322            return Err(StakePoolError::InvalidState.into());
2323        }
2324
2325        validator_list.retain::<ValidatorStakeInfo, _>(|x| !ValidatorStakeInfo::is_removed(x))?;
2326
2327        if stake_pool_info.is_writable {
2328            msg!("Checking preferred validators");
2329            let mut stake_pool =
2330                try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
2331
2332            // Check and reset preferred validators if they don't exist or aren't active
2333            // Check preferred deposit validator
2334            if let Some(preferred_deposit) = stake_pool.preferred_deposit_validator_vote_address {
2335                let maybe_validator = validator_list.find::<ValidatorStakeInfo, _>(|x| {
2336                    ValidatorStakeInfo::memcmp_pubkey(x, &preferred_deposit)
2337                });
2338
2339                let should_reset = match maybe_validator {
2340                    Some(validator) => {
2341                        // Check if validator status is not Active
2342                        match validator.status.try_into() {
2343                            Ok(StakeStatus::Active) => false, // Valid, keep it
2344                            _ => true,                        // Not active, reset it
2345                        }
2346                    }
2347                    None => true, // Not found in list, reset it
2348                };
2349
2350                if should_reset {
2351                    msg!(
2352                        "Preferred deposit validator {} not found or not active, resetting",
2353                        preferred_deposit
2354                    );
2355                    stake_pool.preferred_deposit_validator_vote_address = None;
2356                }
2357            }
2358
2359            // Check preferred withdrawal validator
2360            if let Some(preferred_withdraw) = stake_pool.preferred_withdraw_validator_vote_address {
2361                let maybe_validator = validator_list.find::<ValidatorStakeInfo, _>(|x| {
2362                    ValidatorStakeInfo::memcmp_pubkey(x, &preferred_withdraw)
2363                });
2364
2365                let should_reset = match maybe_validator {
2366                    Some(validator) => {
2367                        // Check if validator status is not Active
2368                        match validator.status.try_into() {
2369                            Ok(StakeStatus::Active) => false, // Valid, keep it
2370                            _ => true,                        // Not active, reset it
2371                        }
2372                    }
2373                    None => true, // Not found in list, reset it
2374                };
2375
2376                if should_reset {
2377                    msg!(
2378                        "Preferred withdrawal validator {} not found or not active, resetting",
2379                        preferred_withdraw
2380                    );
2381                    stake_pool.preferred_withdraw_validator_vote_address = None;
2382                }
2383            }
2384
2385            // Save the updated stake pool state
2386            borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
2387        }
2388
2389        Ok(())
2390    }
2391
2392    /// Processes [`DepositStake`](enum.Instruction.html).
2393    #[inline(never)] // needed to avoid stack size violation
2394    fn process_deposit_stake(
2395        program_id: &Pubkey,
2396        accounts: &[AccountInfo],
2397        minimum_pool_tokens_out: Option<u64>,
2398    ) -> ProgramResult {
2399        let account_info_iter = &mut accounts.iter();
2400        let stake_pool_info = next_account_info(account_info_iter)?;
2401        let validator_list_info = next_account_info(account_info_iter)?;
2402        let stake_deposit_authority_info = next_account_info(account_info_iter)?;
2403        let withdraw_authority_info = next_account_info(account_info_iter)?;
2404        let stake_info = next_account_info(account_info_iter)?;
2405        let validator_stake_account_info = next_account_info(account_info_iter)?;
2406        let reserve_stake_account_info = next_account_info(account_info_iter)?;
2407        let dest_user_pool_info = next_account_info(account_info_iter)?;
2408        let manager_fee_info = next_account_info(account_info_iter)?;
2409        let referrer_fee_info = next_account_info(account_info_iter)?;
2410        let pool_mint_info = next_account_info(account_info_iter)?;
2411        let clock_info = next_account_info(account_info_iter)?;
2412        let clock = &Clock::from_account_info(clock_info)?;
2413        let stake_history_info = next_account_info(account_info_iter)?;
2414        let token_program_info = next_account_info(account_info_iter)?;
2415        let stake_program_info = next_account_info(account_info_iter)?;
2416
2417        check_stake_program(stake_program_info.key)?;
2418
2419        check_account_owner(stake_pool_info, program_id)?;
2420        let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
2421        if !stake_pool.is_valid() {
2422            return Err(StakePoolError::InvalidState.into());
2423        }
2424
2425        stake_pool.check_authority_withdraw(
2426            withdraw_authority_info.key,
2427            program_id,
2428            stake_pool_info.key,
2429        )?;
2430        stake_pool.check_stake_deposit_authority(stake_deposit_authority_info.key)?;
2431        stake_pool.check_mint(pool_mint_info)?;
2432        stake_pool.check_validator_list(validator_list_info)?;
2433        stake_pool.check_reserve_stake(reserve_stake_account_info)?;
2434
2435        if stake_pool.token_program_id != *token_program_info.key {
2436            return Err(ProgramError::IncorrectProgramId);
2437        }
2438
2439        if stake_pool.manager_fee_account != *manager_fee_info.key {
2440            return Err(StakePoolError::InvalidFeeAccount.into());
2441        }
2442        // There is no bypass if the manager fee account is invalid. Deposits
2443        // don't hold user funds hostage, so if the fee account is invalid, users
2444        // cannot deposit in the pool.  Let it fail here!
2445
2446        if stake_pool.last_update_epoch < clock.epoch {
2447            return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
2448        }
2449
2450        check_account_owner(validator_list_info, program_id)?;
2451        let mut validator_list_data = validator_list_info.data.borrow_mut();
2452        let (header, mut validator_list) =
2453            ValidatorListHeader::deserialize_vec(&mut validator_list_data)?;
2454        if !header.is_valid() {
2455            return Err(StakePoolError::InvalidState.into());
2456        }
2457
2458        let (_, validator_stake) = get_stake_state(validator_stake_account_info)?;
2459        let pre_all_validator_lamports = validator_stake_account_info.lamports();
2460        let vote_account_address = validator_stake.delegation.voter_pubkey;
2461        if let Some(preferred_deposit) = stake_pool.preferred_deposit_validator_vote_address {
2462            if preferred_deposit != vote_account_address {
2463                msg!(
2464                    "Incorrect deposit address, expected {}, received {}",
2465                    preferred_deposit,
2466                    vote_account_address
2467                );
2468                return Err(StakePoolError::IncorrectDepositVoteAddress.into());
2469            }
2470        }
2471
2472        let validator_stake_info = validator_list
2473            .find_mut::<ValidatorStakeInfo, _>(|x| {
2474                ValidatorStakeInfo::memcmp_pubkey(x, &vote_account_address)
2475            })
2476            .ok_or(StakePoolError::ValidatorNotFound)?;
2477        check_validator_stake_address(
2478            program_id,
2479            stake_pool_info.key,
2480            validator_stake_account_info.key,
2481            &vote_account_address,
2482            NonZeroU32::new(validator_stake_info.validator_seed_suffix.into()),
2483        )?;
2484
2485        if validator_stake_info.status != StakeStatus::Active.into() {
2486            msg!("Validator is marked for removal and no longer accepting deposits");
2487            return Err(StakePoolError::ValidatorNotFound.into());
2488        }
2489
2490        msg!("Stake pre merge {}", validator_stake.delegation.stake);
2491
2492        let (stake_deposit_authority_program_address, deposit_bump_seed) =
2493            find_deposit_authority_program_address(program_id, stake_pool_info.key);
2494        if *stake_deposit_authority_info.key == stake_deposit_authority_program_address {
2495            Self::stake_authorize_signed(
2496                stake_pool_info.key,
2497                stake_info.clone(),
2498                stake_deposit_authority_info.clone(),
2499                AUTHORITY_DEPOSIT,
2500                deposit_bump_seed,
2501                withdraw_authority_info.key,
2502                clock_info.clone(),
2503            )?;
2504        } else {
2505            Self::stake_authorize(
2506                stake_info.clone(),
2507                stake_deposit_authority_info.clone(),
2508                withdraw_authority_info.key,
2509                clock_info.clone(),
2510            )?;
2511        }
2512
2513        Self::stake_merge(
2514            stake_pool_info.key,
2515            stake_info.clone(),
2516            withdraw_authority_info.clone(),
2517            AUTHORITY_WITHDRAW,
2518            stake_pool.stake_withdraw_bump_seed,
2519            validator_stake_account_info.clone(),
2520            clock_info.clone(),
2521            stake_history_info.clone(),
2522        )?;
2523
2524        let (_, post_validator_stake) = get_stake_state(validator_stake_account_info)?;
2525        let post_all_validator_lamports = validator_stake_account_info.lamports();
2526        msg!("Stake post merge {}", post_validator_stake.delegation.stake);
2527
2528        let total_deposit_lamports = post_all_validator_lamports
2529            .checked_sub(pre_all_validator_lamports)
2530            .ok_or(StakePoolError::CalculationFailure)?;
2531        let stake_deposit_lamports = post_validator_stake
2532            .delegation
2533            .stake
2534            .checked_sub(validator_stake.delegation.stake)
2535            .ok_or(StakePoolError::CalculationFailure)?;
2536        let sol_deposit_lamports = total_deposit_lamports
2537            .checked_sub(stake_deposit_lamports)
2538            .ok_or(StakePoolError::CalculationFailure)?;
2539
2540        let new_pool_tokens = stake_pool
2541            .calc_pool_tokens_for_deposit(total_deposit_lamports)
2542            .ok_or(StakePoolError::CalculationFailure)?;
2543        let new_pool_tokens_from_stake = stake_pool
2544            .calc_pool_tokens_for_deposit(stake_deposit_lamports)
2545            .ok_or(StakePoolError::CalculationFailure)?;
2546        let new_pool_tokens_from_sol = new_pool_tokens
2547            .checked_sub(new_pool_tokens_from_stake)
2548            .ok_or(StakePoolError::CalculationFailure)?;
2549
2550        let stake_deposit_fee = stake_pool
2551            .calc_pool_tokens_stake_deposit_fee(new_pool_tokens_from_stake)
2552            .ok_or(StakePoolError::CalculationFailure)?;
2553        let sol_deposit_fee = stake_pool
2554            .calc_pool_tokens_sol_deposit_fee(new_pool_tokens_from_sol)
2555            .ok_or(StakePoolError::CalculationFailure)?;
2556
2557        let total_fee = stake_deposit_fee
2558            .checked_add(sol_deposit_fee)
2559            .ok_or(StakePoolError::CalculationFailure)?;
2560        let pool_tokens_user = new_pool_tokens
2561            .checked_sub(total_fee)
2562            .ok_or(StakePoolError::CalculationFailure)?;
2563
2564        let pool_tokens_referral_fee = stake_pool
2565            .calc_pool_tokens_stake_referral_fee(total_fee)
2566            .ok_or(StakePoolError::CalculationFailure)?;
2567
2568        let pool_tokens_manager_deposit_fee = total_fee
2569            .checked_sub(pool_tokens_referral_fee)
2570            .ok_or(StakePoolError::CalculationFailure)?;
2571
2572        if pool_tokens_user
2573            .saturating_add(pool_tokens_manager_deposit_fee)
2574            .saturating_add(pool_tokens_referral_fee)
2575            != new_pool_tokens
2576        {
2577            return Err(StakePoolError::CalculationFailure.into());
2578        }
2579
2580        if pool_tokens_user == 0 {
2581            return Err(StakePoolError::DepositTooSmall.into());
2582        }
2583
2584        if let Some(minimum_pool_tokens_out) = minimum_pool_tokens_out {
2585            if pool_tokens_user < minimum_pool_tokens_out {
2586                return Err(StakePoolError::ExceededSlippage.into());
2587            }
2588        }
2589
2590        Self::token_mint_to(
2591            stake_pool_info.key,
2592            token_program_info.clone(),
2593            pool_mint_info.clone(),
2594            dest_user_pool_info.clone(),
2595            withdraw_authority_info.clone(),
2596            AUTHORITY_WITHDRAW,
2597            stake_pool.stake_withdraw_bump_seed,
2598            pool_tokens_user,
2599        )?;
2600        if pool_tokens_manager_deposit_fee > 0 {
2601            Self::token_mint_to(
2602                stake_pool_info.key,
2603                token_program_info.clone(),
2604                pool_mint_info.clone(),
2605                manager_fee_info.clone(),
2606                withdraw_authority_info.clone(),
2607                AUTHORITY_WITHDRAW,
2608                stake_pool.stake_withdraw_bump_seed,
2609                pool_tokens_manager_deposit_fee,
2610            )?;
2611        }
2612        if pool_tokens_referral_fee > 0 {
2613            Self::token_mint_to(
2614                stake_pool_info.key,
2615                token_program_info.clone(),
2616                pool_mint_info.clone(),
2617                referrer_fee_info.clone(),
2618                withdraw_authority_info.clone(),
2619                AUTHORITY_WITHDRAW,
2620                stake_pool.stake_withdraw_bump_seed,
2621                pool_tokens_referral_fee,
2622            )?;
2623        }
2624
2625        // withdraw additional lamports to the reserve
2626        if sol_deposit_lamports > 0 {
2627            Self::stake_withdraw(
2628                stake_pool_info.key,
2629                validator_stake_account_info.clone(),
2630                withdraw_authority_info.clone(),
2631                AUTHORITY_WITHDRAW,
2632                stake_pool.stake_withdraw_bump_seed,
2633                reserve_stake_account_info.clone(),
2634                clock_info.clone(),
2635                stake_history_info.clone(),
2636                sol_deposit_lamports,
2637            )?;
2638        }
2639
2640        stake_pool.pool_token_supply = stake_pool
2641            .pool_token_supply
2642            .checked_add(new_pool_tokens)
2643            .ok_or(StakePoolError::CalculationFailure)?;
2644        // We treat the extra lamports as though they were
2645        // transferred directly to the reserve stake account.
2646        stake_pool.total_lamports = stake_pool
2647            .total_lamports
2648            .checked_add(total_deposit_lamports)
2649            .ok_or(StakePoolError::CalculationFailure)?;
2650        borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
2651
2652        validator_stake_info.active_stake_lamports = validator_stake_account_info.lamports().into();
2653
2654        Ok(())
2655    }
2656
2657    /// Processes [`DepositWsolWithSession`](enum.Instruction.html).
2658    #[inline(never)]
2659    fn process_deposit_wsol_with_session(
2660        program_id: &Pubkey,
2661        accounts: &[AccountInfo],
2662        deposit_lamports: u64,
2663        minimum_pool_tokens_out: Option<u64>,
2664    ) -> ProgramResult {
2665        use fogo_sessions_sdk::token::instruction::transfer_checked;
2666        use fogo_sessions_sdk::{session::Session, token::PROGRAM_SIGNER_SEED};
2667        use solana_program::program_pack::Pack;
2668        use spl_associated_token_account::tools::account::create_pda_account;
2669
2670        let account_info_iter = &mut accounts.iter();
2671        let stake_pool_info = next_account_info(account_info_iter)?;
2672        let withdraw_authority_info = next_account_info(account_info_iter)?;
2673        let reserve_stake_account_info = next_account_info(account_info_iter)?;
2674        let signer_or_session_info = next_account_info(account_info_iter)?;
2675        let dest_user_pool_info = next_account_info(account_info_iter)?;
2676        let manager_fee_info = next_account_info(account_info_iter)?;
2677        let referrer_fee_info = next_account_info(account_info_iter)?;
2678        let pool_mint_info = next_account_info(account_info_iter)?;
2679        let system_program_info = next_account_info(account_info_iter)?;
2680        let token_program_info = next_account_info(account_info_iter)?;
2681
2682        // wsol-specific accounts
2683        let wsol_mint_info = next_account_info(account_info_iter)?;
2684        let wsol_token_info = next_account_info(account_info_iter)?;
2685        let wsol_transient_info = next_account_info(account_info_iter)?;
2686        let program_signer_info = next_account_info(account_info_iter)?;
2687        let payer_info = next_account_info(account_info_iter)?;
2688        let user_wallet_info = next_account_info(account_info_iter)?;
2689
2690        let sol_deposit_authority_info = next_account_info(account_info_iter);
2691
2692        if *wsol_mint_info.key != spl_token::native_mint::id() {
2693            msg!("`wsol_mint` is not the native SOL mint");
2694            return Err(ProgramError::InvalidAccountData);
2695        }
2696
2697        if *wsol_mint_info.owner != *token_program_info.key {
2698            msg!("`wsol_mint` is not owned by the token program");
2699            return Err(ProgramError::InvalidAccountData);
2700        }
2701
2702        if *wsol_token_info.owner != *token_program_info.key {
2703            msg!("`wsol_token` is not owned by the token program");
2704            return Err(ProgramError::InvalidAccountData);
2705        }
2706
2707        let user_pubkey =
2708            Session::extract_user_from_signer_or_session(signer_or_session_info, program_id)?;
2709
2710        // Verify wsol_token_info is a valid wSOL token account owned by the user
2711        let wsol_token_data = spl_token::state::Account::unpack(&wsol_token_info.data.borrow())?;
2712        if wsol_token_data.mint != spl_token::native_mint::id() {
2713            msg!("`wsol_token` is not a wSOL token account");
2714            return Err(ProgramError::InvalidAccountData);
2715        }
2716        if wsol_token_data.owner != user_pubkey {
2717            msg!("`wsol_token` is not owned by the session user");
2718            return Err(ProgramError::InvalidAccountData);
2719        }
2720
2721        const TRANSIENT_SEED: &[u8] = b"transient_wsol";
2722
2723        let (expected_program_signer, program_signer_bump) =
2724            Pubkey::find_program_address(&[PROGRAM_SIGNER_SEED], program_id);
2725
2726        if *program_signer_info.key != expected_program_signer {
2727            msg!("`program_signer` does not match expected address");
2728            return Err(ProgramError::InvalidSeeds);
2729        }
2730
2731        let (expected_transient_pda, transient_bump) =
2732            Pubkey::find_program_address(&[TRANSIENT_SEED, user_pubkey.as_ref()], program_id);
2733
2734        if *wsol_transient_info.key != expected_transient_pda {
2735            msg!("`wsol_transient` does not match expected address");
2736            return Err(ProgramError::InvalidSeeds);
2737        }
2738
2739        let program_signer_seeds: &[&[u8]] = &[PROGRAM_SIGNER_SEED, &[program_signer_bump]];
2740        let transient_seeds: &[&[u8]] = &[TRANSIENT_SEED, user_pubkey.as_ref(), &[transient_bump]];
2741
2742        let rent = Rent::get()?;
2743
2744        // Create the transient wSOL account
2745        create_pda_account(
2746            payer_info,
2747            &rent,
2748            spl_token::state::Account::LEN,
2749            token_program_info.key,
2750            system_program_info,
2751            wsol_transient_info,
2752            transient_seeds,
2753        )?;
2754
2755        invoke(
2756            &spl_token::instruction::initialize_account3(
2757                token_program_info.key,
2758                wsol_transient_info.key,
2759                wsol_mint_info.key,
2760                program_signer_info.key,
2761            )?,
2762            &[wsol_transient_info.clone(), wsol_mint_info.clone()],
2763        )?;
2764
2765        // Transfer wSOL from user to transient account and unwrap to SOL
2766        invoke_signed(
2767            &transfer_checked(
2768                token_program_info.key,
2769                wsol_token_info.key,
2770                wsol_mint_info.key,
2771                wsol_transient_info.key,
2772                signer_or_session_info.key,
2773                Some(program_signer_info.key),
2774                deposit_lamports,
2775                native_mint::DECIMALS,
2776            )?,
2777            &[
2778                token_program_info.clone(),
2779                wsol_token_info.clone(),
2780                wsol_mint_info.clone(),
2781                wsol_transient_info.clone(),
2782                signer_or_session_info.clone(),
2783                program_signer_info.clone(),
2784            ],
2785            &[program_signer_seeds],
2786        )?;
2787
2788        // Close the transient wSOL account to unwrap to SOL
2789        invoke_signed(
2790            &spl_token::instruction::close_account(
2791                token_program_info.key,
2792                wsol_transient_info.key,
2793                program_signer_info.key,
2794                program_signer_info.key,
2795                &[],
2796            )?,
2797            &[wsol_transient_info.clone(), program_signer_info.clone()],
2798            &[program_signer_seeds],
2799        )?;
2800
2801        // Refund transient wSOL rent to payer
2802        let rent_lamports = rent.minimum_balance(spl_token::state::Account::LEN).max(1);
2803
2804        invoke_signed(
2805            &system_instruction::transfer(program_signer_info.key, payer_info.key, rent_lamports),
2806            &[
2807                system_program_info.clone(),
2808                program_signer_info.clone(),
2809                payer_info.clone(),
2810            ],
2811            &[program_signer_seeds],
2812        )?;
2813
2814        // Verify dest_user_pool_info is the correct ATA for user_wallet
2815        let expected_ata =
2816            spl_associated_token_account::get_associated_token_address_with_program_id(
2817                user_wallet_info.key,
2818                pool_mint_info.key,
2819                token_program_info.key,
2820            );
2821        if *dest_user_pool_info.key != expected_ata {
2822            msg!("dest_user_pool is not the ATA for user_wallet");
2823            return Err(ProgramError::InvalidAccountData);
2824        }
2825
2826        let ata_creation_cost = rent
2827            .minimum_balance(spl_token::state::Account::LEN)
2828            .saturating_sub(dest_user_pool_info.lamports());
2829
2830        if deposit_lamports < ata_creation_cost {
2831            msg!("Deposit too small to cover ATA rent");
2832            return Err(StakePoolError::DepositTooSmall.into());
2833        }
2834
2835        if ata_creation_cost > 0 {
2836            invoke_signed(
2837                &spl_associated_token_account::instruction::create_associated_token_account_idempotent(
2838                    program_signer_info.key,
2839                    user_wallet_info.key,
2840                    pool_mint_info.key,
2841                    token_program_info.key,
2842                ),
2843                &[
2844                    program_signer_info.clone(),
2845                    dest_user_pool_info.clone(),
2846                    user_wallet_info.clone(),
2847                    pool_mint_info.clone(),
2848                    system_program_info.clone(),
2849                    token_program_info.clone(),
2850                ],
2851                &[program_signer_seeds],
2852            )?;
2853        }
2854
2855        let effective_deposit = deposit_lamports - ata_creation_cost;
2856
2857        invoke_signed(
2858            &system_instruction::transfer(
2859                program_signer_info.key,
2860                reserve_stake_account_info.key,
2861                effective_deposit,
2862            ),
2863            &[
2864                system_program_info.clone(),
2865                program_signer_info.clone(),
2866                reserve_stake_account_info.clone(),
2867            ],
2868            &[program_signer_seeds],
2869        )?;
2870
2871        let mut new_accounts = vec![
2872            stake_pool_info.clone(),
2873            withdraw_authority_info.clone(),
2874            reserve_stake_account_info.clone(),
2875            signer_or_session_info.clone(),
2876            dest_user_pool_info.clone(),
2877            manager_fee_info.clone(),
2878            referrer_fee_info.clone(),
2879            pool_mint_info.clone(),
2880            system_program_info.clone(),
2881            token_program_info.clone(),
2882        ];
2883
2884        if let Ok(sol_deposit_authority_info) = sol_deposit_authority_info {
2885            new_accounts.push(sol_deposit_authority_info.clone());
2886        }
2887
2888        Self::process_deposit_sol(
2889            program_id,
2890            &new_accounts,
2891            effective_deposit,
2892            minimum_pool_tokens_out,
2893            // the SOL transfer has already been done above
2894            true,
2895        )
2896    }
2897
2898    /// Processes [`DepositSol`](enum.Instruction.html).
2899    #[inline(never)] // needed to avoid stack size violation
2900    fn process_deposit_sol(
2901        program_id: &Pubkey,
2902        accounts: &[AccountInfo],
2903        deposit_lamports: u64,
2904        minimum_pool_tokens_out: Option<u64>,
2905        is_wsol_path: bool,
2906    ) -> ProgramResult {
2907        let account_info_iter = &mut accounts.iter();
2908        let stake_pool_info = next_account_info(account_info_iter)?;
2909        let withdraw_authority_info = next_account_info(account_info_iter)?;
2910        let reserve_stake_account_info = next_account_info(account_info_iter)?;
2911        let from_user_lamports_info = next_account_info(account_info_iter)?;
2912        let dest_user_pool_info = next_account_info(account_info_iter)?;
2913        let manager_fee_info = next_account_info(account_info_iter)?;
2914        let referrer_fee_info = next_account_info(account_info_iter)?;
2915        let pool_mint_info = next_account_info(account_info_iter)?;
2916        let system_program_info = next_account_info(account_info_iter)?;
2917        let token_program_info = next_account_info(account_info_iter)?;
2918        let sol_deposit_authority_info = next_account_info(account_info_iter);
2919
2920        let clock = Clock::get()?;
2921
2922        check_account_owner(stake_pool_info, program_id)?;
2923        let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
2924        if !stake_pool.is_valid() {
2925            return Err(StakePoolError::InvalidState.into());
2926        }
2927
2928        stake_pool.check_authority_withdraw(
2929            withdraw_authority_info.key,
2930            program_id,
2931            stake_pool_info.key,
2932        )?;
2933        stake_pool.check_sol_deposit_authority(sol_deposit_authority_info)?;
2934        stake_pool.check_mint(pool_mint_info)?;
2935        stake_pool.check_reserve_stake(reserve_stake_account_info)?;
2936
2937        if stake_pool.token_program_id != *token_program_info.key {
2938            return Err(ProgramError::IncorrectProgramId);
2939        }
2940        check_system_program(system_program_info.key)?;
2941
2942        if stake_pool.manager_fee_account != *manager_fee_info.key {
2943            return Err(StakePoolError::InvalidFeeAccount.into());
2944        }
2945        // There is no bypass if the manager fee account is invalid. Deposits
2946        // don't hold user funds hostage, so if the fee account is invalid, users
2947        // cannot deposit in the pool.  Let it fail here!
2948
2949        // We want this to hold to ensure that deposit_sol mints pool tokens
2950        // at the right price
2951        if stake_pool.last_update_epoch < clock.epoch {
2952            return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
2953        }
2954
2955        let new_pool_tokens = stake_pool
2956            .calc_pool_tokens_for_deposit(deposit_lamports)
2957            .ok_or(StakePoolError::CalculationFailure)?;
2958
2959        let pool_tokens_sol_deposit_fee = stake_pool
2960            .calc_pool_tokens_sol_deposit_fee(new_pool_tokens)
2961            .ok_or(StakePoolError::CalculationFailure)?;
2962        let pool_tokens_user = new_pool_tokens
2963            .checked_sub(pool_tokens_sol_deposit_fee)
2964            .ok_or(StakePoolError::CalculationFailure)?;
2965
2966        let pool_tokens_referral_fee = stake_pool
2967            .calc_pool_tokens_sol_referral_fee(pool_tokens_sol_deposit_fee)
2968            .ok_or(StakePoolError::CalculationFailure)?;
2969        let pool_tokens_manager_deposit_fee = pool_tokens_sol_deposit_fee
2970            .checked_sub(pool_tokens_referral_fee)
2971            .ok_or(StakePoolError::CalculationFailure)?;
2972
2973        if pool_tokens_user
2974            .saturating_add(pool_tokens_manager_deposit_fee)
2975            .saturating_add(pool_tokens_referral_fee)
2976            != new_pool_tokens
2977        {
2978            return Err(StakePoolError::CalculationFailure.into());
2979        }
2980
2981        if pool_tokens_user == 0 {
2982            return Err(StakePoolError::DepositTooSmall.into());
2983        }
2984
2985        if let Some(minimum_pool_tokens_out) = minimum_pool_tokens_out {
2986            if pool_tokens_user < minimum_pool_tokens_out {
2987                return Err(StakePoolError::ExceededSlippage.into());
2988            }
2989        }
2990
2991        if !is_wsol_path {
2992            Self::sol_transfer(
2993                from_user_lamports_info.clone(),
2994                reserve_stake_account_info.clone(),
2995                deposit_lamports,
2996            )?;
2997        }
2998
2999        Self::token_mint_to(
3000            stake_pool_info.key,
3001            token_program_info.clone(),
3002            pool_mint_info.clone(),
3003            dest_user_pool_info.clone(),
3004            withdraw_authority_info.clone(),
3005            AUTHORITY_WITHDRAW,
3006            stake_pool.stake_withdraw_bump_seed,
3007            pool_tokens_user,
3008        )?;
3009
3010        if pool_tokens_manager_deposit_fee > 0 {
3011            Self::token_mint_to(
3012                stake_pool_info.key,
3013                token_program_info.clone(),
3014                pool_mint_info.clone(),
3015                manager_fee_info.clone(),
3016                withdraw_authority_info.clone(),
3017                AUTHORITY_WITHDRAW,
3018                stake_pool.stake_withdraw_bump_seed,
3019                pool_tokens_manager_deposit_fee,
3020            )?;
3021        }
3022
3023        if pool_tokens_referral_fee > 0 {
3024            Self::token_mint_to(
3025                stake_pool_info.key,
3026                token_program_info.clone(),
3027                pool_mint_info.clone(),
3028                referrer_fee_info.clone(),
3029                withdraw_authority_info.clone(),
3030                AUTHORITY_WITHDRAW,
3031                stake_pool.stake_withdraw_bump_seed,
3032                pool_tokens_referral_fee,
3033            )?;
3034        }
3035
3036        stake_pool.pool_token_supply = stake_pool
3037            .pool_token_supply
3038            .checked_add(new_pool_tokens)
3039            .ok_or(StakePoolError::CalculationFailure)?;
3040        stake_pool.total_lamports = stake_pool
3041            .total_lamports
3042            .checked_add(deposit_lamports)
3043            .ok_or(StakePoolError::CalculationFailure)?;
3044        borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
3045
3046        Ok(())
3047    }
3048
3049    /// Processes [`WithdrawStake`](enum.Instruction.html).
3050    #[inline(never)] // needed to avoid stack size violation
3051    fn process_withdraw_stake(
3052        program_id: &Pubkey,
3053        accounts: &[AccountInfo],
3054        pool_tokens: u64,
3055        minimum_lamports_out: Option<u64>,
3056        user_stake_seed: Option<u64>,
3057    ) -> ProgramResult {
3058        let account_info_iter = &mut accounts.iter();
3059        let stake_pool_info = next_account_info(account_info_iter)?;
3060        let validator_list_info = next_account_info(account_info_iter)?;
3061        let withdraw_authority_info = next_account_info(account_info_iter)?;
3062        let stake_split_from = next_account_info(account_info_iter)?;
3063        let stake_split_to = next_account_info(account_info_iter)?;
3064        let user_stake_authority_info = next_account_info(account_info_iter)?;
3065        let user_transfer_authority_info = next_account_info(account_info_iter)?;
3066        let burn_from_pool_info = next_account_info(account_info_iter)?;
3067        let manager_fee_info = next_account_info(account_info_iter)?;
3068        let pool_mint_info = next_account_info(account_info_iter)?;
3069        let clock_info = next_account_info(account_info_iter)?;
3070        let clock = &Clock::from_account_info(clock_info)?;
3071        let token_program_info = next_account_info(account_info_iter)?;
3072        let stake_program_info = next_account_info(account_info_iter)?;
3073
3074        check_stake_program(stake_program_info.key)?;
3075        check_account_owner(stake_pool_info, program_id)?;
3076        let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
3077        if !stake_pool.is_valid() {
3078            return Err(StakePoolError::InvalidState.into());
3079        }
3080
3081        let decimals = stake_pool.check_mint(pool_mint_info)?;
3082        stake_pool.check_validator_list(validator_list_info)?;
3083        stake_pool.check_authority_withdraw(
3084            withdraw_authority_info.key,
3085            program_id,
3086            stake_pool_info.key,
3087        )?;
3088
3089        if stake_pool.manager_fee_account != *manager_fee_info.key {
3090            return Err(StakePoolError::InvalidFeeAccount.into());
3091        }
3092        if stake_pool.token_program_id != *token_program_info.key {
3093            return Err(ProgramError::IncorrectProgramId);
3094        }
3095
3096        if stake_pool.last_update_epoch < clock.epoch {
3097            return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
3098        }
3099
3100        check_account_owner(validator_list_info, program_id)?;
3101        let mut validator_list_data = validator_list_info.data.borrow_mut();
3102        let (header, mut validator_list) =
3103            ValidatorListHeader::deserialize_vec(&mut validator_list_data)?;
3104        if !header.is_valid() {
3105            return Err(StakePoolError::InvalidState.into());
3106        }
3107
3108        // To prevent a faulty manager fee account from preventing withdrawals
3109        // if the token program does not own the account, or if the account is not
3110        // initialized
3111        let pool_tokens_fee = if stake_pool.manager_fee_account == *burn_from_pool_info.key
3112            || stake_pool.check_manager_fee_info(manager_fee_info).is_err()
3113        {
3114            0
3115        } else {
3116            stake_pool
3117                .calc_pool_tokens_stake_withdrawal_fee(pool_tokens)
3118                .ok_or(StakePoolError::CalculationFailure)?
3119        };
3120        let pool_tokens_burnt = pool_tokens
3121            .checked_sub(pool_tokens_fee)
3122            .ok_or(StakePoolError::CalculationFailure)?;
3123
3124        let mut withdraw_lamports = stake_pool
3125            .calc_lamports_withdraw_amount(pool_tokens_burnt)
3126            .ok_or(StakePoolError::CalculationFailure)?;
3127
3128        if withdraw_lamports == 0 {
3129            return Err(StakePoolError::WithdrawalTooSmall.into());
3130        }
3131
3132        if let Some(minimum_lamports_out) = minimum_lamports_out {
3133            if withdraw_lamports < minimum_lamports_out {
3134                return Err(StakePoolError::ExceededSlippage.into());
3135            }
3136        }
3137
3138        let stake_minimum_delegation = stake::tools::get_minimum_delegation()?;
3139        let stake_state = try_from_slice_unchecked::<stake::state::StakeStateV2>(
3140            &stake_split_from.data.borrow(),
3141        )?;
3142        let meta = stake_state.meta().ok_or(StakePoolError::WrongStakeStake)?;
3143        let required_lamports = minimum_stake_lamports(&meta, stake_minimum_delegation);
3144
3145        let lamports_per_pool_token = stake_pool
3146            .get_lamports_per_pool_token()
3147            .ok_or(StakePoolError::CalculationFailure)?;
3148        let minimum_lamports_with_tolerance =
3149            required_lamports.saturating_add(lamports_per_pool_token);
3150
3151        let has_active_stake = validator_list
3152            .find::<ValidatorStakeInfo, _>(|x| {
3153                ValidatorStakeInfo::active_lamports_greater_than(
3154                    x,
3155                    &minimum_lamports_with_tolerance,
3156                ) && ValidatorStakeInfo::is_active(x)
3157            })
3158            .is_some();
3159        let has_transient_stake = validator_list
3160            .find::<ValidatorStakeInfo, _>(|x| {
3161                ValidatorStakeInfo::transient_lamports_greater_than(
3162                    x,
3163                    &minimum_lamports_with_tolerance,
3164                ) && ValidatorStakeInfo::is_active(x)
3165            })
3166            .is_some();
3167
3168        let validator_list_item_info = if *stake_split_from.key == stake_pool.reserve_stake {
3169            // check that the validator stake accounts have no withdrawable stake
3170            if has_transient_stake || has_active_stake {
3171                msg!("Error withdrawing from reserve: validator stake accounts have lamports available, please use those first.");
3172                return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into());
3173            }
3174
3175            // check that reserve has enough
3176            let minimum_reserve_lamports = minimum_reserve_lamports(&meta);
3177            if stake_split_from
3178                .lamports()
3179                .saturating_sub(withdraw_lamports)
3180                < minimum_reserve_lamports
3181            {
3182                msg!("Attempting to withdraw {} lamports, maximum possible SOL withdrawal is {} lamports",
3183                    withdraw_lamports,
3184                    stake_split_from.lamports().saturating_sub(minimum_reserve_lamports)
3185                );
3186                return Err(StakePoolError::SolWithdrawalTooLarge.into());
3187            }
3188            None
3189        } else {
3190            let delegation = stake_state
3191                .delegation()
3192                .ok_or(StakePoolError::WrongStakeStake)?;
3193            let vote_account_address = delegation.voter_pubkey;
3194
3195            if let Some(preferred_withdraw_validator) =
3196                stake_pool.preferred_withdraw_validator_vote_address
3197            {
3198                // Defensive check, in case the preferred validator was somehow
3199                // removed.
3200                if let Some(preferred_validator_info) = validator_list
3201                    .find::<ValidatorStakeInfo, _>(|x| {
3202                        ValidatorStakeInfo::memcmp_pubkey(x, &preferred_withdraw_validator)
3203                    })
3204                {
3205                    let available_lamports =
3206                        u64::from(preferred_validator_info.active_stake_lamports)
3207                            .saturating_sub(minimum_lamports_with_tolerance);
3208                    if preferred_withdraw_validator != vote_account_address
3209                        && available_lamports > 0
3210                    {
3211                        msg!("Validator vote address {} is preferred for withdrawals, it currently has {} lamports available. Please withdraw those before using other validator stake accounts.", preferred_withdraw_validator, u64::from(preferred_validator_info.active_stake_lamports));
3212                        return Err(StakePoolError::IncorrectWithdrawVoteAddress.into());
3213                    }
3214                } else {
3215                    msg!("Preferred withdraw validator not found, allowing withdrawal from any validator");
3216                }
3217            }
3218
3219            let validator_stake_info = validator_list
3220                .find_mut::<ValidatorStakeInfo, _>(|x| {
3221                    ValidatorStakeInfo::memcmp_pubkey(x, &vote_account_address)
3222                })
3223                .ok_or(StakePoolError::ValidatorNotFound)?;
3224
3225            let withdraw_source = if has_active_stake {
3226                // if there's any active stake, we must withdraw from an active
3227                // stake account
3228                check_validator_stake_address(
3229                    program_id,
3230                    stake_pool_info.key,
3231                    stake_split_from.key,
3232                    &vote_account_address,
3233                    NonZeroU32::new(validator_stake_info.validator_seed_suffix.into()),
3234                )?;
3235                StakeWithdrawSource::Active
3236            } else if has_transient_stake
3237                || validator_stake_info.transient_stake_lamports != 0.into()
3238            {
3239                // if there's any transient stake, we must withdraw from there
3240                // Be particularly cautious to avoid removing a validator with
3241                // transient lamports tied to it
3242                check_transient_stake_address(
3243                    program_id,
3244                    stake_pool_info.key,
3245                    stake_split_from.key,
3246                    &vote_account_address,
3247                    validator_stake_info.transient_seed_suffix.into(),
3248                )?;
3249                StakeWithdrawSource::Transient
3250            } else {
3251                // if there's no active or transient stake, we can take the whole account
3252                check_validator_stake_address(
3253                    program_id,
3254                    stake_pool_info.key,
3255                    stake_split_from.key,
3256                    &vote_account_address,
3257                    NonZeroU32::new(validator_stake_info.validator_seed_suffix.into()),
3258                )?;
3259                StakeWithdrawSource::ValidatorRemoval
3260            };
3261
3262            if validator_stake_info.status != StakeStatus::Active.into() {
3263                msg!("Validator is marked for removal and no longer allowing withdrawals");
3264                return Err(StakePoolError::ValidatorNotFound.into());
3265            }
3266
3267            match withdraw_source {
3268                StakeWithdrawSource::Active | StakeWithdrawSource::Transient => {
3269                    let remaining_lamports = stake_split_from
3270                        .lamports()
3271                        .saturating_sub(withdraw_lamports);
3272                    if remaining_lamports < required_lamports {
3273                        msg!("Attempting to withdraw {} lamports from validator account with {} stake lamports, {} must remain", withdraw_lamports, stake_split_from.lamports(), required_lamports);
3274                        return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into());
3275                    }
3276                }
3277                StakeWithdrawSource::ValidatorRemoval => {
3278                    let split_from_lamports = stake_split_from.lamports();
3279                    let upper_bound = split_from_lamports.saturating_add(lamports_per_pool_token);
3280                    if withdraw_lamports < split_from_lamports || withdraw_lamports > upper_bound {
3281                        msg!(
3282                            "Cannot withdraw a whole account worth {} lamports, \
3283                              must withdraw at least {} lamports worth of pool tokens \
3284                              with a margin of {} lamports",
3285                            withdraw_lamports,
3286                            split_from_lamports,
3287                            lamports_per_pool_token
3288                        );
3289                        return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into());
3290                    }
3291                    // truncate the lamports down to the amount in the account
3292                    withdraw_lamports = split_from_lamports;
3293
3294                    // reset the preferred validator if needed
3295                    if stake_pool.preferred_deposit_validator_vote_address
3296                        == Some(vote_account_address)
3297                    {
3298                        stake_pool.preferred_deposit_validator_vote_address = None;
3299                    }
3300                    if stake_pool.preferred_withdraw_validator_vote_address
3301                        == Some(vote_account_address)
3302                    {
3303                        stake_pool.preferred_withdraw_validator_vote_address = None;
3304                    }
3305                }
3306            }
3307            Some((validator_stake_info, withdraw_source))
3308        };
3309
3310        // WithdrawStakeWithSession path - create user stake account PDA
3311        // Tuple: (user_pubkey, program_signer_bump, program_signer_info, signer_or_session_info, split_lamports)
3312        let session_pda_info: Option<(Pubkey, u8, AccountInfo, AccountInfo, u64)> =
3313            if let Some(seed) = user_stake_seed {
3314                use crate::USER_STAKE_SEED_PREFIX;
3315                use fogo_sessions_sdk::session::Session;
3316                use fogo_sessions_sdk::token::PROGRAM_SIGNER_SEED;
3317
3318                let is_validator_removal = matches!(
3319                    &validator_list_item_info,
3320                    Some((_, StakeWithdrawSource::ValidatorRemoval))
3321                );
3322
3323                let program_signer_info = next_account_info(account_info_iter)?;
3324                let system_program_info = next_account_info(account_info_iter)?;
3325                let reserve_stake_info = next_account_info(account_info_iter)?;
3326                let stake_history_info = next_account_info(account_info_iter)?;
3327
3328                check_system_program(system_program_info.key)?;
3329
3330                if stake_pool.reserve_stake != *reserve_stake_info.key {
3331                    msg!("Invalid reserve stake account");
3332                    return Err(StakePoolError::InvalidProgramAddress.into());
3333                }
3334
3335                let (expected_program_signer, program_signer_bump) =
3336                    Pubkey::find_program_address(&[PROGRAM_SIGNER_SEED], program_id);
3337                if expected_program_signer != *program_signer_info.key {
3338                    msg!("Invalid program signer account");
3339                    return Err(ProgramError::InvalidSeeds);
3340                }
3341
3342                let signer_or_session_info = user_stake_authority_info;
3343                let user_pubkey = Session::extract_user_from_signer_or_session(
3344                    signer_or_session_info,
3345                    program_id,
3346                )?;
3347
3348                // Validate stake account PDA
3349                let (expected_stake_pda, stake_pda_bump) = Pubkey::find_program_address(
3350                    &[
3351                        USER_STAKE_SEED_PREFIX,
3352                        user_pubkey.as_ref(),
3353                        &seed.to_le_bytes(),
3354                    ],
3355                    program_id,
3356                );
3357                if expected_stake_pda != *stake_split_to.key {
3358                    msg!("Invalid stake account PDA");
3359                    return Err(ProgramError::InvalidSeeds);
3360                }
3361
3362                // Calculate rent for stake account
3363                let stake_space = stake::state::StakeStateV2::size_of();
3364                let stake_rent = Rent::get()?.minimum_balance(stake_space);
3365
3366                let stake_pda_seeds: &[&[u8]] = &[
3367                    USER_STAKE_SEED_PREFIX,
3368                    user_pubkey.as_ref(),
3369                    &seed.to_le_bytes(),
3370                    &[stake_pda_bump],
3371                ];
3372
3373                // Create stake account if needed
3374                // - System-owned: new account or GC'd zombie, create it
3375                // - Stake-owned + Uninitialized: zombie not yet GC'd, safe to reuse
3376                // - Stake-owned + Initialized: active stake, reject
3377                if stake_split_to.owner == &system_program::id() {
3378                    create_stake_account(stake_split_to.clone(), stake_pda_seeds, stake_space)?;
3379                } else if stake_split_to.owner == &stake::program::id() {
3380                    let stake_state = try_from_slice_unchecked::<stake::state::StakeStateV2>(
3381                        &stake_split_to.data.borrow(),
3382                    )?;
3383                    if !matches!(stake_state, stake::state::StakeStateV2::Uninitialized) {
3384                        msg!("Stake account PDA already in use. Use a different seed.");
3385                        return Err(StakePoolError::AlreadyInUse.into());
3386                    }
3387                    // Zombie account - already allocated and assigned, skip create
3388                } else {
3389                    msg!("Stake account PDA has invalid owner");
3390                    return Err(ProgramError::IllegalOwner);
3391                }
3392
3393                let split_lamports = if is_validator_removal {
3394                    // Full balance includes rent, no additional funding needed
3395                    withdraw_lamports
3396                } else {
3397                    if stake_rent >= reserve_stake_info.lamports() {
3398                        return Err(StakePoolError::ReserveDepleted.into());
3399                    }
3400                    Self::stake_withdraw(
3401                        stake_pool_info.key,
3402                        reserve_stake_info.clone(),
3403                        withdraw_authority_info.clone(),
3404                        AUTHORITY_WITHDRAW,
3405                        stake_pool.stake_withdraw_bump_seed,
3406                        stake_split_to.clone(),
3407                        clock_info.clone(),
3408                        stake_history_info.clone(),
3409                        stake_rent,
3410                    )?;
3411                    withdraw_lamports.saturating_sub(stake_rent)
3412                };
3413
3414                Some((
3415                    user_pubkey,
3416                    program_signer_bump,
3417                    program_signer_info.clone(),
3418                    signer_or_session_info.clone(),
3419                    split_lamports,
3420                ))
3421            } else {
3422                None
3423            };
3424
3425        let actual_split_amount = if let Some((_, _, _, _, split_lamports)) = &session_pda_info {
3426            *split_lamports
3427        } else {
3428            withdraw_lamports
3429        };
3430
3431        Self::stake_split(
3432            stake_pool_info.key,
3433            stake_split_from.clone(),
3434            withdraw_authority_info.clone(),
3435            AUTHORITY_WITHDRAW,
3436            stake_pool.stake_withdraw_bump_seed,
3437            actual_split_amount,
3438            stake_split_to.clone(),
3439        )?;
3440
3441        // WithdrawStakeWithSession path - use session for token ops, set PDA as its own authority
3442        if let Some((
3443            user_pubkey,
3444            program_signer_bump,
3445            program_signer_info,
3446            signer_or_session_info,
3447            _split_lamports,
3448        )) = session_pda_info
3449        {
3450            use fogo_sessions_sdk::token::instruction::{burn, transfer_checked};
3451            use fogo_sessions_sdk::token::PROGRAM_SIGNER_SEED;
3452            use solana_program::program_pack::Pack;
3453
3454            // Validate burn_from_pool is owned by the session user
3455            let burn_from_pool_data =
3456                spl_token::state::Account::unpack(&burn_from_pool_info.data.borrow())?;
3457            if burn_from_pool_data.owner != user_pubkey {
3458                msg!("`burn_from_pool` is not owned by the session user");
3459                return Err(ProgramError::InvalidAccountData);
3460            }
3461
3462            let program_signer_seeds: &[&[u8]] = &[PROGRAM_SIGNER_SEED, &[program_signer_bump]];
3463
3464            // Burn using session
3465            invoke_signed(
3466                &burn(
3467                    token_program_info.key,
3468                    burn_from_pool_info.key,
3469                    pool_mint_info.key,
3470                    signer_or_session_info.key,
3471                    Some(program_signer_info.key),
3472                    pool_tokens_burnt,
3473                )?,
3474                &[
3475                    burn_from_pool_info.clone(),
3476                    pool_mint_info.clone(),
3477                    signer_or_session_info.clone(),
3478                    program_signer_info.clone(),
3479                ],
3480                &[program_signer_seeds],
3481            )?;
3482
3483            // Transfer fee using session
3484            if pool_tokens_fee > 0 {
3485                invoke_signed(
3486                    &transfer_checked(
3487                        token_program_info.key,
3488                        burn_from_pool_info.key,
3489                        pool_mint_info.key,
3490                        manager_fee_info.key,
3491                        signer_or_session_info.key,
3492                        Some(program_signer_info.key),
3493                        pool_tokens_fee,
3494                        decimals,
3495                    )?,
3496                    &[
3497                        burn_from_pool_info.clone(),
3498                        pool_mint_info.clone(),
3499                        manager_fee_info.clone(),
3500                        signer_or_session_info.clone(),
3501                        program_signer_info.clone(),
3502                    ],
3503                    &[program_signer_seeds],
3504                )?;
3505            }
3506
3507            // Only deactivate if the stake is active and not yet deactivated.
3508            // stake_deactivate fails with AlreadyDeactivated if called on reserve/transient stake.
3509            let split_to_state = try_from_slice_unchecked::<stake::state::StakeStateV2>(
3510                &stake_split_to.data.borrow(),
3511            )?;
3512            if let stake::state::StakeStateV2::Stake(_meta, stake, _) = split_to_state {
3513                if stake.delegation.deactivation_epoch == Epoch::MAX {
3514                    // Stake is active and not yet deactivated - deactivate it now
3515                    Self::stake_deactivate(
3516                        stake_split_to.clone(),
3517                        clock_info.clone(),
3518                        withdraw_authority_info.clone(),
3519                        stake_pool_info.key,
3520                        AUTHORITY_WITHDRAW,
3521                        stake_pool.stake_withdraw_bump_seed,
3522                    )?;
3523                }
3524            }
3525
3526            // Set stake authority to the PDA itself so it can sign the later withdrawal
3527            Self::stake_authorize_signed(
3528                stake_pool_info.key,
3529                stake_split_to.clone(),
3530                withdraw_authority_info.clone(),
3531                AUTHORITY_WITHDRAW,
3532                stake_pool.stake_withdraw_bump_seed,
3533                stake_split_to.key, // The PDA becomes its own authority
3534                clock_info.clone(),
3535            )?;
3536
3537            // Update stake pool: withdraw_lamports left the pool (split from validator + rent from reserve)
3538            stake_pool.pool_token_supply = stake_pool
3539                .pool_token_supply
3540                .checked_sub(pool_tokens_burnt)
3541                .ok_or(StakePoolError::CalculationFailure)?;
3542            stake_pool.total_lamports = stake_pool
3543                .total_lamports
3544                .checked_sub(withdraw_lamports)
3545                .ok_or(StakePoolError::CalculationFailure)?;
3546            borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
3547
3548            // Update validator list (only split_lamports came from the validator stake)
3549            if let Some((validator_list_item, withdraw_source)) = validator_list_item_info {
3550                match withdraw_source {
3551                    StakeWithdrawSource::Active => {
3552                        validator_list_item.active_stake_lamports =
3553                            u64::from(validator_list_item.active_stake_lamports)
3554                                .checked_sub(actual_split_amount)
3555                                .ok_or(StakePoolError::CalculationFailure)?
3556                                .into()
3557                    }
3558                    StakeWithdrawSource::Transient => {
3559                        validator_list_item.transient_stake_lamports =
3560                            u64::from(validator_list_item.transient_stake_lamports)
3561                                .checked_sub(actual_split_amount)
3562                                .ok_or(StakePoolError::CalculationFailure)?
3563                                .into()
3564                    }
3565                    StakeWithdrawSource::ValidatorRemoval => {
3566                        validator_list_item.active_stake_lamports =
3567                            u64::from(validator_list_item.active_stake_lamports)
3568                                .checked_sub(actual_split_amount)
3569                                .ok_or(StakePoolError::CalculationFailure)?
3570                                .into();
3571                        if u64::from(validator_list_item.active_stake_lamports) != 0 {
3572                            msg!("Attempting to remove a validator from the pool, but withdrawal leaves {} lamports, update the pool to merge any unaccounted lamports",
3573                                u64::from(validator_list_item.active_stake_lamports));
3574                            return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
3575                        }
3576                        validator_list_item.status = StakeStatus::ReadyForRemoval.into();
3577                    }
3578                }
3579            }
3580
3581            return Ok(());
3582        }
3583
3584        // Regular path - use standard token operations
3585
3586        Self::token_burn(
3587            token_program_info.clone(),
3588            burn_from_pool_info.clone(),
3589            pool_mint_info.clone(),
3590            user_transfer_authority_info.clone(),
3591            pool_tokens_burnt,
3592        )?;
3593
3594        Self::stake_authorize_signed(
3595            stake_pool_info.key,
3596            stake_split_to.clone(),
3597            withdraw_authority_info.clone(),
3598            AUTHORITY_WITHDRAW,
3599            stake_pool.stake_withdraw_bump_seed,
3600            user_stake_authority_info.key,
3601            clock_info.clone(),
3602        )?;
3603
3604        if pool_tokens_fee > 0 {
3605            Self::token_transfer(
3606                token_program_info.clone(),
3607                burn_from_pool_info.clone(),
3608                pool_mint_info.clone(),
3609                manager_fee_info.clone(),
3610                user_transfer_authority_info.clone(),
3611                pool_tokens_fee,
3612                decimals,
3613            )?;
3614        }
3615
3616        stake_pool.pool_token_supply = stake_pool
3617            .pool_token_supply
3618            .checked_sub(pool_tokens_burnt)
3619            .ok_or(StakePoolError::CalculationFailure)?;
3620        stake_pool.total_lamports = stake_pool
3621            .total_lamports
3622            .checked_sub(withdraw_lamports)
3623            .ok_or(StakePoolError::CalculationFailure)?;
3624        borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
3625
3626        if let Some((validator_list_item, withdraw_source)) = validator_list_item_info {
3627            match withdraw_source {
3628                StakeWithdrawSource::Active => {
3629                    validator_list_item.active_stake_lamports =
3630                        u64::from(validator_list_item.active_stake_lamports)
3631                            .checked_sub(withdraw_lamports)
3632                            .ok_or(StakePoolError::CalculationFailure)?
3633                            .into()
3634                }
3635                StakeWithdrawSource::Transient => {
3636                    validator_list_item.transient_stake_lamports =
3637                        u64::from(validator_list_item.transient_stake_lamports)
3638                            .checked_sub(withdraw_lamports)
3639                            .ok_or(StakePoolError::CalculationFailure)?
3640                            .into()
3641                }
3642                StakeWithdrawSource::ValidatorRemoval => {
3643                    validator_list_item.active_stake_lamports =
3644                        u64::from(validator_list_item.active_stake_lamports)
3645                            .checked_sub(withdraw_lamports)
3646                            .ok_or(StakePoolError::CalculationFailure)?
3647                            .into();
3648                    if u64::from(validator_list_item.active_stake_lamports) != 0 {
3649                        msg!("Attempting to remove a validator from the pool, but withdrawal leaves {} lamports, update the pool to merge any unaccounted lamports",
3650                            u64::from(validator_list_item.active_stake_lamports));
3651                        return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
3652                    }
3653                    // since we already checked that there's no transient stake,
3654                    // we can immediately set this as ready for removal
3655                    validator_list_item.status = StakeStatus::ReadyForRemoval.into();
3656                }
3657            }
3658        }
3659
3660        Ok(())
3661    }
3662
3663    /// Processes [`WithdrawWsolWithSession`](enum.Instruction.html).
3664    #[inline(never)] // needed to avoid stack size violation
3665    fn process_withdraw_wsol_with_session(
3666        program_id: &Pubkey,
3667        accounts: &[AccountInfo],
3668        pool_tokens: u64,
3669        minimum_lamports_out: Option<u64>,
3670    ) -> ProgramResult {
3671        use fogo_sessions_sdk::session::Session;
3672        use fogo_sessions_sdk::token::PROGRAM_SIGNER_SEED;
3673        use solana_program::program_pack::Pack;
3674
3675        let account_info_iter = &mut accounts.iter();
3676        let stake_pool_info = next_account_info(account_info_iter)?;
3677        let withdraw_authority_info = next_account_info(account_info_iter)?;
3678        let signer_or_session_info = next_account_info(account_info_iter)?;
3679        let burn_from_pool_info = next_account_info(account_info_iter)?;
3680        let reserve_stake_info = next_account_info(account_info_iter)?;
3681        let destination_account_info = next_account_info(account_info_iter)?;
3682        let manager_fee_info = next_account_info(account_info_iter)?;
3683        let pool_mint_info = next_account_info(account_info_iter)?;
3684        let clock_info = next_account_info(account_info_iter)?;
3685        let stake_history_info = next_account_info(account_info_iter)?;
3686        let stake_program_info = next_account_info(account_info_iter)?;
3687        let token_program_info = next_account_info(account_info_iter)?;
3688
3689        // wsol-specific accounts
3690        let wsol_mint_info = next_account_info(account_info_iter)?;
3691        let program_signer_info = next_account_info(account_info_iter)?;
3692        let user_wallet_info = next_account_info(account_info_iter)?;
3693        let system_program_info = next_account_info(account_info_iter)?;
3694
3695        let sol_withdraw_authority_info = next_account_info(account_info_iter);
3696
3697        if *wsol_mint_info.key != spl_token::native_mint::id() {
3698            msg!("`wsol_mint` is not the native WSOL mint");
3699            return Err(ProgramError::InvalidAccountData);
3700        }
3701
3702        if *wsol_mint_info.owner != *token_program_info.key {
3703            msg!("`wsol_mint` is not owned by the token program");
3704            return Err(ProgramError::InvalidAccountData);
3705        }
3706
3707        let user_pubkey =
3708            Session::extract_user_from_signer_or_session(signer_or_session_info, program_id)?;
3709
3710        // Verify burn_from_pool_info is owned by the user
3711        let burn_from_pool_data =
3712            spl_token::state::Account::unpack(&burn_from_pool_info.data.borrow())?;
3713        if burn_from_pool_data.owner != user_pubkey {
3714            msg!("`burn_from_pool` is not owned by the session user");
3715            return Err(ProgramError::InvalidAccountData);
3716        }
3717
3718        let (expected_program_signer, program_signer_bump) =
3719            Pubkey::find_program_address(&[PROGRAM_SIGNER_SEED], program_id);
3720
3721        if *program_signer_info.key != expected_program_signer {
3722            msg!("`program_signer` does not match expected address");
3723            return Err(ProgramError::InvalidSeeds);
3724        }
3725
3726        let program_signer_seeds: &[&[u8]] = &[PROGRAM_SIGNER_SEED, &[program_signer_bump]];
3727
3728        let rent = Rent::get()?;
3729
3730        // Verify destination_account_info is the correct wSOL ATA for user_wallet
3731        let expected_ata =
3732            spl_associated_token_account::get_associated_token_address_with_program_id(
3733                user_wallet_info.key,
3734                wsol_mint_info.key,
3735                token_program_info.key,
3736            );
3737        if *destination_account_info.key != expected_ata {
3738            msg!("destination_account is not the wSOL ATA for user_wallet");
3739            return Err(ProgramError::InvalidAccountData);
3740        }
3741
3742        let ata_creation_cost = rent
3743            .minimum_balance(spl_token::state::Account::LEN)
3744            .saturating_sub(destination_account_info.lamports());
3745
3746        let withdraw_lamports = {
3747            let mut accounts: Vec<AccountInfo> = vec![
3748                stake_pool_info.clone(),
3749                withdraw_authority_info.clone(),
3750                signer_or_session_info.clone(),
3751                burn_from_pool_info.clone(),
3752                reserve_stake_info.clone(),
3753                program_signer_info.clone(),
3754                manager_fee_info.clone(),
3755                pool_mint_info.clone(),
3756                clock_info.clone(),
3757                stake_history_info.clone(),
3758                stake_program_info.clone(),
3759                token_program_info.clone(),
3760            ];
3761
3762            if let Ok(sol_withdraw_authority_info) = sol_withdraw_authority_info {
3763                accounts.push(sol_withdraw_authority_info.clone());
3764            } else {
3765                // dummy to keep account order consistent
3766                accounts.push(stake_program_info.clone());
3767            }
3768
3769            accounts.push(program_signer_info.clone());
3770
3771            let balance_before = program_signer_info.lamports();
3772            Self::process_withdraw_sol(program_id, &accounts, pool_tokens, minimum_lamports_out)?;
3773            program_signer_info
3774                .lamports()
3775                .saturating_sub(balance_before)
3776        };
3777
3778        if ata_creation_cost > 0 {
3779            if withdraw_lamports < ata_creation_cost {
3780                msg!("Withdrawal too small to cover ATA rent");
3781                return Err(StakePoolError::WithdrawalTooSmall.into());
3782            }
3783
3784            invoke_signed(
3785                &spl_associated_token_account::instruction::create_associated_token_account_idempotent(
3786                    program_signer_info.key,
3787                    user_wallet_info.key,
3788                    wsol_mint_info.key,
3789                    token_program_info.key,
3790                ),
3791                &[
3792                    program_signer_info.clone(),
3793                    destination_account_info.clone(),
3794                    user_wallet_info.clone(),
3795                    wsol_mint_info.clone(),
3796                    system_program_info.clone(),
3797                    token_program_info.clone(),
3798                ],
3799                &[program_signer_seeds],
3800            )?;
3801        }
3802
3803        let user_lamports = withdraw_lamports.saturating_sub(ata_creation_cost);
3804        if user_lamports > 0 {
3805            invoke_signed(
3806                &system_instruction::transfer(
3807                    program_signer_info.key,
3808                    destination_account_info.key,
3809                    user_lamports,
3810                ),
3811                &[
3812                    program_signer_info.clone(),
3813                    destination_account_info.clone(),
3814                    system_program_info.clone(),
3815                ],
3816                &[program_signer_seeds],
3817            )?;
3818        }
3819
3820        invoke(
3821            &spl_token::instruction::sync_native(
3822                token_program_info.key,
3823                destination_account_info.key,
3824            )?,
3825            core::slice::from_ref(destination_account_info),
3826        )?;
3827
3828        Ok(())
3829    }
3830
3831    /// Processes [`WithdrawSol`](enum.Instruction.html).
3832    #[inline(never)] // needed to avoid stack size violation
3833    fn process_withdraw_sol(
3834        program_id: &Pubkey,
3835        accounts: &[AccountInfo],
3836        pool_tokens: u64,
3837        minimum_lamports_out: Option<u64>,
3838    ) -> ProgramResult {
3839        let account_info_iter = &mut accounts.iter();
3840        let stake_pool_info = next_account_info(account_info_iter)?;
3841        let withdraw_authority_info = next_account_info(account_info_iter)?;
3842        let user_transfer_authority_info = next_account_info(account_info_iter)?;
3843        let burn_from_pool_info = next_account_info(account_info_iter)?;
3844        let reserve_stake_info = next_account_info(account_info_iter)?;
3845        let destination_lamports_info = next_account_info(account_info_iter)?;
3846        let manager_fee_info = next_account_info(account_info_iter)?;
3847        let pool_mint_info = next_account_info(account_info_iter)?;
3848        let clock_info = next_account_info(account_info_iter)?;
3849        let stake_history_info = next_account_info(account_info_iter)?;
3850        let stake_program_info = next_account_info(account_info_iter)?;
3851        let token_program_info = next_account_info(account_info_iter)?;
3852        let sol_withdraw_authority_info = next_account_info(account_info_iter);
3853
3854        check_account_owner(stake_pool_info, program_id)?;
3855        let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
3856        if !stake_pool.is_valid() {
3857            return Err(StakePoolError::InvalidState.into());
3858        }
3859
3860        stake_pool.check_authority_withdraw(
3861            withdraw_authority_info.key,
3862            program_id,
3863            stake_pool_info.key,
3864        )?;
3865        stake_pool.check_sol_withdraw_authority(sol_withdraw_authority_info)?;
3866        let decimals = stake_pool.check_mint(pool_mint_info)?;
3867        stake_pool.check_reserve_stake(reserve_stake_info)?;
3868
3869        if stake_pool.token_program_id != *token_program_info.key {
3870            return Err(ProgramError::IncorrectProgramId);
3871        }
3872        check_stake_program(stake_program_info.key)?;
3873
3874        if stake_pool.manager_fee_account != *manager_fee_info.key {
3875            return Err(StakePoolError::InvalidFeeAccount.into());
3876        }
3877
3878        // We want this to hold to ensure that withdraw_sol burns pool tokens
3879        // at the right price
3880        if stake_pool.last_update_epoch < Clock::get()?.epoch {
3881            return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
3882        }
3883
3884        // To prevent a faulty manager fee account from preventing withdrawals
3885        // if the token program does not own the account, or if the account is not
3886        // initialized
3887        let pool_tokens_fee = if stake_pool.manager_fee_account == *burn_from_pool_info.key
3888            || stake_pool.check_manager_fee_info(manager_fee_info).is_err()
3889        {
3890            0
3891        } else {
3892            stake_pool
3893                .calc_pool_tokens_sol_withdrawal_fee(pool_tokens)
3894                .ok_or(StakePoolError::CalculationFailure)?
3895        };
3896        let pool_tokens_burnt = pool_tokens
3897            .checked_sub(pool_tokens_fee)
3898            .ok_or(StakePoolError::CalculationFailure)?;
3899
3900        let withdraw_lamports = stake_pool
3901            .calc_lamports_withdraw_amount(pool_tokens_burnt)
3902            .ok_or(StakePoolError::CalculationFailure)?;
3903
3904        if withdraw_lamports == 0 {
3905            return Err(StakePoolError::WithdrawalTooSmall.into());
3906        }
3907
3908        if let Some(minimum_lamports_out) = minimum_lamports_out {
3909            if withdraw_lamports < minimum_lamports_out {
3910                return Err(StakePoolError::ExceededSlippage.into());
3911            }
3912        }
3913
3914        let new_reserve_lamports = reserve_stake_info
3915            .lamports()
3916            .saturating_sub(withdraw_lamports);
3917        let stake_state = try_from_slice_unchecked::<stake::state::StakeStateV2>(
3918            &reserve_stake_info.data.borrow(),
3919        )?;
3920        if let stake::state::StakeStateV2::Initialized(meta) = stake_state {
3921            let minimum_reserve_lamports = minimum_reserve_lamports(&meta);
3922            if new_reserve_lamports < minimum_reserve_lamports {
3923                msg!("Attempting to withdraw {} lamports, maximum possible SOL withdrawal is {} lamports",
3924                    withdraw_lamports,
3925                    reserve_stake_info.lamports().saturating_sub(minimum_reserve_lamports)
3926                );
3927                return Err(StakePoolError::SolWithdrawalTooLarge.into());
3928            }
3929        } else {
3930            msg!("Reserve stake account not in initialized state");
3931            return Err(StakePoolError::WrongStakeStake.into());
3932        };
3933
3934        // Determine if we are using the WSOL special path (with program signer account)
3935        if let Ok(program_signer_info) = next_account_info(account_info_iter) {
3936            use fogo_sessions_sdk::token::instruction::burn;
3937            use fogo_sessions_sdk::token::instruction::transfer_checked;
3938            use fogo_sessions_sdk::token::PROGRAM_SIGNER_SEED;
3939
3940            let (expected_program_signer, program_signer_bump) =
3941                Pubkey::find_program_address(&[PROGRAM_SIGNER_SEED], program_id);
3942
3943            if expected_program_signer != *program_signer_info.key {
3944                msg!("Invalid program signer account");
3945                return Err(ProgramError::InvalidSeeds);
3946            }
3947
3948            let program_signer_seeds: &[&[u8]] = &[PROGRAM_SIGNER_SEED, &[program_signer_bump]];
3949
3950            invoke_signed(
3951                &burn(
3952                    token_program_info.key,
3953                    burn_from_pool_info.key,
3954                    pool_mint_info.key,
3955                    user_transfer_authority_info.key,
3956                    Some(program_signer_info.key),
3957                    pool_tokens_burnt,
3958                )?,
3959                &[
3960                    burn_from_pool_info.clone(),
3961                    pool_mint_info.clone(),
3962                    user_transfer_authority_info.clone(),
3963                    program_signer_info.clone(),
3964                ],
3965                &[program_signer_seeds],
3966            )?;
3967
3968            if pool_tokens_fee > 0 {
3969                invoke_signed(
3970                    &transfer_checked(
3971                        token_program_info.key,
3972                        burn_from_pool_info.key,
3973                        pool_mint_info.key,
3974                        manager_fee_info.key,
3975                        user_transfer_authority_info.key,
3976                        Some(program_signer_info.key),
3977                        pool_tokens_fee,
3978                        decimals,
3979                    )?,
3980                    &[
3981                        burn_from_pool_info.clone(),
3982                        pool_mint_info.clone(),
3983                        manager_fee_info.clone(),
3984                        user_transfer_authority_info.clone(),
3985                        program_signer_info.clone(),
3986                    ],
3987                    &[program_signer_seeds],
3988                )?;
3989            }
3990        } else {
3991            Self::token_burn(
3992                token_program_info.clone(),
3993                burn_from_pool_info.clone(),
3994                pool_mint_info.clone(),
3995                user_transfer_authority_info.clone(),
3996                pool_tokens_burnt,
3997            )?;
3998
3999            if pool_tokens_fee > 0 {
4000                Self::token_transfer(
4001                    token_program_info.clone(),
4002                    burn_from_pool_info.clone(),
4003                    pool_mint_info.clone(),
4004                    manager_fee_info.clone(),
4005                    user_transfer_authority_info.clone(),
4006                    pool_tokens_fee,
4007                    decimals,
4008                )?;
4009            }
4010        }
4011
4012        Self::stake_withdraw(
4013            stake_pool_info.key,
4014            reserve_stake_info.clone(),
4015            withdraw_authority_info.clone(),
4016            AUTHORITY_WITHDRAW,
4017            stake_pool.stake_withdraw_bump_seed,
4018            destination_lamports_info.clone(),
4019            clock_info.clone(),
4020            stake_history_info.clone(),
4021            withdraw_lamports,
4022        )?;
4023
4024        stake_pool.pool_token_supply = stake_pool
4025            .pool_token_supply
4026            .checked_sub(pool_tokens_burnt)
4027            .ok_or(StakePoolError::CalculationFailure)?;
4028        stake_pool.total_lamports = stake_pool
4029            .total_lamports
4030            .checked_sub(withdraw_lamports)
4031            .ok_or(StakePoolError::CalculationFailure)?;
4032        borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
4033
4034        Ok(())
4035    }
4036
4037    #[inline(never)]
4038    fn process_create_pool_token_metadata(
4039        program_id: &Pubkey,
4040        accounts: &[AccountInfo],
4041        name: String,
4042        symbol: String,
4043        uri: String,
4044    ) -> ProgramResult {
4045        let account_info_iter = &mut accounts.iter();
4046        let stake_pool_info = next_account_info(account_info_iter)?;
4047        let manager_info = next_account_info(account_info_iter)?;
4048        let withdraw_authority_info = next_account_info(account_info_iter)?;
4049        let pool_mint_info = next_account_info(account_info_iter)?;
4050        let payer_info = next_account_info(account_info_iter)?;
4051        let metadata_info = next_account_info(account_info_iter)?;
4052        let mpl_token_metadata_program_info = next_account_info(account_info_iter)?;
4053        let system_program_info = next_account_info(account_info_iter)?;
4054
4055        if !payer_info.is_signer {
4056            msg!("Payer did not sign metadata creation");
4057            return Err(StakePoolError::SignatureMissing.into());
4058        }
4059
4060        check_system_program(system_program_info.key)?;
4061        check_account_owner(payer_info, &system_program::id())?;
4062        check_account_owner(stake_pool_info, program_id)?;
4063        check_mpl_metadata_program(mpl_token_metadata_program_info.key)?;
4064
4065        let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
4066        if !stake_pool.is_valid() {
4067            return Err(StakePoolError::InvalidState.into());
4068        }
4069
4070        stake_pool.check_manager(manager_info)?;
4071        stake_pool.check_authority_withdraw(
4072            withdraw_authority_info.key,
4073            program_id,
4074            stake_pool_info.key,
4075        )?;
4076        stake_pool.check_mint(pool_mint_info)?;
4077        check_mpl_metadata_account_address(metadata_info.key, &stake_pool.pool_mint)?;
4078
4079        // Token mint authority for stake-pool token is stake-pool withdraw authority
4080        let token_mint_authority = withdraw_authority_info;
4081
4082        let new_metadata_instruction = create_metadata_accounts_v3(
4083            *mpl_token_metadata_program_info.key,
4084            *metadata_info.key,
4085            *pool_mint_info.key,
4086            *token_mint_authority.key,
4087            *payer_info.key,
4088            *token_mint_authority.key,
4089            name,
4090            symbol,
4091            uri,
4092        );
4093
4094        let (_, stake_withdraw_bump_seed) =
4095            crate::find_withdraw_authority_program_address(program_id, stake_pool_info.key);
4096
4097        let token_mint_authority_signer_seeds: &[&[_]] = &[
4098            stake_pool_info.key.as_ref(),
4099            AUTHORITY_WITHDRAW,
4100            &[stake_withdraw_bump_seed],
4101        ];
4102
4103        invoke_signed(
4104            &new_metadata_instruction,
4105            &[
4106                metadata_info.clone(),
4107                pool_mint_info.clone(),
4108                withdraw_authority_info.clone(),
4109                payer_info.clone(),
4110                withdraw_authority_info.clone(),
4111                system_program_info.clone(),
4112            ],
4113            &[token_mint_authority_signer_seeds],
4114        )?;
4115
4116        Ok(())
4117    }
4118
4119    #[inline(never)]
4120    fn process_update_pool_token_metadata(
4121        program_id: &Pubkey,
4122        accounts: &[AccountInfo],
4123        name: String,
4124        symbol: String,
4125        uri: String,
4126    ) -> ProgramResult {
4127        let account_info_iter = &mut accounts.iter();
4128
4129        let stake_pool_info = next_account_info(account_info_iter)?;
4130        let manager_info = next_account_info(account_info_iter)?;
4131        let withdraw_authority_info = next_account_info(account_info_iter)?;
4132        let metadata_info = next_account_info(account_info_iter)?;
4133        let mpl_token_metadata_program_info = next_account_info(account_info_iter)?;
4134
4135        check_account_owner(stake_pool_info, program_id)?;
4136
4137        check_mpl_metadata_program(mpl_token_metadata_program_info.key)?;
4138
4139        let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
4140        if !stake_pool.is_valid() {
4141            return Err(StakePoolError::InvalidState.into());
4142        }
4143
4144        stake_pool.check_manager(manager_info)?;
4145        stake_pool.check_authority_withdraw(
4146            withdraw_authority_info.key,
4147            program_id,
4148            stake_pool_info.key,
4149        )?;
4150        check_mpl_metadata_account_address(metadata_info.key, &stake_pool.pool_mint)?;
4151
4152        // Token mint authority for stake-pool token is withdraw authority only
4153        let token_mint_authority = withdraw_authority_info;
4154
4155        let update_metadata_accounts_instruction = update_metadata_accounts_v2(
4156            *mpl_token_metadata_program_info.key,
4157            *metadata_info.key,
4158            *token_mint_authority.key,
4159            None,
4160            Some(DataV2 {
4161                name,
4162                symbol,
4163                uri,
4164                seller_fee_basis_points: 0,
4165                creators: None,
4166                collection: None,
4167                uses: None,
4168            }),
4169            None,
4170            Some(true),
4171        );
4172
4173        let (_, stake_withdraw_bump_seed) =
4174            crate::find_withdraw_authority_program_address(program_id, stake_pool_info.key);
4175
4176        let token_mint_authority_signer_seeds: &[&[_]] = &[
4177            stake_pool_info.key.as_ref(),
4178            AUTHORITY_WITHDRAW,
4179            &[stake_withdraw_bump_seed],
4180        ];
4181
4182        invoke_signed(
4183            &update_metadata_accounts_instruction,
4184            &[metadata_info.clone(), withdraw_authority_info.clone()],
4185            &[token_mint_authority_signer_seeds],
4186        )?;
4187
4188        Ok(())
4189    }
4190
4191    /// Processes [`SetManager`](enum.Instruction.html).
4192    #[inline(never)] // needed to avoid stack size violation
4193    fn process_set_manager(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
4194        let account_info_iter = &mut accounts.iter();
4195        let stake_pool_info = next_account_info(account_info_iter)?;
4196        let manager_info = next_account_info(account_info_iter)?;
4197        let new_manager_info = next_account_info(account_info_iter)?;
4198        let new_manager_fee_info = next_account_info(account_info_iter)?;
4199
4200        check_account_owner(stake_pool_info, program_id)?;
4201        let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
4202        check_account_owner(new_manager_fee_info, &stake_pool.token_program_id)?;
4203        if !stake_pool.is_valid() {
4204            return Err(StakePoolError::InvalidState.into());
4205        }
4206
4207        stake_pool.check_manager(manager_info)?;
4208        if !new_manager_info.is_signer {
4209            msg!("New manager signature missing");
4210            return Err(StakePoolError::SignatureMissing.into());
4211        }
4212
4213        stake_pool.check_manager_fee_info(new_manager_fee_info)?;
4214
4215        stake_pool.manager = *new_manager_info.key;
4216        stake_pool.manager_fee_account = *new_manager_fee_info.key;
4217        borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
4218        Ok(())
4219    }
4220
4221    /// Processes [`SetFee`](enum.Instruction.html).
4222    #[inline(never)] // needed to avoid stack size violation
4223    fn process_set_fee(
4224        program_id: &Pubkey,
4225        accounts: &[AccountInfo],
4226        fee: FeeType,
4227    ) -> ProgramResult {
4228        let account_info_iter = &mut accounts.iter();
4229        let stake_pool_info = next_account_info(account_info_iter)?;
4230        let manager_info = next_account_info(account_info_iter)?;
4231        let clock = Clock::get()?;
4232
4233        check_account_owner(stake_pool_info, program_id)?;
4234        let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
4235        if !stake_pool.is_valid() {
4236            return Err(StakePoolError::InvalidState.into());
4237        }
4238        stake_pool.check_manager(manager_info)?;
4239
4240        if fee.can_only_change_next_epoch() && stake_pool.last_update_epoch < clock.epoch {
4241            return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
4242        }
4243
4244        fee.check_too_high()?;
4245        stake_pool.update_fee(&fee)?;
4246        borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
4247        Ok(())
4248    }
4249
4250    /// Processes [`SetStaker`](enum.Instruction.html).
4251    #[inline(never)] // needed to avoid stack size violation
4252    fn process_set_staker(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
4253        let account_info_iter = &mut accounts.iter();
4254        let stake_pool_info = next_account_info(account_info_iter)?;
4255        let set_staker_authority_info = next_account_info(account_info_iter)?;
4256        let new_staker_info = next_account_info(account_info_iter)?;
4257
4258        check_account_owner(stake_pool_info, program_id)?;
4259        let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
4260        if !stake_pool.is_valid() {
4261            return Err(StakePoolError::InvalidState.into());
4262        }
4263
4264        let staker_signed = stake_pool.check_staker(set_staker_authority_info);
4265        let manager_signed = stake_pool.check_manager(set_staker_authority_info);
4266        if staker_signed.is_err() && manager_signed.is_err() {
4267            return Err(StakePoolError::SignatureMissing.into());
4268        }
4269        stake_pool.staker = *new_staker_info.key;
4270        borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
4271        Ok(())
4272    }
4273
4274    /// Processes [`SetFundingAuthority`](enum.Instruction.html).
4275    #[inline(never)] // needed to avoid stack size violation
4276    fn process_set_funding_authority(
4277        program_id: &Pubkey,
4278        accounts: &[AccountInfo],
4279        funding_type: FundingType,
4280    ) -> ProgramResult {
4281        let account_info_iter = &mut accounts.iter();
4282        let stake_pool_info = next_account_info(account_info_iter)?;
4283        let manager_info = next_account_info(account_info_iter)?;
4284
4285        let new_authority = next_account_info(account_info_iter)
4286            .ok()
4287            .map(|new_authority_account_info| *new_authority_account_info.key);
4288
4289        check_account_owner(stake_pool_info, program_id)?;
4290        let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
4291        if !stake_pool.is_valid() {
4292            return Err(StakePoolError::InvalidState.into());
4293        }
4294        stake_pool.check_manager(manager_info)?;
4295        match funding_type {
4296            FundingType::StakeDeposit => {
4297                stake_pool.stake_deposit_authority = new_authority.unwrap_or(
4298                    find_deposit_authority_program_address(program_id, stake_pool_info.key).0,
4299                );
4300            }
4301            FundingType::SolDeposit => stake_pool.sol_deposit_authority = new_authority,
4302            FundingType::SolWithdraw => stake_pool.sol_withdraw_authority = new_authority,
4303        }
4304        borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
4305        Ok(())
4306    }
4307
4308    /// Process `WithdrawFromStakeAccountWithSession` instruction
4309    /// Withdraws lamports from a user stake account after cooldown.
4310    fn process_withdraw_from_stake_account_with_session(
4311        program_id: &Pubkey,
4312        accounts: &[AccountInfo],
4313        lamports: u64,
4314        user_stake_seed: u64,
4315    ) -> ProgramResult {
4316        use crate::USER_STAKE_SEED_PREFIX;
4317        use fogo_sessions_sdk::session::Session;
4318
4319        let account_info_iter = &mut accounts.iter();
4320        let stake_account_info = next_account_info(account_info_iter)?;
4321        let recipient_info = next_account_info(account_info_iter)?;
4322        let clock_info = next_account_info(account_info_iter)?;
4323        let stake_history_info = next_account_info(account_info_iter)?;
4324        let signer_or_session_info = next_account_info(account_info_iter)?;
4325
4326        let user_pubkey =
4327            Session::extract_user_from_signer_or_session(signer_or_session_info, program_id)?;
4328
4329        if *recipient_info.key != user_pubkey {
4330            msg!("Recipient must be the session user wallet");
4331            return Err(ProgramError::InvalidAccountData);
4332        }
4333
4334        // Validate stake account PDA
4335        let (expected_stake_pda, stake_pda_bump) = Pubkey::find_program_address(
4336            &[
4337                USER_STAKE_SEED_PREFIX,
4338                user_pubkey.as_ref(),
4339                &user_stake_seed.to_le_bytes(),
4340            ],
4341            program_id,
4342        );
4343        if expected_stake_pda != *stake_account_info.key {
4344            msg!("Invalid stake account PDA");
4345            return Err(ProgramError::InvalidSeeds);
4346        }
4347
4348        // Verify stake account is deactivated and ready for withdrawal
4349        let stake_state = try_from_slice_unchecked::<stake::state::StakeStateV2>(
4350            &stake_account_info.data.borrow(),
4351        )?;
4352
4353        let clock = Clock::get()?;
4354
4355        match stake_state {
4356            stake::state::StakeStateV2::Stake(meta, stake, _) => {
4357                // Verify the stake's withdrawer is the PDA itself
4358                if meta.authorized.withdrawer != expected_stake_pda {
4359                    msg!(
4360                        "Stake withdrawer mismatch. Expected: {}, got: {}",
4361                        expected_stake_pda,
4362                        meta.authorized.withdrawer
4363                    );
4364                    return Err(ProgramError::InvalidAccountData);
4365                }
4366
4367                // Check if stake is fully deactivated
4368                if stake.delegation.deactivation_epoch == Epoch::MAX {
4369                    msg!("Stake account is not deactivated yet");
4370                    return Err(StakePoolError::UserStakeNotActive.into());
4371                }
4372                if stake.delegation.deactivation_epoch >= clock.epoch {
4373                    msg!(
4374                        "Stake account not fully deactivated. Deactivation epoch: {}, current: {}",
4375                        stake.delegation.deactivation_epoch,
4376                        clock.epoch
4377                    );
4378                    return Err(StakePoolError::UserStakeNotActive.into());
4379                }
4380            }
4381            _ => {
4382                msg!("Stake account is not in staked state");
4383                return Err(StakePoolError::WrongStakeStake.into());
4384            }
4385        }
4386
4387        let stake_balance = stake_account_info.lamports();
4388
4389        // Determine withdrawal amount (u64::MAX means full withdrawal)
4390        let withdraw_amount = if lamports == u64::MAX {
4391            stake_balance
4392        } else {
4393            lamports
4394        };
4395
4396        let stake_pda_seeds: &[&[u8]] = &[
4397            USER_STAKE_SEED_PREFIX,
4398            user_pubkey.as_ref(),
4399            &user_stake_seed.to_le_bytes(),
4400            &[stake_pda_bump],
4401        ];
4402
4403        invoke_signed(
4404            &stake::instruction::withdraw(
4405                stake_account_info.key,
4406                stake_account_info.key,
4407                recipient_info.key,
4408                withdraw_amount,
4409                None,
4410            ),
4411            &[
4412                stake_account_info.clone(),
4413                recipient_info.clone(),
4414                clock_info.clone(),
4415                stake_history_info.clone(),
4416                stake_account_info.clone(),
4417            ],
4418            &[stake_pda_seeds],
4419        )?;
4420
4421        Ok(())
4422    }
4423
4424    /// Processes [`Instruction`](enum.Instruction.html).
4425    pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
4426        let instruction = StakePoolInstruction::try_from_slice(input)?;
4427        match instruction {
4428            StakePoolInstruction::Initialize {
4429                fee,
4430                withdrawal_fee,
4431                deposit_fee,
4432                referral_fee,
4433                max_validators,
4434            } => {
4435                msg!("Instruction: Initialize stake pool");
4436                Self::process_initialize(
4437                    program_id,
4438                    accounts,
4439                    fee,
4440                    withdrawal_fee,
4441                    deposit_fee,
4442                    referral_fee,
4443                    max_validators,
4444                )
4445            }
4446            StakePoolInstruction::AddValidatorToPool(seed) => {
4447                msg!("Instruction: AddValidatorToPool");
4448                Self::process_add_validator_to_pool(program_id, accounts, seed)
4449            }
4450            StakePoolInstruction::RemoveValidatorFromPool => {
4451                msg!("Instruction: RemoveValidatorFromPool");
4452                Self::process_remove_validator_from_pool(program_id, accounts)
4453            }
4454            StakePoolInstruction::DecreaseValidatorStake {
4455                lamports,
4456                transient_stake_seed,
4457            } => {
4458                msg!("Instruction: DecreaseValidatorStake");
4459                msg!("NOTE: This instruction is deprecated, please use `DecreaseValidatorStakeWithReserve`");
4460                Self::process_decrease_validator_stake(
4461                    program_id,
4462                    accounts,
4463                    lamports,
4464                    transient_stake_seed,
4465                    None,
4466                    false,
4467                )
4468            }
4469            StakePoolInstruction::DecreaseValidatorStakeWithReserve {
4470                lamports,
4471                transient_stake_seed,
4472            } => {
4473                msg!("Instruction: DecreaseValidatorStakeWithReserve");
4474                Self::process_decrease_validator_stake(
4475                    program_id,
4476                    accounts,
4477                    lamports,
4478                    transient_stake_seed,
4479                    None,
4480                    true,
4481                )
4482            }
4483            StakePoolInstruction::DecreaseAdditionalValidatorStake {
4484                lamports,
4485                transient_stake_seed,
4486                ephemeral_stake_seed,
4487            } => {
4488                msg!("Instruction: DecreaseAdditionalValidatorStake");
4489                Self::process_decrease_validator_stake(
4490                    program_id,
4491                    accounts,
4492                    lamports,
4493                    transient_stake_seed,
4494                    Some(ephemeral_stake_seed),
4495                    true,
4496                )
4497            }
4498            StakePoolInstruction::IncreaseValidatorStake {
4499                lamports,
4500                transient_stake_seed,
4501            } => {
4502                msg!("Instruction: IncreaseValidatorStake");
4503                Self::process_increase_validator_stake(
4504                    program_id,
4505                    accounts,
4506                    lamports,
4507                    transient_stake_seed,
4508                    None,
4509                )
4510            }
4511            StakePoolInstruction::IncreaseAdditionalValidatorStake {
4512                lamports,
4513                transient_stake_seed,
4514                ephemeral_stake_seed,
4515            } => {
4516                msg!("Instruction: IncreaseAdditionalValidatorStake");
4517                Self::process_increase_validator_stake(
4518                    program_id,
4519                    accounts,
4520                    lamports,
4521                    transient_stake_seed,
4522                    Some(ephemeral_stake_seed),
4523                )
4524            }
4525            StakePoolInstruction::SetPreferredValidator {
4526                validator_type,
4527                validator_vote_address,
4528            } => {
4529                msg!("Instruction: SetPreferredValidator");
4530                Self::process_set_preferred_validator(
4531                    program_id,
4532                    accounts,
4533                    validator_type,
4534                    validator_vote_address,
4535                )
4536            }
4537            StakePoolInstruction::UpdateValidatorListBalance {
4538                start_index,
4539                no_merge,
4540            } => {
4541                msg!("Instruction: UpdateValidatorListBalance");
4542                Self::process_update_validator_list_balance(
4543                    program_id,
4544                    accounts,
4545                    start_index,
4546                    no_merge,
4547                )
4548            }
4549            StakePoolInstruction::UpdateStakePoolBalance => {
4550                msg!("Instruction: UpdateStakePoolBalance");
4551                Self::process_update_stake_pool_balance(program_id, accounts)
4552            }
4553            StakePoolInstruction::CleanupRemovedValidatorEntries => {
4554                msg!("Instruction: CleanupRemovedValidatorEntries");
4555                Self::process_cleanup_removed_validator_entries(program_id, accounts)
4556            }
4557            StakePoolInstruction::DepositStake => {
4558                msg!("Instruction: DepositStake");
4559                Self::process_deposit_stake(program_id, accounts, None)
4560            }
4561            StakePoolInstruction::WithdrawStake(amount) => {
4562                msg!("Instruction: WithdrawStake");
4563                Self::process_withdraw_stake(program_id, accounts, amount, None, None)
4564            }
4565            StakePoolInstruction::SetFee { fee } => {
4566                msg!("Instruction: SetFee");
4567                Self::process_set_fee(program_id, accounts, fee)
4568            }
4569            StakePoolInstruction::SetManager => {
4570                msg!("Instruction: SetManager");
4571                Self::process_set_manager(program_id, accounts)
4572            }
4573            StakePoolInstruction::SetStaker => {
4574                msg!("Instruction: SetStaker");
4575                Self::process_set_staker(program_id, accounts)
4576            }
4577            StakePoolInstruction::SetFundingAuthority(funding_type) => {
4578                msg!("Instruction: SetFundingAuthority");
4579                Self::process_set_funding_authority(program_id, accounts, funding_type)
4580            }
4581            StakePoolInstruction::DepositSol(lamports) => {
4582                msg!("Instruction: DepositSol");
4583                Self::process_deposit_sol(program_id, accounts, lamports, None, false)
4584            }
4585            StakePoolInstruction::WithdrawSol(pool_tokens) => {
4586                msg!("Instruction: WithdrawSol");
4587                Self::process_withdraw_sol(program_id, accounts, pool_tokens, None)
4588            }
4589            StakePoolInstruction::CreateTokenMetadata { name, symbol, uri } => {
4590                msg!("Instruction: CreateTokenMetadata");
4591                Self::process_create_pool_token_metadata(program_id, accounts, name, symbol, uri)
4592            }
4593            StakePoolInstruction::UpdateTokenMetadata { name, symbol, uri } => {
4594                msg!("Instruction: UpdateTokenMetadata");
4595                Self::process_update_pool_token_metadata(program_id, accounts, name, symbol, uri)
4596            }
4597            #[allow(deprecated)]
4598            StakePoolInstruction::Redelegate { .. } => {
4599                msg!("Instruction: Redelegate will not be enabled");
4600                Err(ProgramError::InvalidInstructionData)
4601            }
4602            StakePoolInstruction::DepositStakeWithSlippage {
4603                minimum_pool_tokens_out,
4604            } => {
4605                msg!("Instruction: DepositStakeWithSlippage");
4606                Self::process_deposit_stake(program_id, accounts, Some(minimum_pool_tokens_out))
4607            }
4608            StakePoolInstruction::WithdrawStakeWithSlippage {
4609                pool_tokens_in,
4610                minimum_lamports_out,
4611            } => {
4612                msg!("Instruction: WithdrawStakeWithSlippage");
4613                Self::process_withdraw_stake(
4614                    program_id,
4615                    accounts,
4616                    pool_tokens_in,
4617                    Some(minimum_lamports_out),
4618                    None,
4619                )
4620            }
4621            StakePoolInstruction::DepositSolWithSlippage {
4622                lamports_in,
4623                minimum_pool_tokens_out,
4624            } => {
4625                msg!("Instruction: DepositSolWithSlippage");
4626                Self::process_deposit_sol(
4627                    program_id,
4628                    accounts,
4629                    lamports_in,
4630                    Some(minimum_pool_tokens_out),
4631                    false,
4632                )
4633            }
4634            StakePoolInstruction::WithdrawSolWithSlippage {
4635                pool_tokens_in,
4636                minimum_lamports_out,
4637            } => {
4638                msg!("Instruction: WithdrawSolWithSlippage");
4639                Self::process_withdraw_sol(
4640                    program_id,
4641                    accounts,
4642                    pool_tokens_in,
4643                    Some(minimum_lamports_out),
4644                )
4645            }
4646            StakePoolInstruction::DepositWsolWithSession {
4647                lamports_in,
4648                minimum_pool_tokens_out,
4649            } => {
4650                msg!("Instruction: DepositWsolWithSession");
4651                Self::process_deposit_wsol_with_session(
4652                    program_id,
4653                    accounts,
4654                    lamports_in,
4655                    Some(minimum_pool_tokens_out),
4656                )
4657            }
4658            StakePoolInstruction::WithdrawWsolWithSession {
4659                pool_tokens_in,
4660                minimum_lamports_out,
4661            } => {
4662                msg!("Instruction: WithdrawWsolWithSession");
4663                Self::process_withdraw_wsol_with_session(
4664                    program_id,
4665                    accounts,
4666                    pool_tokens_in,
4667                    Some(minimum_lamports_out),
4668                )
4669            }
4670            StakePoolInstruction::WithdrawStakeWithSession {
4671                pool_tokens_in,
4672                minimum_lamports_out,
4673                user_stake_seed,
4674            } => {
4675                msg!("Instruction: WithdrawStakeWithSession");
4676                Self::process_withdraw_stake(
4677                    program_id,
4678                    accounts,
4679                    pool_tokens_in,
4680                    Some(minimum_lamports_out),
4681                    Some(user_stake_seed),
4682                )
4683            }
4684            StakePoolInstruction::WithdrawFromStakeAccountWithSession {
4685                lamports,
4686                user_stake_seed,
4687            } => {
4688                msg!("Instruction: WithdrawFromStakeAccountWithSession");
4689                Self::process_withdraw_from_stake_account_with_session(
4690                    program_id,
4691                    accounts,
4692                    lamports,
4693                    user_stake_seed,
4694                )
4695            }
4696        }
4697    }
4698}
4699
4700impl PrintProgramError for StakePoolError {
4701    fn print<E>(&self)
4702    where
4703        E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
4704    {
4705        match self {
4706            StakePoolError::AlreadyInUse => msg!("Error: The account cannot be initialized because it is already being used"),
4707            StakePoolError::InvalidProgramAddress => msg!("Error: The program address provided doesn't match the value generated by the program"),
4708            StakePoolError::InvalidState => msg!("Error: The stake pool state is invalid"),
4709            StakePoolError::CalculationFailure => msg!("Error: The calculation failed"),
4710            StakePoolError::FeeTooHigh => msg!("Error: Stake pool fee > 1"),
4711            StakePoolError::WrongAccountMint => msg!("Error: Token account is associated with the wrong mint"),
4712            StakePoolError::WrongManager => msg!("Error: Wrong pool manager account"),
4713            StakePoolError::SignatureMissing => msg!("Error: Required signature is missing"),
4714            StakePoolError::InvalidValidatorStakeList => msg!("Error: Invalid validator stake list account"),
4715            StakePoolError::InvalidFeeAccount => msg!("Error: Invalid manager fee account"),
4716            StakePoolError::WrongPoolMint => msg!("Error: Specified pool mint account is wrong"),
4717            StakePoolError::WrongStakeStake => msg!("Error: Stake account is not in the state expected by the program"),
4718            StakePoolError::UserStakeNotActive => msg!("Error: User stake is not active"),
4719            StakePoolError::ValidatorAlreadyAdded => msg!("Error: Stake account voting for this validator already exists in the pool"),
4720            StakePoolError::ValidatorNotFound => msg!("Error: Stake account for this validator not found in the pool"),
4721            StakePoolError::InvalidStakeAccountAddress => msg!("Error: Stake account address not properly derived from the validator address"),
4722            StakePoolError::StakeListOutOfDate => msg!("Error: Identify validator stake accounts with old balances and update them"),
4723            StakePoolError::StakeListAndPoolOutOfDate => msg!("Error: First update old validator stake account balances and then pool stake balance"),
4724            StakePoolError::UnknownValidatorStakeAccount => {
4725                msg!("Error: Validator stake account is not found in the list storage")
4726            }
4727            StakePoolError::WrongMintingAuthority => msg!("Error: Wrong minting authority set for mint pool account"),
4728            StakePoolError::UnexpectedValidatorListAccountSize=> msg!("Error: The size of the given validator stake list does match the expected amount"),
4729            StakePoolError::WrongStaker=> msg!("Error: Wrong pool staker account"),
4730            StakePoolError::NonZeroPoolTokenSupply => msg!("Error: Pool token supply is not zero on initialization"),
4731            StakePoolError::StakeLamportsNotEqualToMinimum => msg!("Error: The lamports in the validator stake account is not equal to the minimum"),
4732            StakePoolError::IncorrectDepositVoteAddress => msg!("Error: The provided deposit stake account is not delegated to the preferred deposit vote account"),
4733            StakePoolError::IncorrectWithdrawVoteAddress => msg!("Error: The provided withdraw stake account is not the preferred deposit vote account"),
4734            StakePoolError::InvalidMintFreezeAuthority => msg!("Error: The mint has an invalid freeze authority"),
4735            StakePoolError::FeeIncreaseTooHigh => msg!("Error: The fee cannot increase by a factor exceeding the stipulated ratio"),
4736            StakePoolError::WithdrawalTooSmall => msg!("Error: Not enough pool tokens provided to withdraw 1-lamport stake"),
4737            StakePoolError::DepositTooSmall => msg!("Error: Not enough lamports provided for deposit to result in one pool token"),
4738            StakePoolError::InvalidStakeDepositAuthority => msg!("Error: Provided stake deposit authority does not match the program's"),
4739            StakePoolError::InvalidSolDepositAuthority => msg!("Error: Provided sol deposit authority does not match the program's"),
4740            StakePoolError::InvalidPreferredValidator => msg!("Error: Provided preferred validator is invalid"),
4741            StakePoolError::TransientAccountInUse => msg!("Error: Provided validator stake account already has a transient stake account in use"),
4742            StakePoolError::InvalidSolWithdrawAuthority => msg!("Error: Provided sol withdraw authority does not match the program's"),
4743            StakePoolError::SolWithdrawalTooLarge => msg!("Error: Too much SOL withdrawn from the stake pool's reserve account"),
4744            StakePoolError::InvalidMetadataAccount => msg!("Error: Metadata account derived from pool mint account does not match the one passed to program"),
4745            StakePoolError::UnsupportedMintExtension => msg!("Error: mint has an unsupported extension"),
4746            StakePoolError::UnsupportedFeeAccountExtension => msg!("Error: fee account has an unsupported extension"),
4747            StakePoolError::ExceededSlippage => msg!("Error: instruction exceeds desired slippage limit"),
4748            StakePoolError::IncorrectMintDecimals => msg!("Error: Provided mint does not have 9 decimals to match SOL"),
4749            StakePoolError::ReserveDepleted => msg!("Error: Pool reserve does not have enough lamports to fund rent-exempt reserve in split destination. Deposit more SOL in reserve, or pre-fund split destination with the rent-exempt reserve for a stake account."),
4750            StakePoolError::MissingRequiredSysvar => msg!("Missing required sysvar account"),
4751            StakePoolError::EpochRewardDistributionInProgress => msg!("Epoch reward distribution is currently in progress, stakes are still being updated"),
4752            StakePoolError::TooManyValidatorsInPool => msg!("The stake pool has too many validators in the pool"),
4753        }
4754    }
4755}