1use {
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
50fn 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
62fn 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 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
86fn 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 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
108fn 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 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
125fn 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
138fn 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
152fn 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
166fn 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
180fn 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
197fn 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
208fn 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
215fn 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
235fn 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
257fn 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
284fn 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
313fn 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
331pub struct Processor {}
333impl Processor {
334 #[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 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 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 #[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 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 #[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 #[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 #[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 #[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 #[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 #[inline(never)] 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 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 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 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 #[inline(never)] 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 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 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_stake_account(stake_info.clone(), stake_account_signer_seeds, stake_space)?;
1011 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 #[inline(never)] 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 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 #[inline(never)] 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 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 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 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 (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 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 let Some(reserve_stake_info) = maybe_reserve_stake_info {
1444 let required_lamports =
1445 stake_rent.saturating_sub(transient_stake_account_info.lamports());
1446 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 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 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 #[inline(never)] 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 Rent::get()?
1534 } else {
1535 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 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 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 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 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 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 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 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 let stake_state = try_from_slice_unchecked::<stake::state::StakeStateV2>(
1761 &transient_stake_account_info.data.borrow(),
1762 )?;
1763 match stake_state {
1764 stake::state::StakeStateV2::Stake(_, stake, _)
1766 if stake.delegation.activation_epoch <= clock.epoch => {}
1767 _ => {
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 #[inline(never)] 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 #[inline(always)] 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 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 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 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 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 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) => {} }
2067 }
2068 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 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 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 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 #[inline(always)] 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 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 #[inline(never)] 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 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 match validator.status.try_into() {
2343 Ok(StakeStatus::Active) => false, _ => true, }
2346 }
2347 None => true, };
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 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 match validator.status.try_into() {
2369 Ok(StakeStatus::Active) => false, _ => true, }
2372 }
2373 None => true, };
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 borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
2387 }
2388
2389 Ok(())
2390 }
2391
2392 #[inline(never)] 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 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 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 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 #[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 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 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_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 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 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 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 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 true,
2895 )
2896 }
2897
2898 #[inline(never)] 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 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 #[inline(never)] 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 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 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 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 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 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 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 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 withdraw_lamports = split_from_lamports;
3293
3294 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 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 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 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 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 } 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 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 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 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 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 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 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 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 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, clock_info.clone(),
3535 )?;
3536
3537 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 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 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 validator_list_item.status = StakeStatus::ReadyForRemoval.into();
3656 }
3657 }
3658 }
3659
3660 Ok(())
3661 }
3662
3663 #[inline(never)] 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 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 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 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 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 #[inline(never)] 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 if stake_pool.last_update_epoch < Clock::get()?.epoch {
3881 return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
3882 }
3883
3884 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 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 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 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 #[inline(never)] 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 #[inline(never)] 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 #[inline(never)] 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 #[inline(never)] 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 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 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 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 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 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 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 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}