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 solana_account_info::{next_account_info, AccountInfo},
25 solana_borsh::v1::try_from_slice_unchecked,
26 solana_clock::{Clock, Epoch},
27 solana_cpi::{invoke, invoke_signed},
28 solana_epoch_rewards::EpochRewards,
29 solana_msg::msg,
30 solana_program_error::{ProgramError, ProgramResult},
31 solana_pubkey::Pubkey,
32 solana_rent::Rent,
33 solana_stake_interface as stake,
34 solana_system_interface::{instruction as system_instruction, program as system_program},
35 solana_sysvar::{Sysvar, SysvarSerialize},
36 spl_token_2022_interface::{
37 check_spl_token_program_account,
38 extension::{BaseStateWithExtensions, StateWithExtensions},
39 native_mint,
40 state::Mint,
41 },
42 std::num::NonZeroU32,
43};
44
45fn get_stake_state(
47 stake_account_info: &AccountInfo,
48) -> Result<(stake::state::Meta, stake::state::Stake), ProgramError> {
49 let stake_state =
50 try_from_slice_unchecked::<stake::state::StakeStateV2>(&stake_account_info.data.borrow())?;
51 match stake_state {
52 stake::state::StakeStateV2::Stake(meta, stake, _) => Ok((meta, stake)),
53 _ => Err(StakePoolError::WrongStakeStake.into()),
54 }
55}
56
57fn check_validator_stake_address(
59 program_id: &Pubkey,
60 stake_pool_address: &Pubkey,
61 stake_account_address: &Pubkey,
62 vote_address: &Pubkey,
63 seed: Option<NonZeroU32>,
64) -> Result<(), ProgramError> {
65 let (validator_stake_address, _) =
67 crate::find_stake_program_address(program_id, vote_address, stake_pool_address, seed);
68 if validator_stake_address != *stake_account_address {
69 msg!(
70 "Incorrect stake account address for vote {}, expected {}, received {}",
71 vote_address,
72 validator_stake_address,
73 stake_account_address
74 );
75 Err(StakePoolError::InvalidStakeAccountAddress.into())
76 } else {
77 Ok(())
78 }
79}
80
81fn check_transient_stake_address(
83 program_id: &Pubkey,
84 stake_pool_address: &Pubkey,
85 stake_account_address: &Pubkey,
86 vote_address: &Pubkey,
87 seed: u64,
88) -> Result<u8, ProgramError> {
89 let (transient_stake_address, bump_seed) = crate::find_transient_stake_program_address(
91 program_id,
92 vote_address,
93 stake_pool_address,
94 seed,
95 );
96 if transient_stake_address != *stake_account_address {
97 Err(StakePoolError::InvalidStakeAccountAddress.into())
98 } else {
99 Ok(bump_seed)
100 }
101}
102
103fn check_ephemeral_stake_address(
105 program_id: &Pubkey,
106 stake_pool_address: &Pubkey,
107 stake_account_address: &Pubkey,
108 seed: u64,
109) -> Result<u8, ProgramError> {
110 let (ephemeral_stake_address, bump_seed) =
112 crate::find_ephemeral_stake_program_address(program_id, stake_pool_address, seed);
113 if ephemeral_stake_address != *stake_account_address {
114 Err(StakePoolError::InvalidStakeAccountAddress.into())
115 } else {
116 Ok(bump_seed)
117 }
118}
119
120fn check_mpl_metadata_account_address(
122 metadata_address: &Pubkey,
123 pool_mint: &Pubkey,
124) -> Result<(), ProgramError> {
125 let (metadata_account_pubkey, _) = find_metadata_account(pool_mint);
126 if metadata_account_pubkey != *metadata_address {
127 Err(StakePoolError::InvalidMetadataAccount.into())
128 } else {
129 Ok(())
130 }
131}
132
133fn check_system_program(program_id: &Pubkey) -> Result<(), ProgramError> {
135 if *program_id != system_program::id() {
136 msg!(
137 "Expected system program {}, received {}",
138 system_program::id(),
139 program_id
140 );
141 Err(ProgramError::IncorrectProgramId)
142 } else {
143 Ok(())
144 }
145}
146
147fn check_stake_program(program_id: &Pubkey) -> Result<(), ProgramError> {
149 if *program_id != stake::program::id() {
150 msg!(
151 "Expected stake program {}, received {}",
152 stake::program::id(),
153 program_id
154 );
155 Err(ProgramError::IncorrectProgramId)
156 } else {
157 Ok(())
158 }
159}
160
161fn check_mpl_metadata_program(program_id: &Pubkey) -> Result<(), ProgramError> {
163 if *program_id != inline_mpl_token_metadata::id() {
164 msg!(
165 "Expected mpl metadata program {}, received {}",
166 inline_mpl_token_metadata::id(),
167 program_id
168 );
169 Err(ProgramError::IncorrectProgramId)
170 } else {
171 Ok(())
172 }
173}
174
175fn check_account_owner(
177 account_info: &AccountInfo,
178 program_id: &Pubkey,
179) -> Result<(), ProgramError> {
180 if *program_id != *account_info.owner {
181 msg!(
182 "Expected account to be owned by program {}, received {}",
183 program_id,
184 account_info.owner
185 );
186 Err(ProgramError::IncorrectProgramId)
187 } else {
188 Ok(())
189 }
190}
191
192fn stake_is_usable_by_pool(
194 meta: &stake::state::Meta,
195 expected_authority: &Pubkey,
196 expected_lockup: &stake::state::Lockup,
197) -> bool {
198 meta.authorized.staker == *expected_authority
199 && meta.authorized.withdrawer == *expected_authority
200 && meta.lockup == *expected_lockup
201}
202
203fn stake_is_inactive_without_history(stake: &stake::state::Stake, epoch: Epoch) -> bool {
205 stake.delegation.deactivation_epoch < epoch
206 || (stake.delegation.activation_epoch == epoch
207 && stake.delegation.deactivation_epoch == epoch)
208}
209
210fn check_if_stake_deactivating(
212 account_info: &AccountInfo,
213 vote_account_address: &Pubkey,
214 epoch: Epoch,
215) -> Result<(), ProgramError> {
216 let (_, stake) = get_stake_state(account_info)?;
217 if stake.delegation.deactivation_epoch != epoch {
218 msg!(
219 "Existing stake {} delegated to {} not deactivated in epoch {}",
220 account_info.key,
221 vote_account_address,
222 epoch,
223 );
224 Err(StakePoolError::WrongStakeStake.into())
225 } else {
226 Ok(())
227 }
228}
229
230fn check_if_stake_activating(
232 account_info: &AccountInfo,
233 vote_account_address: &Pubkey,
234 epoch: Epoch,
235) -> Result<(), ProgramError> {
236 let (_, stake) = get_stake_state(account_info)?;
237 if stake.delegation.deactivation_epoch != Epoch::MAX
238 || stake.delegation.activation_epoch != epoch
239 {
240 msg!(
241 "Existing stake {} delegated to {} not activated in epoch {}",
242 account_info.key,
243 vote_account_address,
244 epoch,
245 );
246 Err(StakePoolError::WrongStakeStake.into())
247 } else {
248 Ok(())
249 }
250}
251
252fn check_stake_state(
255 stake_account_info: &AccountInfo,
256 withdraw_authority: &Pubkey,
257 vote_account_address: &Pubkey,
258 lockup: &stake::state::Lockup,
259) -> Result<(), ProgramError> {
260 let (meta, stake) = get_stake_state(stake_account_info)?;
261 if !stake_is_usable_by_pool(&meta, withdraw_authority, lockup) {
262 msg!(
263 "Validator stake for {} not usable by pool, must be owned by withdraw authority",
264 vote_account_address
265 );
266 return Err(StakePoolError::WrongStakeStake.into());
267 }
268 if stake.delegation.voter_pubkey != *vote_account_address {
269 msg!(
270 "Validator stake {} not delegated to {}",
271 stake_account_info.key,
272 vote_account_address
273 );
274 return Err(StakePoolError::WrongStakeStake.into());
275 }
276 Ok(())
277}
278
279fn check_validator_stake_account(
283 stake_account_info: &AccountInfo,
284 program_id: &Pubkey,
285 stake_pool: &Pubkey,
286 withdraw_authority: &Pubkey,
287 vote_account_address: &Pubkey,
288 seed: u32,
289 lockup: &stake::state::Lockup,
290) -> Result<(), ProgramError> {
291 check_account_owner(stake_account_info, &stake::program::id())?;
292 check_validator_stake_address(
293 program_id,
294 stake_pool,
295 stake_account_info.key,
296 vote_account_address,
297 NonZeroU32::new(seed),
298 )?;
299 check_stake_state(
300 stake_account_info,
301 withdraw_authority,
302 vote_account_address,
303 lockup,
304 )?;
305 Ok(())
306}
307
308fn create_stake_account(
310 stake_account_info: AccountInfo<'_>,
311 stake_account_signer_seeds: &[&[u8]],
312 stake_space: usize,
313) -> Result<(), ProgramError> {
314 invoke_signed(
315 &system_instruction::allocate(stake_account_info.key, stake_space as u64),
316 core::slice::from_ref(&stake_account_info),
317 &[stake_account_signer_seeds],
318 )?;
319 invoke_signed(
320 &system_instruction::assign(stake_account_info.key, &stake::program::id()),
321 &[stake_account_info],
322 &[stake_account_signer_seeds],
323 )
324}
325
326pub struct Processor {}
328impl Processor {
329 #[allow(clippy::too_many_arguments)]
331 fn stake_delegate<'a>(
332 stake_info: AccountInfo<'a>,
333 vote_account_info: AccountInfo<'a>,
334 clock_info: AccountInfo<'a>,
335 stake_history_info: AccountInfo<'a>,
336 stake_config_info: AccountInfo<'a>,
337 authority_info: AccountInfo<'a>,
338 stake_pool: &Pubkey,
339 authority_type: &[u8],
340 bump_seed: u8,
341 ) -> Result<(), ProgramError> {
342 let authority_signature_seeds = [stake_pool.as_ref(), authority_type, &[bump_seed]];
343 let signers = &[&authority_signature_seeds[..]];
344
345 let ix = stake::instruction::delegate_stake(
346 stake_info.key,
347 authority_info.key,
348 vote_account_info.key,
349 );
350
351 invoke_signed(
352 &ix,
353 &[
354 stake_info,
355 vote_account_info,
356 clock_info,
357 stake_history_info,
358 stake_config_info,
359 authority_info,
360 ],
361 signers,
362 )
363 }
364
365 fn stake_deactivate<'a>(
367 stake_info: AccountInfo<'a>,
368 clock_info: AccountInfo<'a>,
369 authority_info: AccountInfo<'a>,
370 stake_pool: &Pubkey,
371 authority_type: &[u8],
372 bump_seed: u8,
373 ) -> Result<(), ProgramError> {
374 let authority_signature_seeds = [stake_pool.as_ref(), authority_type, &[bump_seed]];
375 let signers = &[&authority_signature_seeds[..]];
376
377 let ix = stake::instruction::deactivate_stake(stake_info.key, authority_info.key);
378
379 invoke_signed(&ix, &[stake_info, clock_info, authority_info], signers)
380 }
381
382 fn stake_split<'a>(
384 stake_pool: &Pubkey,
385 stake_account: AccountInfo<'a>,
386 authority: AccountInfo<'a>,
387 authority_type: &[u8],
388 bump_seed: u8,
389 amount: u64,
390 split_stake: AccountInfo<'a>,
391 ) -> Result<(), ProgramError> {
392 let authority_signature_seeds = [stake_pool.as_ref(), authority_type, &[bump_seed]];
393 let signers = &[&authority_signature_seeds[..]];
394
395 let split_instruction =
396 stake::instruction::split(stake_account.key, authority.key, amount, split_stake.key);
397
398 invoke_signed(
399 split_instruction
400 .last()
401 .ok_or(ProgramError::InvalidInstructionData)?,
402 &[stake_account, split_stake, authority],
403 signers,
404 )
405 }
406
407 #[allow(clippy::too_many_arguments)]
409 fn stake_merge<'a>(
410 stake_pool: &Pubkey,
411 source_account: AccountInfo<'a>,
412 authority: AccountInfo<'a>,
413 authority_type: &[u8],
414 bump_seed: u8,
415 destination_account: AccountInfo<'a>,
416 clock: AccountInfo<'a>,
417 stake_history: AccountInfo<'a>,
418 ) -> Result<(), ProgramError> {
419 let authority_signature_seeds = [stake_pool.as_ref(), authority_type, &[bump_seed]];
420 let signers = &[&authority_signature_seeds[..]];
421
422 let merge_instruction =
423 stake::instruction::merge(destination_account.key, source_account.key, authority.key);
424
425 invoke_signed(
426 &merge_instruction[0],
427 &[
428 destination_account,
429 source_account,
430 clock,
431 stake_history,
432 authority,
433 ],
434 signers,
435 )
436 }
437
438 fn stake_authorize<'a>(
441 stake_account: AccountInfo<'a>,
442 stake_authority: AccountInfo<'a>,
443 new_stake_authority: &Pubkey,
444 clock: AccountInfo<'a>,
445 ) -> Result<(), ProgramError> {
446 let authorize_instruction = stake::instruction::authorize(
447 stake_account.key,
448 stake_authority.key,
449 new_stake_authority,
450 stake::state::StakeAuthorize::Staker,
451 None,
452 );
453
454 invoke(
455 &authorize_instruction,
456 &[
457 stake_account.clone(),
458 clock.clone(),
459 stake_authority.clone(),
460 ],
461 )?;
462
463 let authorize_instruction = stake::instruction::authorize(
464 stake_account.key,
465 stake_authority.key,
466 new_stake_authority,
467 stake::state::StakeAuthorize::Withdrawer,
468 None,
469 );
470
471 invoke(
472 &authorize_instruction,
473 &[stake_account, clock, stake_authority],
474 )
475 }
476
477 #[allow(clippy::too_many_arguments)]
480 fn stake_authorize_signed<'a>(
481 stake_pool: &Pubkey,
482 stake_account: AccountInfo<'a>,
483 stake_authority: AccountInfo<'a>,
484 authority_type: &[u8],
485 bump_seed: u8,
486 new_stake_authority: &Pubkey,
487 clock: AccountInfo<'a>,
488 ) -> Result<(), ProgramError> {
489 let authority_signature_seeds = [stake_pool.as_ref(), authority_type, &[bump_seed]];
490 let signers = &[&authority_signature_seeds[..]];
491
492 let authorize_instruction = stake::instruction::authorize(
493 stake_account.key,
494 stake_authority.key,
495 new_stake_authority,
496 stake::state::StakeAuthorize::Staker,
497 None,
498 );
499
500 invoke_signed(
501 &authorize_instruction,
502 &[
503 stake_account.clone(),
504 clock.clone(),
505 stake_authority.clone(),
506 ],
507 signers,
508 )?;
509
510 let authorize_instruction = stake::instruction::authorize(
511 stake_account.key,
512 stake_authority.key,
513 new_stake_authority,
514 stake::state::StakeAuthorize::Withdrawer,
515 None,
516 );
517 invoke_signed(
518 &authorize_instruction,
519 &[stake_account, clock, stake_authority],
520 signers,
521 )
522 }
523
524 #[allow(clippy::too_many_arguments)]
527 fn stake_withdraw<'a>(
528 stake_pool: &Pubkey,
529 source_account: AccountInfo<'a>,
530 authority: AccountInfo<'a>,
531 authority_type: &[u8],
532 bump_seed: u8,
533 destination_account: AccountInfo<'a>,
534 clock: AccountInfo<'a>,
535 stake_history: AccountInfo<'a>,
536 lamports: u64,
537 ) -> Result<(), ProgramError> {
538 let authority_signature_seeds = [stake_pool.as_ref(), authority_type, &[bump_seed]];
539 let signers = &[&authority_signature_seeds[..]];
540 let custodian_pubkey = None;
541
542 let withdraw_instruction = stake::instruction::withdraw(
543 source_account.key,
544 authority.key,
545 destination_account.key,
546 lamports,
547 custodian_pubkey,
548 );
549
550 invoke_signed(
551 &withdraw_instruction,
552 &[
553 source_account,
554 destination_account,
555 clock,
556 stake_history,
557 authority,
558 ],
559 signers,
560 )
561 }
562
563 #[allow(clippy::too_many_arguments)]
565 fn token_burn<'a>(
566 token_program: AccountInfo<'a>,
567 burn_account: AccountInfo<'a>,
568 mint: AccountInfo<'a>,
569 authority: AccountInfo<'a>,
570 amount: u64,
571 ) -> Result<(), ProgramError> {
572 let ix = spl_token_2022_interface::instruction::burn(
573 token_program.key,
574 burn_account.key,
575 mint.key,
576 authority.key,
577 &[],
578 amount,
579 )?;
580
581 invoke(&ix, &[burn_account, mint, authority])
582 }
583
584 #[allow(clippy::too_many_arguments)]
586 fn token_mint_to<'a>(
587 stake_pool: &Pubkey,
588 token_program: AccountInfo<'a>,
589 mint: AccountInfo<'a>,
590 destination: AccountInfo<'a>,
591 authority: AccountInfo<'a>,
592 authority_type: &[u8],
593 bump_seed: u8,
594 amount: u64,
595 ) -> Result<(), ProgramError> {
596 let authority_signature_seeds = [stake_pool.as_ref(), authority_type, &[bump_seed]];
597 let signers = &[&authority_signature_seeds[..]];
598
599 let ix = spl_token_2022_interface::instruction::mint_to(
600 token_program.key,
601 mint.key,
602 destination.key,
603 authority.key,
604 &[],
605 amount,
606 )?;
607
608 invoke_signed(&ix, &[mint, destination, authority], signers)
609 }
610
611 #[allow(clippy::too_many_arguments)]
613 fn token_transfer<'a>(
614 token_program: AccountInfo<'a>,
615 source: AccountInfo<'a>,
616 mint: AccountInfo<'a>,
617 destination: AccountInfo<'a>,
618 authority: AccountInfo<'a>,
619 amount: u64,
620 decimals: u8,
621 ) -> Result<(), ProgramError> {
622 let ix = spl_token_2022_interface::instruction::transfer_checked(
623 token_program.key,
624 source.key,
625 mint.key,
626 destination.key,
627 authority.key,
628 &[],
629 amount,
630 decimals,
631 )?;
632 invoke(&ix, &[source, mint, destination, authority])
633 }
634
635 fn sol_transfer<'a>(
636 source: AccountInfo<'a>,
637 destination: AccountInfo<'a>,
638 amount: u64,
639 ) -> Result<(), ProgramError> {
640 let ix = system_instruction::transfer(source.key, destination.key, amount);
641 invoke(&ix, &[source, destination])
642 }
643
644 #[inline(never)] fn process_initialize(
647 program_id: &Pubkey,
648 accounts: &[AccountInfo],
649 epoch_fee: Fee,
650 withdrawal_fee: Fee,
651 deposit_fee: Fee,
652 referral_fee: u8,
653 max_validators: u32,
654 ) -> ProgramResult {
655 let account_info_iter = &mut accounts.iter();
656 let stake_pool_info = next_account_info(account_info_iter)?;
657 let manager_info = next_account_info(account_info_iter)?;
658 let staker_info = next_account_info(account_info_iter)?;
659 let withdraw_authority_info = next_account_info(account_info_iter)?;
660 let validator_list_info = next_account_info(account_info_iter)?;
661 let reserve_stake_info = next_account_info(account_info_iter)?;
662 let pool_mint_info = next_account_info(account_info_iter)?;
663 let manager_fee_info = next_account_info(account_info_iter)?;
664 let token_program_info = next_account_info(account_info_iter)?;
665
666 let rent = Rent::get()?;
667
668 if !manager_info.is_signer {
669 msg!("Manager did not sign initialization");
670 return Err(StakePoolError::SignatureMissing.into());
671 }
672
673 if stake_pool_info.key == validator_list_info.key {
674 msg!("Cannot use same account for stake pool and validator list");
675 return Err(StakePoolError::AlreadyInUse.into());
676 }
677
678 check_account_owner(stake_pool_info, program_id)?;
681 let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
682 if !stake_pool.is_uninitialized() {
683 msg!("Provided stake pool already in use");
684 return Err(StakePoolError::AlreadyInUse.into());
685 }
686
687 check_account_owner(validator_list_info, program_id)?;
690 let mut validator_list =
691 try_from_slice_unchecked::<ValidatorList>(&validator_list_info.data.borrow())?;
692 if !validator_list.header.is_uninitialized() {
693 msg!("Provided validator list already in use");
694 return Err(StakePoolError::AlreadyInUse.into());
695 }
696
697 let data_length = validator_list_info.data_len();
698 let expected_max_validators = ValidatorList::calculate_max_validators(data_length);
699 if expected_max_validators != max_validators as usize || max_validators == 0 {
700 msg!(
701 "Incorrect validator list size provided, expected {}, provided {}",
702 expected_max_validators,
703 max_validators
704 );
705 return Err(StakePoolError::UnexpectedValidatorListAccountSize.into());
706 }
707 if max_validators > MAX_VALIDATORS_IN_POOL {
708 return Err(StakePoolError::TooManyValidatorsInPool.into());
709 }
710 validator_list.header.account_type = AccountType::ValidatorList;
711 validator_list.header.max_validators = max_validators;
712 validator_list.validators.clear();
713
714 if !rent.is_exempt(stake_pool_info.lamports(), stake_pool_info.data_len()) {
715 msg!("Stake pool not rent-exempt");
716 return Err(ProgramError::AccountNotRentExempt);
717 }
718
719 if !rent.is_exempt(
720 validator_list_info.lamports(),
721 validator_list_info.data_len(),
722 ) {
723 msg!("Validator stake list not rent-exempt");
724 return Err(ProgramError::AccountNotRentExempt);
725 }
726
727 if epoch_fee.numerator > epoch_fee.denominator
729 || withdrawal_fee.numerator > withdrawal_fee.denominator
730 || deposit_fee.numerator > deposit_fee.denominator
731 || referral_fee > 100u8
732 {
733 return Err(StakePoolError::FeeTooHigh.into());
734 }
735
736 check_spl_token_program_account(token_program_info.key)?;
737
738 if pool_mint_info.owner != token_program_info.key {
739 return Err(ProgramError::IncorrectProgramId);
740 }
741
742 stake_pool.token_program_id = *token_program_info.key;
743 stake_pool.pool_mint = *pool_mint_info.key;
744
745 let (stake_deposit_authority, sol_deposit_authority) =
746 match next_account_info(account_info_iter) {
747 Ok(deposit_authority_info) => (
748 *deposit_authority_info.key,
749 Some(*deposit_authority_info.key),
750 ),
751 Err(_) => (
752 find_deposit_authority_program_address(program_id, stake_pool_info.key).0,
753 None,
754 ),
755 };
756 let (withdraw_authority_key, stake_withdraw_bump_seed) =
757 crate::find_withdraw_authority_program_address(program_id, stake_pool_info.key);
758 if withdraw_authority_key != *withdraw_authority_info.key {
759 msg!(
760 "Incorrect withdraw authority provided, expected {}, received {}",
761 withdraw_authority_key,
762 withdraw_authority_info.key
763 );
764 return Err(StakePoolError::InvalidProgramAddress.into());
765 }
766
767 {
768 let pool_mint_data = pool_mint_info.try_borrow_data()?;
769 let pool_mint = StateWithExtensions::<Mint>::unpack(&pool_mint_data)?;
770
771 if pool_mint.base.supply != 0 {
772 return Err(StakePoolError::NonZeroPoolTokenSupply.into());
773 }
774
775 if pool_mint.base.decimals != native_mint::DECIMALS {
776 return Err(StakePoolError::IncorrectMintDecimals.into());
777 }
778
779 if !pool_mint
780 .base
781 .mint_authority
782 .contains(&withdraw_authority_key)
783 {
784 return Err(StakePoolError::WrongMintingAuthority.into());
785 }
786
787 if pool_mint.base.freeze_authority.is_some() {
788 return Err(StakePoolError::InvalidMintFreezeAuthority.into());
789 }
790
791 let extensions = pool_mint.get_extension_types()?;
792 if extensions
793 .iter()
794 .any(|x| !is_extension_supported_for_mint(x))
795 {
796 return Err(StakePoolError::UnsupportedMintExtension.into());
797 }
798 }
799 stake_pool.check_manager_fee_info(manager_fee_info)?;
800
801 if *reserve_stake_info.owner != stake::program::id() {
802 msg!("Reserve stake account not owned by stake program");
803 return Err(ProgramError::IncorrectProgramId);
804 }
805 let reserve_rent = rent.minimum_balance(reserve_stake_info.data_len());
806 let stake_state = try_from_slice_unchecked::<stake::state::StakeStateV2>(
807 &reserve_stake_info.data.borrow(),
808 )?;
809 let total_lamports = if let stake::state::StakeStateV2::Initialized(meta) = stake_state {
810 if meta.lockup != stake::state::Lockup::default() {
811 msg!("Reserve stake account has some lockup");
812 return Err(StakePoolError::WrongStakeStake.into());
813 }
814
815 if meta.authorized.staker != withdraw_authority_key {
816 msg!(
817 "Reserve stake account has incorrect staker {}, should be {}",
818 meta.authorized.staker,
819 withdraw_authority_key
820 );
821 return Err(StakePoolError::WrongStakeStake.into());
822 }
823
824 if meta.authorized.withdrawer != withdraw_authority_key {
825 msg!(
826 "Reserve stake account has incorrect withdrawer {}, should be {}",
827 meta.authorized.staker,
828 withdraw_authority_key
829 );
830 return Err(StakePoolError::WrongStakeStake.into());
831 }
832 reserve_stake_info
833 .lamports()
834 .checked_sub(minimum_reserve_lamports(reserve_rent))
835 .ok_or(StakePoolError::CalculationFailure)?
836 } else {
837 msg!("Reserve stake account not in intialized state");
838 return Err(StakePoolError::WrongStakeStake.into());
839 };
840
841 if total_lamports > 0 {
842 Self::token_mint_to(
843 stake_pool_info.key,
844 token_program_info.clone(),
845 pool_mint_info.clone(),
846 manager_fee_info.clone(),
847 withdraw_authority_info.clone(),
848 AUTHORITY_WITHDRAW,
849 stake_withdraw_bump_seed,
850 total_lamports,
851 )?;
852 }
853
854 borsh::to_writer(
855 &mut validator_list_info.data.borrow_mut()[..],
856 &validator_list,
857 )?;
858
859 stake_pool.account_type = AccountType::StakePool;
860 stake_pool.manager = *manager_info.key;
861 stake_pool.staker = *staker_info.key;
862 stake_pool.stake_deposit_authority = stake_deposit_authority;
863 stake_pool.stake_withdraw_bump_seed = stake_withdraw_bump_seed;
864 stake_pool.validator_list = *validator_list_info.key;
865 stake_pool.reserve_stake = *reserve_stake_info.key;
866 stake_pool.manager_fee_account = *manager_fee_info.key;
867 stake_pool.total_lamports = total_lamports;
868 stake_pool.pool_token_supply = total_lamports;
869 stake_pool.last_update_epoch = Clock::get()?.epoch;
870 stake_pool.lockup = stake::state::Lockup::default();
871 stake_pool.epoch_fee = epoch_fee;
872 stake_pool.next_epoch_fee = FutureEpoch::None;
873 stake_pool.preferred_deposit_validator_vote_address = None;
874 stake_pool.preferred_withdraw_validator_vote_address = None;
875 stake_pool.stake_deposit_fee = deposit_fee;
876 stake_pool.stake_withdrawal_fee = withdrawal_fee;
877 stake_pool.next_stake_withdrawal_fee = FutureEpoch::None;
878 stake_pool.stake_referral_fee = referral_fee;
879 stake_pool.sol_deposit_authority = sol_deposit_authority;
880 stake_pool.sol_deposit_fee = deposit_fee;
881 stake_pool.sol_referral_fee = referral_fee;
882 stake_pool.sol_withdraw_authority = None;
883 stake_pool.sol_withdrawal_fee = withdrawal_fee;
884 stake_pool.next_sol_withdrawal_fee = FutureEpoch::None;
885 stake_pool.last_epoch_pool_token_supply = 0;
886 stake_pool.last_epoch_total_lamports = 0;
887
888 borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)
889 .map_err(|e| e.into())
890 }
891
892 #[inline(never)] fn process_add_validator_to_pool(
895 program_id: &Pubkey,
896 accounts: &[AccountInfo],
897 raw_validator_seed: u32,
898 ) -> ProgramResult {
899 let account_info_iter = &mut accounts.iter();
900 let stake_pool_info = next_account_info(account_info_iter)?;
901 let staker_info = next_account_info(account_info_iter)?;
902 let reserve_stake_info = next_account_info(account_info_iter)?;
903 let withdraw_authority_info = next_account_info(account_info_iter)?;
904 let validator_list_info = next_account_info(account_info_iter)?;
905 let stake_info = next_account_info(account_info_iter)?;
906 let validator_vote_info = next_account_info(account_info_iter)?;
907 let rent_info = next_account_info(account_info_iter)?;
908 let rent = &Rent::from_account_info(rent_info)?;
909 let clock_info = next_account_info(account_info_iter)?;
910 let clock = &Clock::from_account_info(clock_info)?;
911 let stake_history_info = next_account_info(account_info_iter)?;
912 let stake_config_info = next_account_info(account_info_iter)?;
913 let system_program_info = next_account_info(account_info_iter)?;
914 let stake_program_info = next_account_info(account_info_iter)?;
915
916 check_system_program(system_program_info.key)?;
917 check_stake_program(stake_program_info.key)?;
918
919 check_account_owner(stake_pool_info, program_id)?;
920 let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
921 if !stake_pool.is_valid() {
922 return Err(StakePoolError::InvalidState.into());
923 }
924
925 stake_pool.check_authority_withdraw(
926 withdraw_authority_info.key,
927 program_id,
928 stake_pool_info.key,
929 )?;
930
931 stake_pool.check_staker(staker_info)?;
932 stake_pool.check_reserve_stake(reserve_stake_info)?;
933 stake_pool.check_validator_list(validator_list_info)?;
934
935 if stake_pool.last_update_epoch < clock.epoch {
936 return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
937 }
938
939 check_account_owner(validator_list_info, program_id)?;
940 let mut validator_list_data = validator_list_info.data.borrow_mut();
941 let (header, mut validator_list) =
942 ValidatorListHeader::deserialize_vec(&mut validator_list_data)?;
943 if !header.is_valid() {
944 return Err(StakePoolError::InvalidState.into());
945 }
946 if header.max_validators == validator_list.len() {
947 return Err(ProgramError::AccountDataTooSmall);
948 }
949 if validator_list.len() >= MAX_VALIDATORS_IN_POOL {
950 return Err(StakePoolError::TooManyValidatorsInPool.into());
951 }
952 let maybe_validator_stake_info = validator_list.find::<ValidatorStakeInfo, _>(|x| {
953 ValidatorStakeInfo::memcmp_pubkey(x, validator_vote_info.key)
954 });
955 if maybe_validator_stake_info.is_some() {
956 return Err(StakePoolError::ValidatorAlreadyAdded.into());
957 }
958
959 let validator_seed = NonZeroU32::new(raw_validator_seed);
960 let (stake_address, bump_seed) = crate::find_stake_program_address(
961 program_id,
962 validator_vote_info.key,
963 stake_pool_info.key,
964 validator_seed,
965 );
966 if stake_address != *stake_info.key {
967 return Err(StakePoolError::InvalidStakeAccountAddress.into());
968 }
969
970 let validator_seed_bytes = validator_seed.map(|s| s.get().to_le_bytes());
971 let stake_account_signer_seeds: &[&[_]] = &[
972 validator_vote_info.key.as_ref(),
973 stake_pool_info.key.as_ref(),
974 validator_seed_bytes
975 .as_ref()
976 .map(|s| s.as_slice())
977 .unwrap_or(&[]),
978 &[bump_seed],
979 ];
980
981 let stake_space = std::mem::size_of::<stake::state::StakeStateV2>();
983 let stake_minimum_delegation = stake::tools::get_minimum_delegation()?;
984 let required_lamports = minimum_delegation(stake_minimum_delegation)
985 .saturating_add(rent.minimum_balance(stake_space));
986
987 let reserve_rent = rent.minimum_balance(reserve_stake_info.data_len());
989 let minimum_lamports = minimum_reserve_lamports(reserve_rent);
990 let reserve_lamports = reserve_stake_info.lamports();
991 if reserve_lamports.saturating_sub(required_lamports) < minimum_lamports {
992 msg!(
993 "Need to add {} lamports for the reserve stake to be rent-exempt after adding a validator, reserve currently has {} lamports",
994 required_lamports.saturating_add(minimum_lamports).saturating_sub(reserve_lamports),
995 reserve_lamports
996 );
997 return Err(ProgramError::InsufficientFunds);
998 }
999
1000 create_stake_account(stake_info.clone(), stake_account_signer_seeds, stake_space)?;
1002 Self::stake_split(
1004 stake_pool_info.key,
1005 reserve_stake_info.clone(),
1006 withdraw_authority_info.clone(),
1007 AUTHORITY_WITHDRAW,
1008 stake_pool.stake_withdraw_bump_seed,
1009 required_lamports,
1010 stake_info.clone(),
1011 )?;
1012
1013 Self::stake_delegate(
1014 stake_info.clone(),
1015 validator_vote_info.clone(),
1016 clock_info.clone(),
1017 stake_history_info.clone(),
1018 stake_config_info.clone(),
1019 withdraw_authority_info.clone(),
1020 stake_pool_info.key,
1021 AUTHORITY_WITHDRAW,
1022 stake_pool.stake_withdraw_bump_seed,
1023 )?;
1024
1025 validator_list.push(ValidatorStakeInfo {
1026 status: StakeStatus::Active.into(),
1027 vote_account_address: *validator_vote_info.key,
1028 active_stake_lamports: required_lamports.into(),
1029 transient_stake_lamports: 0.into(),
1030 last_update_epoch: clock.epoch.into(),
1031 transient_seed_suffix: 0.into(),
1032 unused: 0.into(),
1033 validator_seed_suffix: raw_validator_seed.into(),
1034 })?;
1035
1036 Ok(())
1037 }
1038
1039 #[inline(never)] fn process_remove_validator_from_pool(
1042 program_id: &Pubkey,
1043 accounts: &[AccountInfo],
1044 ) -> ProgramResult {
1045 let account_info_iter = &mut accounts.iter();
1046 let stake_pool_info = next_account_info(account_info_iter)?;
1047 let staker_info = next_account_info(account_info_iter)?;
1048 let withdraw_authority_info = next_account_info(account_info_iter)?;
1049 let validator_list_info = next_account_info(account_info_iter)?;
1050 let stake_account_info = next_account_info(account_info_iter)?;
1051 let transient_stake_account_info = next_account_info(account_info_iter)?;
1052 let clock_info = next_account_info(account_info_iter)?;
1053 let clock = &Clock::from_account_info(clock_info)?;
1054 let stake_program_info = next_account_info(account_info_iter)?;
1055
1056 check_stake_program(stake_program_info.key)?;
1057 check_account_owner(stake_pool_info, program_id)?;
1058
1059 let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
1060 if !stake_pool.is_valid() {
1061 return Err(StakePoolError::InvalidState.into());
1062 }
1063
1064 stake_pool.check_authority_withdraw(
1065 withdraw_authority_info.key,
1066 program_id,
1067 stake_pool_info.key,
1068 )?;
1069 stake_pool.check_staker(staker_info)?;
1070
1071 if stake_pool.last_update_epoch < clock.epoch {
1072 msg!(
1073 "clock {} pool {}",
1074 clock.epoch,
1075 stake_pool.last_update_epoch
1076 );
1077 return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
1078 }
1079
1080 stake_pool.check_validator_list(validator_list_info)?;
1081
1082 check_account_owner(validator_list_info, program_id)?;
1083 let mut validator_list_data = validator_list_info.data.borrow_mut();
1084 let (header, mut validator_list) =
1085 ValidatorListHeader::deserialize_vec(&mut validator_list_data)?;
1086 if !header.is_valid() {
1087 return Err(StakePoolError::InvalidState.into());
1088 }
1089
1090 let (_, stake) = get_stake_state(stake_account_info)?;
1091 let vote_account_address = stake.delegation.voter_pubkey;
1092 let maybe_validator_stake_info = validator_list.find_mut::<ValidatorStakeInfo, _>(|x| {
1093 ValidatorStakeInfo::memcmp_pubkey(x, &vote_account_address)
1094 });
1095 if maybe_validator_stake_info.is_none() {
1096 msg!(
1097 "Vote account {} not found in stake pool",
1098 vote_account_address
1099 );
1100 return Err(StakePoolError::ValidatorNotFound.into());
1101 }
1102 let validator_stake_info = maybe_validator_stake_info.unwrap();
1103 check_validator_stake_address(
1104 program_id,
1105 stake_pool_info.key,
1106 stake_account_info.key,
1107 &vote_account_address,
1108 NonZeroU32::new(validator_stake_info.validator_seed_suffix.into()),
1109 )?;
1110
1111 if validator_stake_info.status != StakeStatus::Active.into() {
1112 msg!("Validator is already marked for removal");
1113 return Err(StakePoolError::ValidatorNotFound.into());
1114 }
1115
1116 let new_status = if u64::from(validator_stake_info.transient_stake_lamports) > 0 {
1117 check_transient_stake_address(
1118 program_id,
1119 stake_pool_info.key,
1120 transient_stake_account_info.key,
1121 &vote_account_address,
1122 validator_stake_info.transient_seed_suffix.into(),
1123 )?;
1124
1125 match get_stake_state(transient_stake_account_info) {
1126 Ok((meta, stake))
1127 if stake_is_usable_by_pool(
1128 &meta,
1129 withdraw_authority_info.key,
1130 &stake_pool.lockup,
1131 ) =>
1132 {
1133 if stake.delegation.deactivation_epoch == Epoch::MAX {
1134 Self::stake_deactivate(
1135 transient_stake_account_info.clone(),
1136 clock_info.clone(),
1137 withdraw_authority_info.clone(),
1138 stake_pool_info.key,
1139 AUTHORITY_WITHDRAW,
1140 stake_pool.stake_withdraw_bump_seed,
1141 )?;
1142 }
1143 }
1144 _ => (),
1145 }
1146 StakeStatus::DeactivatingAll
1147 } else {
1148 StakeStatus::DeactivatingValidator
1149 };
1150
1151 if stake.delegation.deactivation_epoch == Epoch::MAX {
1154 Self::stake_deactivate(
1155 stake_account_info.clone(),
1156 clock_info.clone(),
1157 withdraw_authority_info.clone(),
1158 stake_pool_info.key,
1159 AUTHORITY_WITHDRAW,
1160 stake_pool.stake_withdraw_bump_seed,
1161 )?;
1162 }
1163
1164 validator_stake_info.status = new_status.into();
1165
1166 if stake_pool.preferred_deposit_validator_vote_address == Some(vote_account_address) {
1167 stake_pool.preferred_deposit_validator_vote_address = None;
1168 }
1169 if stake_pool.preferred_withdraw_validator_vote_address == Some(vote_account_address) {
1170 stake_pool.preferred_withdraw_validator_vote_address = None;
1171 }
1172 borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
1173
1174 Ok(())
1175 }
1176
1177 #[inline(never)] fn process_decrease_validator_stake(
1180 program_id: &Pubkey,
1181 accounts: &[AccountInfo],
1182 lamports: u64,
1183 transient_stake_seed: u64,
1184 maybe_ephemeral_stake_seed: Option<u64>,
1185 fund_rent_exempt_reserve: bool,
1186 ) -> ProgramResult {
1187 let account_info_iter = &mut accounts.iter();
1188 let stake_pool_info = next_account_info(account_info_iter)?;
1189 let staker_info = next_account_info(account_info_iter)?;
1190 let withdraw_authority_info = next_account_info(account_info_iter)?;
1191 let validator_list_info = next_account_info(account_info_iter)?;
1192 let maybe_reserve_stake_info = fund_rent_exempt_reserve
1193 .then(|| next_account_info(account_info_iter))
1194 .transpose()?;
1195 let validator_stake_account_info = next_account_info(account_info_iter)?;
1196 let maybe_ephemeral_stake_account_info = maybe_ephemeral_stake_seed
1197 .map(|_| next_account_info(account_info_iter))
1198 .transpose()?;
1199 let transient_stake_account_info = next_account_info(account_info_iter)?;
1200 let clock_info = next_account_info(account_info_iter)?;
1201 let clock = &Clock::from_account_info(clock_info)?;
1202 let (rent, maybe_stake_history_info) =
1203 if maybe_ephemeral_stake_seed.is_some() || fund_rent_exempt_reserve {
1204 (Rent::get()?, Some(next_account_info(account_info_iter)?))
1205 } else {
1206 let rent_info = next_account_info(account_info_iter)?;
1208 (Rent::from_account_info(rent_info)?, None)
1209 };
1210 let system_program_info = next_account_info(account_info_iter)?;
1211 let stake_program_info = next_account_info(account_info_iter)?;
1212
1213 check_system_program(system_program_info.key)?;
1214 check_stake_program(stake_program_info.key)?;
1215 check_account_owner(stake_pool_info, program_id)?;
1216
1217 let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
1218 if !stake_pool.is_valid() {
1219 msg!("Expected valid stake pool");
1220 return Err(StakePoolError::InvalidState.into());
1221 }
1222
1223 stake_pool.check_authority_withdraw(
1224 withdraw_authority_info.key,
1225 program_id,
1226 stake_pool_info.key,
1227 )?;
1228 stake_pool.check_staker(staker_info)?;
1229
1230 if stake_pool.last_update_epoch < clock.epoch {
1231 return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
1232 }
1233
1234 stake_pool.check_validator_list(validator_list_info)?;
1235 check_account_owner(validator_list_info, program_id)?;
1236 let validator_list_data = &mut *validator_list_info.data.borrow_mut();
1237 let (validator_list_header, mut validator_list) =
1238 ValidatorListHeader::deserialize_vec(validator_list_data)?;
1239 if !validator_list_header.is_valid() {
1240 return Err(StakePoolError::InvalidState.into());
1241 }
1242
1243 if let Some(reserve_stake_info) = maybe_reserve_stake_info {
1244 stake_pool.check_reserve_stake(reserve_stake_info)?;
1245 }
1246
1247 let validator_stake_rent = rent.minimum_balance(validator_stake_account_info.data_len());
1248 let (_, stake) = get_stake_state(validator_stake_account_info)?;
1249 let vote_account_address = stake.delegation.voter_pubkey;
1250
1251 let maybe_validator_stake_info = validator_list.find_mut::<ValidatorStakeInfo, _>(|x| {
1252 ValidatorStakeInfo::memcmp_pubkey(x, &vote_account_address)
1253 });
1254 if maybe_validator_stake_info.is_none() {
1255 msg!(
1256 "Vote account {} not found in stake pool",
1257 vote_account_address
1258 );
1259 return Err(StakePoolError::ValidatorNotFound.into());
1260 }
1261 let validator_stake_info = maybe_validator_stake_info.unwrap();
1262 check_validator_stake_address(
1263 program_id,
1264 stake_pool_info.key,
1265 validator_stake_account_info.key,
1266 &vote_account_address,
1267 NonZeroU32::new(validator_stake_info.validator_seed_suffix.into()),
1268 )?;
1269 if u64::from(validator_stake_info.transient_stake_lamports) > 0 {
1270 if maybe_ephemeral_stake_seed.is_none() {
1271 msg!("Attempting to decrease stake on a validator with pending transient stake, use DecreaseAdditionalValidatorStake with the existing seed");
1272 return Err(StakePoolError::TransientAccountInUse.into());
1273 }
1274 if transient_stake_seed != u64::from(validator_stake_info.transient_seed_suffix) {
1275 msg!(
1276 "Transient stake already exists with seed {}, you must use that one",
1277 u64::from(validator_stake_info.transient_seed_suffix)
1278 );
1279 return Err(ProgramError::InvalidSeeds);
1280 }
1281 check_if_stake_deactivating(
1282 transient_stake_account_info,
1283 &vote_account_address,
1284 clock.epoch,
1285 )?;
1286 }
1287
1288 if validator_stake_info.status != StakeStatus::Active.into() {
1289 msg!("Validator is marked for removal and no longer allows decreases");
1290 return Err(StakePoolError::ValidatorNotFound.into());
1291 }
1292
1293 let stake_space = std::mem::size_of::<stake::state::StakeStateV2>();
1294 let stake_rent = rent.minimum_balance(stake_space);
1295
1296 let stake_minimum_delegation = stake::tools::get_minimum_delegation()?;
1297 let current_minimum_lamports = minimum_delegation(stake_minimum_delegation);
1298 if lamports < current_minimum_lamports {
1299 msg!(
1300 "Need at least {} lamports for transient stake to meet minimum delegation and rent-exempt requirements, {} provided",
1301 current_minimum_lamports,
1302 lamports
1303 );
1304 return Err(ProgramError::AccountNotRentExempt);
1305 }
1306
1307 let remaining_lamports = validator_stake_account_info
1308 .lamports()
1309 .checked_sub(lamports)
1310 .ok_or(ProgramError::InsufficientFunds)?;
1311 let required_lamports =
1312 minimum_stake_lamports(validator_stake_rent, stake_minimum_delegation);
1313 if remaining_lamports < required_lamports {
1314 msg!("Need at least {} lamports in the stake account after decrease, {} requested, {} is the current possible maximum",
1315 required_lamports,
1316 lamports,
1317 validator_stake_account_info.lamports().checked_sub(required_lamports).ok_or(StakePoolError::CalculationFailure)?
1318 );
1319 return Err(ProgramError::InsufficientFunds);
1320 }
1321
1322 let (source_stake_account_info, split_lamports) =
1323 if let Some((ephemeral_stake_seed, ephemeral_stake_account_info)) =
1324 maybe_ephemeral_stake_seed.zip(maybe_ephemeral_stake_account_info)
1325 {
1326 let ephemeral_stake_bump_seed = check_ephemeral_stake_address(
1327 program_id,
1328 stake_pool_info.key,
1329 ephemeral_stake_account_info.key,
1330 ephemeral_stake_seed,
1331 )?;
1332 let ephemeral_stake_account_signer_seeds: &[&[_]] = &[
1333 EPHEMERAL_STAKE_SEED_PREFIX,
1334 stake_pool_info.key.as_ref(),
1335 &ephemeral_stake_seed.to_le_bytes(),
1336 &[ephemeral_stake_bump_seed],
1337 ];
1338 create_stake_account(
1339 ephemeral_stake_account_info.clone(),
1340 ephemeral_stake_account_signer_seeds,
1341 stake_space,
1342 )?;
1343
1344 if let Some(reserve_stake_info) = maybe_reserve_stake_info {
1346 let required_lamports_for_rent_exemption =
1347 stake_rent.saturating_sub(ephemeral_stake_account_info.lamports());
1348 if required_lamports_for_rent_exemption > 0 {
1349 if required_lamports_for_rent_exemption >= reserve_stake_info.lamports() {
1350 return Err(StakePoolError::ReserveDepleted.into());
1351 }
1352 let stake_history_info = maybe_stake_history_info
1353 .ok_or(StakePoolError::MissingRequiredSysvar)?;
1354 Self::stake_withdraw(
1355 stake_pool_info.key,
1356 reserve_stake_info.clone(),
1357 withdraw_authority_info.clone(),
1358 AUTHORITY_WITHDRAW,
1359 stake_pool.stake_withdraw_bump_seed,
1360 ephemeral_stake_account_info.clone(),
1361 clock_info.clone(),
1362 stake_history_info.clone(),
1363 required_lamports_for_rent_exemption,
1364 )?;
1365 }
1366 }
1367
1368 Self::stake_split(
1370 stake_pool_info.key,
1371 validator_stake_account_info.clone(),
1372 withdraw_authority_info.clone(),
1373 AUTHORITY_WITHDRAW,
1374 stake_pool.stake_withdraw_bump_seed,
1375 lamports,
1376 ephemeral_stake_account_info.clone(),
1377 )?;
1378
1379 Self::stake_deactivate(
1380 ephemeral_stake_account_info.clone(),
1381 clock_info.clone(),
1382 withdraw_authority_info.clone(),
1383 stake_pool_info.key,
1384 AUTHORITY_WITHDRAW,
1385 stake_pool.stake_withdraw_bump_seed,
1386 )?;
1387
1388 (
1389 ephemeral_stake_account_info,
1390 ephemeral_stake_account_info.lamports(),
1391 )
1392 } else {
1393 (validator_stake_account_info, lamports)
1396 };
1397
1398 let transient_stake_bump_seed = check_transient_stake_address(
1399 program_id,
1400 stake_pool_info.key,
1401 transient_stake_account_info.key,
1402 &vote_account_address,
1403 transient_stake_seed,
1404 )?;
1405
1406 if u64::from(validator_stake_info.transient_stake_lamports) > 0 {
1407 let stake_history_info = maybe_stake_history_info.unwrap();
1408 Self::stake_merge(
1411 stake_pool_info.key,
1412 source_stake_account_info.clone(),
1413 withdraw_authority_info.clone(),
1414 AUTHORITY_WITHDRAW,
1415 stake_pool.stake_withdraw_bump_seed,
1416 transient_stake_account_info.clone(),
1417 clock_info.clone(),
1418 stake_history_info.clone(),
1419 )?;
1420 } else {
1421 let transient_stake_account_signer_seeds: &[&[_]] = &[
1422 TRANSIENT_STAKE_SEED_PREFIX,
1423 vote_account_address.as_ref(),
1424 stake_pool_info.key.as_ref(),
1425 &transient_stake_seed.to_le_bytes(),
1426 &[transient_stake_bump_seed],
1427 ];
1428
1429 create_stake_account(
1430 transient_stake_account_info.clone(),
1431 transient_stake_account_signer_seeds,
1432 stake_space,
1433 )?;
1434
1435 if let Some(reserve_stake_info) = maybe_reserve_stake_info {
1437 let required_lamports =
1438 stake_rent.saturating_sub(transient_stake_account_info.lamports());
1439 if source_stake_account_info.lamports() != split_lamports {
1443 let stake_history_info =
1444 maybe_stake_history_info.ok_or(StakePoolError::MissingRequiredSysvar)?;
1445 if required_lamports >= reserve_stake_info.lamports() {
1446 return Err(StakePoolError::ReserveDepleted.into());
1447 }
1448 if required_lamports > 0 {
1449 Self::stake_withdraw(
1450 stake_pool_info.key,
1451 reserve_stake_info.clone(),
1452 withdraw_authority_info.clone(),
1453 AUTHORITY_WITHDRAW,
1454 stake_pool.stake_withdraw_bump_seed,
1455 transient_stake_account_info.clone(),
1456 clock_info.clone(),
1457 stake_history_info.clone(),
1458 required_lamports,
1459 )?;
1460 }
1461 }
1462 }
1463
1464 Self::stake_split(
1466 stake_pool_info.key,
1467 source_stake_account_info.clone(),
1468 withdraw_authority_info.clone(),
1469 AUTHORITY_WITHDRAW,
1470 stake_pool.stake_withdraw_bump_seed,
1471 split_lamports,
1472 transient_stake_account_info.clone(),
1473 )?;
1474
1475 let (_, stake) = get_stake_state(transient_stake_account_info)?;
1477 if stake.delegation.deactivation_epoch == Epoch::MAX {
1478 Self::stake_deactivate(
1479 transient_stake_account_info.clone(),
1480 clock_info.clone(),
1481 withdraw_authority_info.clone(),
1482 stake_pool_info.key,
1483 AUTHORITY_WITHDRAW,
1484 stake_pool.stake_withdraw_bump_seed,
1485 )?;
1486 }
1487 }
1488
1489 validator_stake_info.active_stake_lamports =
1490 u64::from(validator_stake_info.active_stake_lamports)
1491 .checked_sub(lamports)
1492 .ok_or(StakePoolError::CalculationFailure)?
1493 .into();
1494 validator_stake_info.transient_stake_lamports =
1495 transient_stake_account_info.lamports().into();
1496 validator_stake_info.transient_seed_suffix = transient_stake_seed.into();
1497
1498 Ok(())
1499 }
1500
1501 #[inline(never)] fn process_increase_validator_stake(
1504 program_id: &Pubkey,
1505 accounts: &[AccountInfo],
1506 lamports: u64,
1507 transient_stake_seed: u64,
1508 maybe_ephemeral_stake_seed: Option<u64>,
1509 ) -> ProgramResult {
1510 let account_info_iter = &mut accounts.iter();
1511 let stake_pool_info = next_account_info(account_info_iter)?;
1512 let staker_info = next_account_info(account_info_iter)?;
1513 let withdraw_authority_info = next_account_info(account_info_iter)?;
1514 let validator_list_info = next_account_info(account_info_iter)?;
1515 let reserve_stake_account_info = next_account_info(account_info_iter)?;
1516 let maybe_ephemeral_stake_account_info = maybe_ephemeral_stake_seed
1517 .map(|_| next_account_info(account_info_iter))
1518 .transpose()?;
1519 let transient_stake_account_info = next_account_info(account_info_iter)?;
1520 let validator_stake_account_info = next_account_info(account_info_iter)?;
1521 let validator_vote_account_info = next_account_info(account_info_iter)?;
1522 let clock_info = next_account_info(account_info_iter)?;
1523 let clock = &Clock::from_account_info(clock_info)?;
1524 let rent = if maybe_ephemeral_stake_seed.is_some() {
1525 Rent::get()?
1527 } else {
1528 let rent_info = next_account_info(account_info_iter)?;
1530 Rent::from_account_info(rent_info)?
1531 };
1532 let stake_history_info = next_account_info(account_info_iter)?;
1533 let stake_config_info = next_account_info(account_info_iter)?;
1534 let system_program_info = next_account_info(account_info_iter)?;
1535 let stake_program_info = next_account_info(account_info_iter)?;
1536
1537 check_system_program(system_program_info.key)?;
1538 check_stake_program(stake_program_info.key)?;
1539 check_account_owner(stake_pool_info, program_id)?;
1540
1541 let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
1542 if !stake_pool.is_valid() {
1543 msg!("Expected valid stake pool");
1544 return Err(StakePoolError::InvalidState.into());
1545 }
1546
1547 stake_pool.check_authority_withdraw(
1548 withdraw_authority_info.key,
1549 program_id,
1550 stake_pool_info.key,
1551 )?;
1552 stake_pool.check_staker(staker_info)?;
1553
1554 if stake_pool.last_update_epoch < clock.epoch {
1555 return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
1556 }
1557
1558 stake_pool.check_validator_list(validator_list_info)?;
1559 stake_pool.check_reserve_stake(reserve_stake_account_info)?;
1560 check_account_owner(validator_list_info, program_id)?;
1561
1562 let mut validator_list_data = validator_list_info.data.borrow_mut();
1563 let (header, mut validator_list) =
1564 ValidatorListHeader::deserialize_vec(&mut validator_list_data)?;
1565 if !header.is_valid() {
1566 return Err(StakePoolError::InvalidState.into());
1567 }
1568
1569 let vote_account_address = validator_vote_account_info.key;
1570
1571 let maybe_validator_stake_info = validator_list.find_mut::<ValidatorStakeInfo, _>(|x| {
1572 ValidatorStakeInfo::memcmp_pubkey(x, vote_account_address)
1573 });
1574 if maybe_validator_stake_info.is_none() {
1575 msg!(
1576 "Vote account {} not found in stake pool",
1577 vote_account_address
1578 );
1579 return Err(StakePoolError::ValidatorNotFound.into());
1580 }
1581 let validator_stake_info = maybe_validator_stake_info.unwrap();
1582 if u64::from(validator_stake_info.transient_stake_lamports) > 0 {
1583 if maybe_ephemeral_stake_seed.is_none() {
1584 msg!("Attempting to increase stake on a validator with pending transient stake, use IncreaseAdditionalValidatorStake with the existing seed");
1585 return Err(StakePoolError::TransientAccountInUse.into());
1586 }
1587 if transient_stake_seed != u64::from(validator_stake_info.transient_seed_suffix) {
1588 msg!(
1589 "Transient stake already exists with seed {}, you must use that one",
1590 u64::from(validator_stake_info.transient_seed_suffix)
1591 );
1592 return Err(ProgramError::InvalidSeeds);
1593 }
1594 check_if_stake_activating(
1595 transient_stake_account_info,
1596 vote_account_address,
1597 clock.epoch,
1598 )?;
1599 }
1600
1601 check_validator_stake_account(
1602 validator_stake_account_info,
1603 program_id,
1604 stake_pool_info.key,
1605 withdraw_authority_info.key,
1606 vote_account_address,
1607 validator_stake_info.validator_seed_suffix.into(),
1608 &stake_pool.lockup,
1609 )?;
1610
1611 if validator_stake_info.status != StakeStatus::Active.into() {
1612 msg!("Validator is marked for removal and no longer allows increases");
1613 return Err(StakePoolError::ValidatorNotFound.into());
1614 }
1615
1616 let stake_space = std::mem::size_of::<stake::state::StakeStateV2>();
1617 let stake_rent = rent.minimum_balance(stake_space);
1618 let stake_minimum_delegation = stake::tools::get_minimum_delegation()?;
1619 let current_minimum_delegation = minimum_delegation(stake_minimum_delegation);
1620 if lamports < current_minimum_delegation {
1621 msg!(
1622 "Need more than {} lamports for transient stake to meet minimum delegation requirement, {} provided",
1623 current_minimum_delegation,
1624 lamports
1625 );
1626 return Err(ProgramError::Custom(
1627 stake::error::StakeError::InsufficientDelegation as u32,
1628 ));
1629 }
1630
1631 let total_lamports = lamports.saturating_add(stake_rent);
1635
1636 if reserve_stake_account_info
1637 .lamports()
1638 .saturating_sub(total_lamports)
1639 < stake_rent
1640 {
1641 let max_split_amount = reserve_stake_account_info
1642 .lamports()
1643 .saturating_sub(stake_rent.saturating_mul(2));
1644 msg!(
1645 "Reserve stake does not have enough lamports for increase, maximum amount {}, {} requested",
1646 max_split_amount,
1647 lamports
1648 );
1649 return Err(ProgramError::InsufficientFunds);
1650 }
1651
1652 let source_stake_account_info =
1653 if let Some((ephemeral_stake_seed, ephemeral_stake_account_info)) =
1654 maybe_ephemeral_stake_seed.zip(maybe_ephemeral_stake_account_info)
1655 {
1656 let ephemeral_stake_bump_seed = check_ephemeral_stake_address(
1657 program_id,
1658 stake_pool_info.key,
1659 ephemeral_stake_account_info.key,
1660 ephemeral_stake_seed,
1661 )?;
1662 let ephemeral_stake_account_signer_seeds: &[&[_]] = &[
1663 EPHEMERAL_STAKE_SEED_PREFIX,
1664 stake_pool_info.key.as_ref(),
1665 &ephemeral_stake_seed.to_le_bytes(),
1666 &[ephemeral_stake_bump_seed],
1667 ];
1668 create_stake_account(
1669 ephemeral_stake_account_info.clone(),
1670 ephemeral_stake_account_signer_seeds,
1671 stake_space,
1672 )?;
1673
1674 Self::stake_split(
1676 stake_pool_info.key,
1677 reserve_stake_account_info.clone(),
1678 withdraw_authority_info.clone(),
1679 AUTHORITY_WITHDRAW,
1680 stake_pool.stake_withdraw_bump_seed,
1681 total_lamports,
1682 ephemeral_stake_account_info.clone(),
1683 )?;
1684
1685 Self::stake_delegate(
1687 ephemeral_stake_account_info.clone(),
1688 validator_vote_account_info.clone(),
1689 clock_info.clone(),
1690 stake_history_info.clone(),
1691 stake_config_info.clone(),
1692 withdraw_authority_info.clone(),
1693 stake_pool_info.key,
1694 AUTHORITY_WITHDRAW,
1695 stake_pool.stake_withdraw_bump_seed,
1696 )?;
1697 ephemeral_stake_account_info
1698 } else {
1699 reserve_stake_account_info
1702 };
1703
1704 let transient_stake_bump_seed = check_transient_stake_address(
1705 program_id,
1706 stake_pool_info.key,
1707 transient_stake_account_info.key,
1708 vote_account_address,
1709 transient_stake_seed,
1710 )?;
1711
1712 if u64::from(validator_stake_info.transient_stake_lamports) > 0 {
1713 Self::stake_merge(
1716 stake_pool_info.key,
1717 source_stake_account_info.clone(),
1718 withdraw_authority_info.clone(),
1719 AUTHORITY_WITHDRAW,
1720 stake_pool.stake_withdraw_bump_seed,
1721 transient_stake_account_info.clone(),
1722 clock_info.clone(),
1723 stake_history_info.clone(),
1724 )?;
1725 } else {
1726 let transient_stake_account_signer_seeds: &[&[_]] = &[
1728 TRANSIENT_STAKE_SEED_PREFIX,
1729 vote_account_address.as_ref(),
1730 stake_pool_info.key.as_ref(),
1731 &transient_stake_seed.to_le_bytes(),
1732 &[transient_stake_bump_seed],
1733 ];
1734
1735 create_stake_account(
1736 transient_stake_account_info.clone(),
1737 transient_stake_account_signer_seeds,
1738 stake_space,
1739 )?;
1740
1741 Self::stake_split(
1743 stake_pool_info.key,
1744 source_stake_account_info.clone(),
1745 withdraw_authority_info.clone(),
1746 AUTHORITY_WITHDRAW,
1747 stake_pool.stake_withdraw_bump_seed,
1748 total_lamports,
1749 transient_stake_account_info.clone(),
1750 )?;
1751
1752 let stake_state = try_from_slice_unchecked::<stake::state::StakeStateV2>(
1754 &transient_stake_account_info.data.borrow(),
1755 )?;
1756 match stake_state {
1757 stake::state::StakeStateV2::Stake(_, stake, _)
1759 if stake.delegation.activation_epoch <= clock.epoch => {}
1760 _ => {
1762 Self::stake_delegate(
1763 transient_stake_account_info.clone(),
1764 validator_vote_account_info.clone(),
1765 clock_info.clone(),
1766 stake_history_info.clone(),
1767 stake_config_info.clone(),
1768 withdraw_authority_info.clone(),
1769 stake_pool_info.key,
1770 AUTHORITY_WITHDRAW,
1771 stake_pool.stake_withdraw_bump_seed,
1772 )?;
1773 }
1774 }
1775 }
1776
1777 validator_stake_info.transient_stake_lamports =
1778 u64::from(validator_stake_info.transient_stake_lamports)
1779 .checked_add(total_lamports)
1780 .ok_or(StakePoolError::CalculationFailure)?
1781 .into();
1782 validator_stake_info.transient_seed_suffix = transient_stake_seed.into();
1783
1784 Ok(())
1785 }
1786
1787 #[inline(never)] fn process_set_preferred_validator(
1790 program_id: &Pubkey,
1791 accounts: &[AccountInfo],
1792 validator_type: PreferredValidatorType,
1793 vote_account_address: Option<Pubkey>,
1794 ) -> ProgramResult {
1795 let account_info_iter = &mut accounts.iter();
1796 let stake_pool_info = next_account_info(account_info_iter)?;
1797 let staker_info = next_account_info(account_info_iter)?;
1798 let validator_list_info = next_account_info(account_info_iter)?;
1799
1800 check_account_owner(stake_pool_info, program_id)?;
1801 check_account_owner(validator_list_info, program_id)?;
1802
1803 let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
1804 if !stake_pool.is_valid() {
1805 msg!("Expected valid stake pool");
1806 return Err(StakePoolError::InvalidState.into());
1807 }
1808
1809 stake_pool.check_staker(staker_info)?;
1810 stake_pool.check_validator_list(validator_list_info)?;
1811
1812 let mut validator_list_data = validator_list_info.data.borrow_mut();
1813 let (header, validator_list) =
1814 ValidatorListHeader::deserialize_vec(&mut validator_list_data)?;
1815 if !header.is_valid() {
1816 return Err(StakePoolError::InvalidState.into());
1817 }
1818
1819 if let Some(vote_account_address) = vote_account_address {
1820 let maybe_validator_stake_info = validator_list.find::<ValidatorStakeInfo, _>(|x| {
1821 ValidatorStakeInfo::memcmp_pubkey(x, &vote_account_address)
1822 });
1823 match maybe_validator_stake_info {
1824 Some(vsi) => {
1825 if vsi.status != StakeStatus::Active.into() {
1826 msg!("Validator for {:?} about to be removed, cannot set as preferred deposit account", validator_type);
1827 return Err(StakePoolError::InvalidPreferredValidator.into());
1828 }
1829 }
1830 None => {
1831 msg!("Validator for {:?} not present in the stake pool, cannot set as preferred deposit account", validator_type);
1832 return Err(StakePoolError::ValidatorNotFound.into());
1833 }
1834 }
1835 }
1836
1837 match validator_type {
1838 PreferredValidatorType::Deposit => {
1839 stake_pool.preferred_deposit_validator_vote_address = vote_account_address
1840 }
1841 PreferredValidatorType::Withdraw => {
1842 stake_pool.preferred_withdraw_validator_vote_address = vote_account_address
1843 }
1844 };
1845 borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
1846 Ok(())
1847 }
1848
1849 #[inline(always)] fn process_update_validator_list_balance(
1852 program_id: &Pubkey,
1853 accounts: &[AccountInfo],
1854 start_index: u32,
1855 no_merge: bool,
1856 ) -> ProgramResult {
1857 let account_info_iter = &mut accounts.iter();
1858 let stake_pool_info = next_account_info(account_info_iter)?;
1859 let withdraw_authority_info = next_account_info(account_info_iter)?;
1860 let validator_list_info = next_account_info(account_info_iter)?;
1861 let reserve_stake_info = next_account_info(account_info_iter)?;
1862 let clock_info = next_account_info(account_info_iter)?;
1863 let clock = &Clock::from_account_info(clock_info)?;
1864 let stake_history_info = next_account_info(account_info_iter)?;
1865 let stake_program_info = next_account_info(account_info_iter)?;
1866 let validator_stake_accounts = account_info_iter.as_slice();
1867
1868 let rent = Rent::get()?;
1869
1870 check_account_owner(stake_pool_info, program_id)?;
1871 let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
1872 if !stake_pool.is_valid() {
1873 return Err(StakePoolError::InvalidState.into());
1874 }
1875 stake_pool.check_validator_list(validator_list_info)?;
1876 stake_pool.check_authority_withdraw(
1877 withdraw_authority_info.key,
1878 program_id,
1879 stake_pool_info.key,
1880 )?;
1881 stake_pool.check_reserve_stake(reserve_stake_info)?;
1882 check_stake_program(stake_program_info.key)?;
1883
1884 let epoch_rewards = EpochRewards::get()?;
1886 if epoch_rewards.active {
1887 return Err(StakePoolError::EpochRewardDistributionInProgress.into());
1888 }
1889
1890 if validator_stake_accounts
1891 .len()
1892 .checked_rem(2)
1893 .ok_or(StakePoolError::CalculationFailure)?
1894 != 0
1895 {
1896 msg!("Odd number of validator stake accounts passed in, should be pairs of validator stake and transient stake accounts");
1897 return Err(StakePoolError::UnexpectedValidatorListAccountSize.into());
1898 }
1899
1900 check_account_owner(validator_list_info, program_id)?;
1901 let mut validator_list_data = validator_list_info.data.borrow_mut();
1902 let (validator_list_header, mut big_vec) =
1903 ValidatorListHeader::deserialize_vec(&mut validator_list_data)?;
1904 let validator_slice = ValidatorListHeader::deserialize_mut_slice(
1905 &mut big_vec,
1906 start_index as usize,
1907 validator_stake_accounts.len() / 2,
1908 )?;
1909
1910 if !validator_list_header.is_valid() {
1911 return Err(StakePoolError::InvalidState.into());
1912 }
1913
1914 let validator_iter = &mut validator_slice
1915 .iter_mut()
1916 .zip(validator_stake_accounts.chunks_exact(2));
1917 for (validator_stake_record, validator_stakes) in validator_iter {
1918 let validator_stake_info = validator_stakes
1920 .first()
1921 .ok_or(ProgramError::InvalidInstructionData)?;
1922 let transient_stake_info = validator_stakes
1923 .last()
1924 .ok_or(ProgramError::InvalidInstructionData)?;
1925 if check_validator_stake_address(
1926 program_id,
1927 stake_pool_info.key,
1928 validator_stake_info.key,
1929 &validator_stake_record.vote_account_address,
1930 NonZeroU32::new(validator_stake_record.validator_seed_suffix.into()),
1931 )
1932 .is_err()
1933 {
1934 continue;
1935 };
1936 if check_transient_stake_address(
1937 program_id,
1938 stake_pool_info.key,
1939 transient_stake_info.key,
1940 &validator_stake_record.vote_account_address,
1941 validator_stake_record.transient_seed_suffix.into(),
1942 )
1943 .is_err()
1944 {
1945 continue;
1946 };
1947
1948 let validator_stake_rent = rent.minimum_balance(validator_stake_info.data_len());
1949
1950 let mut active_stake_lamports = 0;
1951 let mut transient_stake_lamports = 0;
1952 let validator_stake_state = try_from_slice_unchecked::<stake::state::StakeStateV2>(
1953 &validator_stake_info.data.borrow(),
1954 )
1955 .ok();
1956 let transient_stake_state = try_from_slice_unchecked::<stake::state::StakeStateV2>(
1957 &transient_stake_info.data.borrow(),
1958 )
1959 .ok();
1960
1961 if validator_stake_record.transient_stake_lamports != 0.into() {
1968 match transient_stake_state {
1969 Some(stake::state::StakeStateV2::Initialized(meta)) => {
1970 if stake_is_usable_by_pool(
1971 &meta,
1972 withdraw_authority_info.key,
1973 &stake_pool.lockup,
1974 ) {
1975 if no_merge {
1976 transient_stake_lamports = transient_stake_info.lamports();
1977 } else {
1978 Self::stake_merge(
1980 stake_pool_info.key,
1981 transient_stake_info.clone(),
1982 withdraw_authority_info.clone(),
1983 AUTHORITY_WITHDRAW,
1984 stake_pool.stake_withdraw_bump_seed,
1985 reserve_stake_info.clone(),
1986 clock_info.clone(),
1987 stake_history_info.clone(),
1988 )?;
1989 validator_stake_record.status.remove_transient_stake()?;
1990 }
1991 }
1992 }
1993 Some(stake::state::StakeStateV2::Stake(meta, stake, _)) => {
1994 if stake_is_usable_by_pool(
1995 &meta,
1996 withdraw_authority_info.key,
1997 &stake_pool.lockup,
1998 ) {
1999 if !no_merge {
2000 if stake_is_inactive_without_history(&stake, clock.epoch) {
2001 Self::stake_merge(
2003 stake_pool_info.key,
2004 transient_stake_info.clone(),
2005 withdraw_authority_info.clone(),
2006 AUTHORITY_WITHDRAW,
2007 stake_pool.stake_withdraw_bump_seed,
2008 reserve_stake_info.clone(),
2009 clock_info.clone(),
2010 stake_history_info.clone(),
2011 )?;
2012 validator_stake_record.status.remove_transient_stake()?;
2013 } else if validator_stake_record.status.try_into()
2014 == Ok(StakeStatus::Active)
2015 {
2016 if stake.delegation.activation_epoch < clock.epoch {
2017 if let Some(stake::state::StakeStateV2::Stake(
2018 _,
2019 validator_stake,
2020 _,
2021 )) = validator_stake_state
2022 {
2023 if validator_stake.delegation.activation_epoch
2024 < clock.epoch
2025 {
2026 Self::stake_merge(
2027 stake_pool_info.key,
2028 transient_stake_info.clone(),
2029 withdraw_authority_info.clone(),
2030 AUTHORITY_WITHDRAW,
2031 stake_pool.stake_withdraw_bump_seed,
2032 validator_stake_info.clone(),
2033 clock_info.clone(),
2034 stake_history_info.clone(),
2035 )?;
2036 } else {
2037 msg!("Stake activating or just active, not ready to merge");
2038 }
2039 } else {
2040 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);
2041 }
2042 } else {
2043 msg!("Transient stake not ready to be merged anywhere");
2044 }
2045 } else if stake.delegation.deactivation_epoch == Epoch::MAX {
2046 msg!("Transient stake is activating or active, deactivating.");
2047 Self::stake_deactivate(
2048 transient_stake_info.clone(),
2049 clock_info.clone(),
2050 withdraw_authority_info.clone(),
2051 stake_pool_info.key,
2052 AUTHORITY_WITHDRAW,
2053 stake_pool.stake_withdraw_bump_seed,
2054 )?;
2055 }
2056 }
2057 transient_stake_lamports = transient_stake_info.lamports();
2058 }
2059 }
2060 None
2061 | Some(stake::state::StakeStateV2::Uninitialized)
2062 | Some(stake::state::StakeStateV2::RewardsPool) => {} }
2064 }
2065 let validator_stake_state = try_from_slice_unchecked::<stake::state::StakeStateV2>(
2070 &validator_stake_info.data.borrow(),
2071 )
2072 .ok();
2073 match validator_stake_state {
2074 Some(stake::state::StakeStateV2::Stake(meta, stake, _))
2075 if stake_is_usable_by_pool(
2076 &meta,
2077 withdraw_authority_info.key,
2078 &stake_pool.lockup,
2079 ) =>
2080 {
2081 let additional_lamports = validator_stake_info
2082 .lamports()
2083 .saturating_sub(stake.delegation.stake)
2084 .saturating_sub(validator_stake_rent);
2085 if additional_lamports > 0 {
2087 Self::stake_withdraw(
2088 stake_pool_info.key,
2089 validator_stake_info.clone(),
2090 withdraw_authority_info.clone(),
2091 AUTHORITY_WITHDRAW,
2092 stake_pool.stake_withdraw_bump_seed,
2093 reserve_stake_info.clone(),
2094 clock_info.clone(),
2095 stake_history_info.clone(),
2096 additional_lamports,
2097 )?;
2098 }
2099 match validator_stake_record.status.try_into()? {
2100 StakeStatus::Active => {
2101 active_stake_lamports = validator_stake_info.lamports();
2102 }
2103 StakeStatus::DeactivatingValidator | StakeStatus::DeactivatingAll => {
2104 if no_merge {
2105 active_stake_lamports = validator_stake_info.lamports();
2106 } else if stake_is_inactive_without_history(&stake, clock.epoch) {
2107 Self::stake_merge(
2110 stake_pool_info.key,
2111 validator_stake_info.clone(),
2112 withdraw_authority_info.clone(),
2113 AUTHORITY_WITHDRAW,
2114 stake_pool.stake_withdraw_bump_seed,
2115 reserve_stake_info.clone(),
2116 clock_info.clone(),
2117 stake_history_info.clone(),
2118 )?;
2119 validator_stake_record.status.remove_validator_stake()?;
2120 } else {
2121 active_stake_lamports = validator_stake_info.lamports();
2122 }
2123 }
2124 StakeStatus::DeactivatingTransient | StakeStatus::ReadyForRemoval => {
2125 msg!("Validator stake account no longer part of the pool, ignoring");
2126 }
2127 }
2128 }
2129 Some(stake::state::StakeStateV2::Initialized(meta))
2130 if stake_is_usable_by_pool(
2131 &meta,
2132 withdraw_authority_info.key,
2133 &stake_pool.lockup,
2134 ) =>
2135 {
2136 Self::stake_merge(
2141 stake_pool_info.key,
2142 validator_stake_info.clone(),
2143 withdraw_authority_info.clone(),
2144 AUTHORITY_WITHDRAW,
2145 stake_pool.stake_withdraw_bump_seed,
2146 reserve_stake_info.clone(),
2147 clock_info.clone(),
2148 stake_history_info.clone(),
2149 )?;
2150 if transient_stake_lamports != 0 {
2151 validator_stake_record.status = StakeStatus::DeactivatingTransient.into();
2152 } else {
2153 validator_stake_record.status = StakeStatus::ReadyForRemoval.into();
2154 }
2155 }
2156 Some(stake::state::StakeStateV2::Stake(_, _, _))
2157 | Some(stake::state::StakeStateV2::Initialized(_))
2158 | Some(stake::state::StakeStateV2::Uninitialized)
2159 | Some(stake::state::StakeStateV2::RewardsPool)
2160 | None => {
2161 msg!("Validator stake account no longer part of the pool, ignoring");
2162 }
2163 }
2164
2165 validator_stake_record.last_update_epoch = clock.epoch.into();
2166 validator_stake_record.active_stake_lamports = active_stake_lamports.into();
2167 validator_stake_record.transient_stake_lamports = transient_stake_lamports.into();
2168 }
2169
2170 Ok(())
2171 }
2172
2173 #[inline(always)] fn process_update_stake_pool_balance(
2176 program_id: &Pubkey,
2177 accounts: &[AccountInfo],
2178 ) -> ProgramResult {
2179 let account_info_iter = &mut accounts.iter();
2180 let stake_pool_info = next_account_info(account_info_iter)?;
2181 let withdraw_info = next_account_info(account_info_iter)?;
2182 let validator_list_info = next_account_info(account_info_iter)?;
2183 let reserve_stake_info = next_account_info(account_info_iter)?;
2184 let manager_fee_info = next_account_info(account_info_iter)?;
2185 let pool_mint_info = next_account_info(account_info_iter)?;
2186 let token_program_info = next_account_info(account_info_iter)?;
2187 let clock = Clock::get()?;
2188 let rent = Rent::get()?;
2189
2190 check_account_owner(stake_pool_info, program_id)?;
2191 let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
2192 if !stake_pool.is_valid() {
2193 return Err(StakePoolError::InvalidState.into());
2194 }
2195 stake_pool.check_mint(pool_mint_info)?;
2196 stake_pool.check_authority_withdraw(withdraw_info.key, program_id, stake_pool_info.key)?;
2197 stake_pool.check_reserve_stake(reserve_stake_info)?;
2198 if stake_pool.manager_fee_account != *manager_fee_info.key {
2199 return Err(StakePoolError::InvalidFeeAccount.into());
2200 }
2201
2202 if *validator_list_info.key != stake_pool.validator_list {
2203 return Err(StakePoolError::InvalidValidatorStakeList.into());
2204 }
2205 if stake_pool.token_program_id != *token_program_info.key {
2206 return Err(ProgramError::IncorrectProgramId);
2207 }
2208
2209 check_account_owner(validator_list_info, program_id)?;
2210 let mut validator_list_data = validator_list_info.data.borrow_mut();
2211 let (header, validator_list) =
2212 ValidatorListHeader::deserialize_vec(&mut validator_list_data)?;
2213 if !header.is_valid() {
2214 return Err(StakePoolError::InvalidState.into());
2215 }
2216
2217 let previous_lamports = stake_pool.total_lamports;
2218 let previous_pool_token_supply = stake_pool.pool_token_supply;
2219 let reserve_rent = rent.minimum_balance(reserve_stake_info.data_len());
2220
2221 let mut total_lamports = reserve_stake_info
2224 .lamports()
2225 .saturating_sub(minimum_reserve_lamports(reserve_rent));
2226
2227 for validator_stake_record in validator_list
2228 .deserialize_slice::<ValidatorStakeInfo>(0, validator_list.len() as usize)?
2229 {
2230 if u64::from(validator_stake_record.last_update_epoch) < clock.epoch {
2231 return Err(StakePoolError::StakeListOutOfDate.into());
2232 }
2233 total_lamports = total_lamports
2234 .checked_add(validator_stake_record.stake_lamports()?)
2235 .ok_or(StakePoolError::CalculationFailure)?;
2236 }
2237
2238 let reward_lamports = total_lamports.saturating_sub(previous_lamports);
2239
2240 let fee = if stake_pool.check_manager_fee_info(manager_fee_info).is_ok() {
2242 stake_pool
2243 .calc_epoch_fee_amount(reward_lamports)
2244 .ok_or(StakePoolError::CalculationFailure)?
2245 } else {
2246 0
2247 };
2248
2249 if fee > 0 {
2250 Self::token_mint_to(
2251 stake_pool_info.key,
2252 token_program_info.clone(),
2253 pool_mint_info.clone(),
2254 manager_fee_info.clone(),
2255 withdraw_info.clone(),
2256 AUTHORITY_WITHDRAW,
2257 stake_pool.stake_withdraw_bump_seed,
2258 fee,
2259 )?;
2260 }
2261
2262 if stake_pool.last_update_epoch < clock.epoch {
2263 if let Some(fee) = stake_pool.next_epoch_fee.get() {
2264 stake_pool.epoch_fee = *fee;
2265 }
2266 stake_pool.next_epoch_fee.update_epoch();
2267
2268 if let Some(fee) = stake_pool.next_stake_withdrawal_fee.get() {
2269 stake_pool.stake_withdrawal_fee = *fee;
2270 }
2271 stake_pool.next_stake_withdrawal_fee.update_epoch();
2272
2273 if let Some(fee) = stake_pool.next_sol_withdrawal_fee.get() {
2274 stake_pool.sol_withdrawal_fee = *fee;
2275 }
2276 stake_pool.next_sol_withdrawal_fee.update_epoch();
2277
2278 stake_pool.last_update_epoch = clock.epoch;
2279 stake_pool.last_epoch_total_lamports = previous_lamports;
2280 stake_pool.last_epoch_pool_token_supply = previous_pool_token_supply;
2281 }
2282 stake_pool.total_lamports = total_lamports;
2283
2284 let pool_mint_data = pool_mint_info.try_borrow_data()?;
2285 let pool_mint = StateWithExtensions::<Mint>::unpack(&pool_mint_data)?;
2286 stake_pool.pool_token_supply = pool_mint.base.supply;
2287
2288 borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
2289
2290 Ok(())
2291 }
2292
2293 #[inline(never)] fn process_cleanup_removed_validator_entries(
2296 program_id: &Pubkey,
2297 accounts: &[AccountInfo],
2298 ) -> ProgramResult {
2299 let account_info_iter = &mut accounts.iter();
2300 let stake_pool_info = next_account_info(account_info_iter)?;
2301 let validator_list_info = next_account_info(account_info_iter)?;
2302
2303 check_account_owner(stake_pool_info, program_id)?;
2304 let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
2305 if !stake_pool.is_valid() {
2306 return Err(StakePoolError::InvalidState.into());
2307 }
2308 stake_pool.check_validator_list(validator_list_info)?;
2309
2310 check_account_owner(validator_list_info, program_id)?;
2311 let mut validator_list_data = validator_list_info.data.borrow_mut();
2312 let (header, mut validator_list) =
2313 ValidatorListHeader::deserialize_vec(&mut validator_list_data)?;
2314 if !header.is_valid() {
2315 return Err(StakePoolError::InvalidState.into());
2316 }
2317
2318 validator_list.retain::<ValidatorStakeInfo, _>(|x| !ValidatorStakeInfo::is_removed(x))?;
2319
2320 if stake_pool_info.is_writable {
2321 msg!("Checking preferred validators");
2322 let mut stake_pool =
2323 try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
2324
2325 if let Some(preferred_deposit) = stake_pool.preferred_deposit_validator_vote_address {
2328 let maybe_validator = validator_list.find::<ValidatorStakeInfo, _>(|x| {
2329 ValidatorStakeInfo::memcmp_pubkey(x, &preferred_deposit)
2330 });
2331
2332 let should_reset = match maybe_validator {
2333 Some(validator) => {
2334 match validator.status.try_into() {
2336 Ok(StakeStatus::Active) => false, _ => true, }
2339 }
2340 None => true, };
2342
2343 if should_reset {
2344 msg!(
2345 "Preferred deposit validator {} not found or not active, resetting",
2346 preferred_deposit
2347 );
2348 stake_pool.preferred_deposit_validator_vote_address = None;
2349 }
2350 }
2351
2352 if let Some(preferred_withdraw) = stake_pool.preferred_withdraw_validator_vote_address {
2354 let maybe_validator = validator_list.find::<ValidatorStakeInfo, _>(|x| {
2355 ValidatorStakeInfo::memcmp_pubkey(x, &preferred_withdraw)
2356 });
2357
2358 let should_reset = match maybe_validator {
2359 Some(validator) => {
2360 match validator.status.try_into() {
2362 Ok(StakeStatus::Active) => false, _ => true, }
2365 }
2366 None => true, };
2368
2369 if should_reset {
2370 msg!(
2371 "Preferred withdrawal validator {} not found or not active, resetting",
2372 preferred_withdraw
2373 );
2374 stake_pool.preferred_withdraw_validator_vote_address = None;
2375 }
2376 }
2377
2378 borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
2380 }
2381
2382 Ok(())
2383 }
2384
2385 #[inline(never)] fn process_deposit_stake(
2388 program_id: &Pubkey,
2389 accounts: &[AccountInfo],
2390 minimum_pool_tokens_out: Option<u64>,
2391 ) -> ProgramResult {
2392 let account_info_iter = &mut accounts.iter();
2393 let stake_pool_info = next_account_info(account_info_iter)?;
2394 let validator_list_info = next_account_info(account_info_iter)?;
2395 let stake_deposit_authority_info = next_account_info(account_info_iter)?;
2396 let withdraw_authority_info = next_account_info(account_info_iter)?;
2397 let stake_info = next_account_info(account_info_iter)?;
2398 let validator_stake_account_info = next_account_info(account_info_iter)?;
2399 let reserve_stake_account_info = next_account_info(account_info_iter)?;
2400 let dest_user_pool_info = next_account_info(account_info_iter)?;
2401 let manager_fee_info = next_account_info(account_info_iter)?;
2402 let referrer_fee_info = next_account_info(account_info_iter)?;
2403 let pool_mint_info = next_account_info(account_info_iter)?;
2404 let clock_info = next_account_info(account_info_iter)?;
2405 let clock = &Clock::from_account_info(clock_info)?;
2406 let stake_history_info = next_account_info(account_info_iter)?;
2407 let token_program_info = next_account_info(account_info_iter)?;
2408 let stake_program_info = next_account_info(account_info_iter)?;
2409
2410 check_stake_program(stake_program_info.key)?;
2411
2412 check_account_owner(stake_pool_info, program_id)?;
2413 let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
2414 if !stake_pool.is_valid() {
2415 return Err(StakePoolError::InvalidState.into());
2416 }
2417
2418 stake_pool.check_authority_withdraw(
2419 withdraw_authority_info.key,
2420 program_id,
2421 stake_pool_info.key,
2422 )?;
2423 stake_pool.check_stake_deposit_authority(stake_deposit_authority_info.key)?;
2424 stake_pool.check_mint(pool_mint_info)?;
2425 stake_pool.check_validator_list(validator_list_info)?;
2426 stake_pool.check_reserve_stake(reserve_stake_account_info)?;
2427
2428 if stake_pool.token_program_id != *token_program_info.key {
2429 return Err(ProgramError::IncorrectProgramId);
2430 }
2431
2432 if stake_pool.manager_fee_account != *manager_fee_info.key {
2433 return Err(StakePoolError::InvalidFeeAccount.into());
2434 }
2435 if stake_pool.last_update_epoch < clock.epoch {
2440 return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
2441 }
2442
2443 check_account_owner(validator_list_info, program_id)?;
2444 let mut validator_list_data = validator_list_info.data.borrow_mut();
2445 let (header, mut validator_list) =
2446 ValidatorListHeader::deserialize_vec(&mut validator_list_data)?;
2447 if !header.is_valid() {
2448 return Err(StakePoolError::InvalidState.into());
2449 }
2450
2451 let (_, validator_stake) = get_stake_state(validator_stake_account_info)?;
2452 let pre_all_validator_lamports = validator_stake_account_info.lamports();
2453 let vote_account_address = validator_stake.delegation.voter_pubkey;
2454 if let Some(preferred_deposit) = stake_pool.preferred_deposit_validator_vote_address {
2455 if preferred_deposit != vote_account_address {
2456 msg!(
2457 "Incorrect deposit address, expected {}, received {}",
2458 preferred_deposit,
2459 vote_account_address
2460 );
2461 return Err(StakePoolError::IncorrectDepositVoteAddress.into());
2462 }
2463 }
2464
2465 let validator_stake_info = validator_list
2466 .find_mut::<ValidatorStakeInfo, _>(|x| {
2467 ValidatorStakeInfo::memcmp_pubkey(x, &vote_account_address)
2468 })
2469 .ok_or(StakePoolError::ValidatorNotFound)?;
2470 check_validator_stake_address(
2471 program_id,
2472 stake_pool_info.key,
2473 validator_stake_account_info.key,
2474 &vote_account_address,
2475 NonZeroU32::new(validator_stake_info.validator_seed_suffix.into()),
2476 )?;
2477
2478 if validator_stake_info.status != StakeStatus::Active.into() {
2479 msg!("Validator is marked for removal and no longer accepting deposits");
2480 return Err(StakePoolError::ValidatorNotFound.into());
2481 }
2482
2483 msg!("Stake pre merge {}", validator_stake.delegation.stake);
2484
2485 let (stake_deposit_authority_program_address, deposit_bump_seed) =
2486 find_deposit_authority_program_address(program_id, stake_pool_info.key);
2487 if *stake_deposit_authority_info.key == stake_deposit_authority_program_address {
2488 Self::stake_authorize_signed(
2489 stake_pool_info.key,
2490 stake_info.clone(),
2491 stake_deposit_authority_info.clone(),
2492 AUTHORITY_DEPOSIT,
2493 deposit_bump_seed,
2494 withdraw_authority_info.key,
2495 clock_info.clone(),
2496 )?;
2497 } else {
2498 Self::stake_authorize(
2499 stake_info.clone(),
2500 stake_deposit_authority_info.clone(),
2501 withdraw_authority_info.key,
2502 clock_info.clone(),
2503 )?;
2504 }
2505
2506 Self::stake_merge(
2507 stake_pool_info.key,
2508 stake_info.clone(),
2509 withdraw_authority_info.clone(),
2510 AUTHORITY_WITHDRAW,
2511 stake_pool.stake_withdraw_bump_seed,
2512 validator_stake_account_info.clone(),
2513 clock_info.clone(),
2514 stake_history_info.clone(),
2515 )?;
2516
2517 let (_, post_validator_stake) = get_stake_state(validator_stake_account_info)?;
2518 let post_all_validator_lamports = validator_stake_account_info.lamports();
2519 msg!("Stake post merge {}", post_validator_stake.delegation.stake);
2520
2521 let total_deposit_lamports = post_all_validator_lamports
2522 .checked_sub(pre_all_validator_lamports)
2523 .ok_or(StakePoolError::CalculationFailure)?;
2524 let stake_deposit_lamports = post_validator_stake
2525 .delegation
2526 .stake
2527 .checked_sub(validator_stake.delegation.stake)
2528 .ok_or(StakePoolError::CalculationFailure)?;
2529 let sol_deposit_lamports = total_deposit_lamports
2530 .checked_sub(stake_deposit_lamports)
2531 .ok_or(StakePoolError::CalculationFailure)?;
2532
2533 let new_pool_tokens = stake_pool
2534 .calc_pool_tokens_for_deposit(total_deposit_lamports)
2535 .ok_or(StakePoolError::CalculationFailure)?;
2536 let new_pool_tokens_from_stake = stake_pool
2537 .calc_pool_tokens_for_deposit(stake_deposit_lamports)
2538 .ok_or(StakePoolError::CalculationFailure)?;
2539 let new_pool_tokens_from_sol = new_pool_tokens
2540 .checked_sub(new_pool_tokens_from_stake)
2541 .ok_or(StakePoolError::CalculationFailure)?;
2542
2543 let stake_deposit_fee = stake_pool
2544 .calc_pool_tokens_stake_deposit_fee(new_pool_tokens_from_stake)
2545 .ok_or(StakePoolError::CalculationFailure)?;
2546 let sol_deposit_fee = stake_pool
2547 .calc_pool_tokens_sol_deposit_fee(new_pool_tokens_from_sol)
2548 .ok_or(StakePoolError::CalculationFailure)?;
2549
2550 let total_fee = stake_deposit_fee
2551 .checked_add(sol_deposit_fee)
2552 .ok_or(StakePoolError::CalculationFailure)?;
2553 let pool_tokens_user = new_pool_tokens
2554 .checked_sub(total_fee)
2555 .ok_or(StakePoolError::CalculationFailure)?;
2556
2557 let pool_tokens_referral_fee = stake_pool
2558 .calc_pool_tokens_stake_referral_fee(total_fee)
2559 .ok_or(StakePoolError::CalculationFailure)?;
2560
2561 let pool_tokens_manager_deposit_fee = total_fee
2562 .checked_sub(pool_tokens_referral_fee)
2563 .ok_or(StakePoolError::CalculationFailure)?;
2564
2565 if pool_tokens_user
2566 .saturating_add(pool_tokens_manager_deposit_fee)
2567 .saturating_add(pool_tokens_referral_fee)
2568 != new_pool_tokens
2569 {
2570 return Err(StakePoolError::CalculationFailure.into());
2571 }
2572
2573 if pool_tokens_user == 0 {
2574 return Err(StakePoolError::DepositTooSmall.into());
2575 }
2576
2577 if let Some(minimum_pool_tokens_out) = minimum_pool_tokens_out {
2578 if pool_tokens_user < minimum_pool_tokens_out {
2579 return Err(StakePoolError::ExceededSlippage.into());
2580 }
2581 }
2582
2583 Self::token_mint_to(
2584 stake_pool_info.key,
2585 token_program_info.clone(),
2586 pool_mint_info.clone(),
2587 dest_user_pool_info.clone(),
2588 withdraw_authority_info.clone(),
2589 AUTHORITY_WITHDRAW,
2590 stake_pool.stake_withdraw_bump_seed,
2591 pool_tokens_user,
2592 )?;
2593 if pool_tokens_manager_deposit_fee > 0 {
2594 Self::token_mint_to(
2595 stake_pool_info.key,
2596 token_program_info.clone(),
2597 pool_mint_info.clone(),
2598 manager_fee_info.clone(),
2599 withdraw_authority_info.clone(),
2600 AUTHORITY_WITHDRAW,
2601 stake_pool.stake_withdraw_bump_seed,
2602 pool_tokens_manager_deposit_fee,
2603 )?;
2604 }
2605 if pool_tokens_referral_fee > 0 {
2606 Self::token_mint_to(
2607 stake_pool_info.key,
2608 token_program_info.clone(),
2609 pool_mint_info.clone(),
2610 referrer_fee_info.clone(),
2611 withdraw_authority_info.clone(),
2612 AUTHORITY_WITHDRAW,
2613 stake_pool.stake_withdraw_bump_seed,
2614 pool_tokens_referral_fee,
2615 )?;
2616 }
2617
2618 if sol_deposit_lamports > 0 {
2620 Self::stake_withdraw(
2621 stake_pool_info.key,
2622 validator_stake_account_info.clone(),
2623 withdraw_authority_info.clone(),
2624 AUTHORITY_WITHDRAW,
2625 stake_pool.stake_withdraw_bump_seed,
2626 reserve_stake_account_info.clone(),
2627 clock_info.clone(),
2628 stake_history_info.clone(),
2629 sol_deposit_lamports,
2630 )?;
2631 }
2632
2633 stake_pool.pool_token_supply = stake_pool
2634 .pool_token_supply
2635 .checked_add(new_pool_tokens)
2636 .ok_or(StakePoolError::CalculationFailure)?;
2637 stake_pool.total_lamports = stake_pool
2640 .total_lamports
2641 .checked_add(total_deposit_lamports)
2642 .ok_or(StakePoolError::CalculationFailure)?;
2643 borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
2644
2645 validator_stake_info.active_stake_lamports = validator_stake_account_info.lamports().into();
2646
2647 Ok(())
2648 }
2649
2650 #[inline(never)] fn process_deposit_sol(
2653 program_id: &Pubkey,
2654 accounts: &[AccountInfo],
2655 deposit_lamports: u64,
2656 minimum_pool_tokens_out: Option<u64>,
2657 ) -> ProgramResult {
2658 let account_info_iter = &mut accounts.iter();
2659 let stake_pool_info = next_account_info(account_info_iter)?;
2660 let withdraw_authority_info = next_account_info(account_info_iter)?;
2661 let reserve_stake_account_info = next_account_info(account_info_iter)?;
2662 let from_user_lamports_info = next_account_info(account_info_iter)?;
2663 let dest_user_pool_info = next_account_info(account_info_iter)?;
2664 let manager_fee_info = next_account_info(account_info_iter)?;
2665 let referrer_fee_info = next_account_info(account_info_iter)?;
2666 let pool_mint_info = next_account_info(account_info_iter)?;
2667 let system_program_info = next_account_info(account_info_iter)?;
2668 let token_program_info = next_account_info(account_info_iter)?;
2669 let sol_deposit_authority_info = next_account_info(account_info_iter);
2670
2671 let clock = Clock::get()?;
2672
2673 check_account_owner(stake_pool_info, program_id)?;
2674 let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
2675 if !stake_pool.is_valid() {
2676 return Err(StakePoolError::InvalidState.into());
2677 }
2678
2679 stake_pool.check_authority_withdraw(
2680 withdraw_authority_info.key,
2681 program_id,
2682 stake_pool_info.key,
2683 )?;
2684 stake_pool.check_sol_deposit_authority(sol_deposit_authority_info)?;
2685 stake_pool.check_mint(pool_mint_info)?;
2686 stake_pool.check_reserve_stake(reserve_stake_account_info)?;
2687
2688 if stake_pool.token_program_id != *token_program_info.key {
2689 return Err(ProgramError::IncorrectProgramId);
2690 }
2691 check_system_program(system_program_info.key)?;
2692
2693 if stake_pool.manager_fee_account != *manager_fee_info.key {
2694 return Err(StakePoolError::InvalidFeeAccount.into());
2695 }
2696 if stake_pool.last_update_epoch < clock.epoch {
2703 return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
2704 }
2705
2706 let new_pool_tokens = stake_pool
2707 .calc_pool_tokens_for_deposit(deposit_lamports)
2708 .ok_or(StakePoolError::CalculationFailure)?;
2709
2710 let pool_tokens_sol_deposit_fee = stake_pool
2711 .calc_pool_tokens_sol_deposit_fee(new_pool_tokens)
2712 .ok_or(StakePoolError::CalculationFailure)?;
2713 let pool_tokens_user = new_pool_tokens
2714 .checked_sub(pool_tokens_sol_deposit_fee)
2715 .ok_or(StakePoolError::CalculationFailure)?;
2716
2717 let pool_tokens_referral_fee = stake_pool
2718 .calc_pool_tokens_sol_referral_fee(pool_tokens_sol_deposit_fee)
2719 .ok_or(StakePoolError::CalculationFailure)?;
2720 let pool_tokens_manager_deposit_fee = pool_tokens_sol_deposit_fee
2721 .checked_sub(pool_tokens_referral_fee)
2722 .ok_or(StakePoolError::CalculationFailure)?;
2723
2724 if pool_tokens_user
2725 .saturating_add(pool_tokens_manager_deposit_fee)
2726 .saturating_add(pool_tokens_referral_fee)
2727 != new_pool_tokens
2728 {
2729 return Err(StakePoolError::CalculationFailure.into());
2730 }
2731
2732 if pool_tokens_user == 0 {
2733 return Err(StakePoolError::DepositTooSmall.into());
2734 }
2735
2736 if let Some(minimum_pool_tokens_out) = minimum_pool_tokens_out {
2737 if pool_tokens_user < minimum_pool_tokens_out {
2738 return Err(StakePoolError::ExceededSlippage.into());
2739 }
2740 }
2741
2742 Self::sol_transfer(
2743 from_user_lamports_info.clone(),
2744 reserve_stake_account_info.clone(),
2745 deposit_lamports,
2746 )?;
2747
2748 Self::token_mint_to(
2749 stake_pool_info.key,
2750 token_program_info.clone(),
2751 pool_mint_info.clone(),
2752 dest_user_pool_info.clone(),
2753 withdraw_authority_info.clone(),
2754 AUTHORITY_WITHDRAW,
2755 stake_pool.stake_withdraw_bump_seed,
2756 pool_tokens_user,
2757 )?;
2758
2759 if pool_tokens_manager_deposit_fee > 0 {
2760 Self::token_mint_to(
2761 stake_pool_info.key,
2762 token_program_info.clone(),
2763 pool_mint_info.clone(),
2764 manager_fee_info.clone(),
2765 withdraw_authority_info.clone(),
2766 AUTHORITY_WITHDRAW,
2767 stake_pool.stake_withdraw_bump_seed,
2768 pool_tokens_manager_deposit_fee,
2769 )?;
2770 }
2771
2772 if pool_tokens_referral_fee > 0 {
2773 Self::token_mint_to(
2774 stake_pool_info.key,
2775 token_program_info.clone(),
2776 pool_mint_info.clone(),
2777 referrer_fee_info.clone(),
2778 withdraw_authority_info.clone(),
2779 AUTHORITY_WITHDRAW,
2780 stake_pool.stake_withdraw_bump_seed,
2781 pool_tokens_referral_fee,
2782 )?;
2783 }
2784
2785 stake_pool.pool_token_supply = stake_pool
2786 .pool_token_supply
2787 .checked_add(new_pool_tokens)
2788 .ok_or(StakePoolError::CalculationFailure)?;
2789 stake_pool.total_lamports = stake_pool
2790 .total_lamports
2791 .checked_add(deposit_lamports)
2792 .ok_or(StakePoolError::CalculationFailure)?;
2793 borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
2794
2795 Ok(())
2796 }
2797
2798 #[inline(never)] fn process_withdraw_stake(
2801 program_id: &Pubkey,
2802 accounts: &[AccountInfo],
2803 pool_tokens: u64,
2804 minimum_lamports_out: Option<u64>,
2805 ) -> ProgramResult {
2806 let account_info_iter = &mut accounts.iter();
2807 let stake_pool_info = next_account_info(account_info_iter)?;
2808 let validator_list_info = next_account_info(account_info_iter)?;
2809 let withdraw_authority_info = next_account_info(account_info_iter)?;
2810 let stake_split_from = next_account_info(account_info_iter)?;
2811 let stake_split_to = next_account_info(account_info_iter)?;
2812 let user_stake_authority_info = next_account_info(account_info_iter)?;
2813 let user_transfer_authority_info = next_account_info(account_info_iter)?;
2814 let burn_from_pool_info = next_account_info(account_info_iter)?;
2815 let manager_fee_info = next_account_info(account_info_iter)?;
2816 let pool_mint_info = next_account_info(account_info_iter)?;
2817 let clock_info = next_account_info(account_info_iter)?;
2818 let clock = &Clock::from_account_info(clock_info)?;
2819 let token_program_info = next_account_info(account_info_iter)?;
2820 let stake_program_info = next_account_info(account_info_iter)?;
2821
2822 let rent = Rent::get()?;
2823
2824 check_stake_program(stake_program_info.key)?;
2825 check_account_owner(stake_pool_info, program_id)?;
2826 let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
2827 if !stake_pool.is_valid() {
2828 return Err(StakePoolError::InvalidState.into());
2829 }
2830
2831 let decimals = stake_pool.check_mint(pool_mint_info)?;
2832 stake_pool.check_validator_list(validator_list_info)?;
2833 stake_pool.check_authority_withdraw(
2834 withdraw_authority_info.key,
2835 program_id,
2836 stake_pool_info.key,
2837 )?;
2838
2839 if stake_pool.manager_fee_account != *manager_fee_info.key {
2840 return Err(StakePoolError::InvalidFeeAccount.into());
2841 }
2842 if stake_pool.token_program_id != *token_program_info.key {
2843 return Err(ProgramError::IncorrectProgramId);
2844 }
2845
2846 if stake_pool.last_update_epoch < clock.epoch {
2847 return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
2848 }
2849
2850 check_account_owner(validator_list_info, program_id)?;
2851 let mut validator_list_data = validator_list_info.data.borrow_mut();
2852 let (header, mut validator_list) =
2853 ValidatorListHeader::deserialize_vec(&mut validator_list_data)?;
2854 if !header.is_valid() {
2855 return Err(StakePoolError::InvalidState.into());
2856 }
2857
2858 let pool_tokens_fee = if stake_pool.manager_fee_account == *burn_from_pool_info.key
2862 || stake_pool.check_manager_fee_info(manager_fee_info).is_err()
2863 {
2864 0
2865 } else {
2866 stake_pool
2867 .calc_pool_tokens_stake_withdrawal_fee(pool_tokens)
2868 .ok_or(StakePoolError::CalculationFailure)?
2869 };
2870 let pool_tokens_burnt = pool_tokens
2871 .checked_sub(pool_tokens_fee)
2872 .ok_or(StakePoolError::CalculationFailure)?;
2873
2874 let mut withdraw_lamports = stake_pool
2875 .calc_lamports_withdraw_amount(pool_tokens_burnt)
2876 .ok_or(StakePoolError::CalculationFailure)?;
2877
2878 if withdraw_lamports == 0 {
2879 return Err(StakePoolError::WithdrawalTooSmall.into());
2880 }
2881
2882 if let Some(minimum_lamports_out) = minimum_lamports_out {
2883 if withdraw_lamports < minimum_lamports_out {
2884 return Err(StakePoolError::ExceededSlippage.into());
2885 }
2886 }
2887
2888 let split_from_rent = rent.minimum_balance(stake_split_from.data_len());
2889 let stake_minimum_delegation = stake::tools::get_minimum_delegation()?;
2890 let stake_state = try_from_slice_unchecked::<stake::state::StakeStateV2>(
2891 &stake_split_from.data.borrow(),
2892 )?;
2893 let required_lamports = minimum_stake_lamports(split_from_rent, stake_minimum_delegation);
2894
2895 let lamports_per_pool_token = stake_pool
2896 .get_lamports_per_pool_token()
2897 .ok_or(StakePoolError::CalculationFailure)?;
2898 let minimum_lamports_with_tolerance =
2899 required_lamports.saturating_add(lamports_per_pool_token);
2900
2901 let has_active_stake = validator_list
2902 .find::<ValidatorStakeInfo, _>(|x| {
2903 ValidatorStakeInfo::active_lamports_greater_than(
2904 x,
2905 &minimum_lamports_with_tolerance,
2906 ) && ValidatorStakeInfo::is_active(x)
2907 })
2908 .is_some();
2909 let has_transient_stake = validator_list
2910 .find::<ValidatorStakeInfo, _>(|x| {
2911 ValidatorStakeInfo::transient_lamports_greater_than(
2912 x,
2913 &minimum_lamports_with_tolerance,
2914 ) && ValidatorStakeInfo::is_active(x)
2915 })
2916 .is_some();
2917
2918 let validator_list_item_info = if *stake_split_from.key == stake_pool.reserve_stake {
2919 if has_transient_stake || has_active_stake {
2921 msg!("Error withdrawing from reserve: validator stake accounts have lamports available, please use those first.");
2922 return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into());
2923 }
2924
2925 let minimum_reserve_lamports = minimum_reserve_lamports(split_from_rent);
2927 if stake_split_from
2928 .lamports()
2929 .saturating_sub(withdraw_lamports)
2930 < minimum_reserve_lamports
2931 {
2932 msg!("Attempting to withdraw {} lamports, maximum possible SOL withdrawal is {} lamports",
2933 withdraw_lamports,
2934 stake_split_from.lamports().saturating_sub(minimum_reserve_lamports)
2935 );
2936 return Err(StakePoolError::SolWithdrawalTooLarge.into());
2937 }
2938 None
2939 } else {
2940 let delegation = stake_state
2941 .delegation()
2942 .ok_or(StakePoolError::WrongStakeStake)?;
2943 let vote_account_address = delegation.voter_pubkey;
2944
2945 if let Some(preferred_withdraw_validator) =
2946 stake_pool.preferred_withdraw_validator_vote_address
2947 {
2948 if let Some(preferred_validator_info) = validator_list
2951 .find::<ValidatorStakeInfo, _>(|x| {
2952 ValidatorStakeInfo::memcmp_pubkey(x, &preferred_withdraw_validator)
2953 })
2954 {
2955 let available_lamports =
2956 u64::from(preferred_validator_info.active_stake_lamports)
2957 .saturating_sub(minimum_lamports_with_tolerance);
2958 if preferred_withdraw_validator != vote_account_address
2959 && available_lamports > 0
2960 {
2961 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));
2962 return Err(StakePoolError::IncorrectWithdrawVoteAddress.into());
2963 }
2964 } else {
2965 msg!("Preferred withdraw validator not found, allowing withdrawal from any validator");
2966 }
2967 }
2968
2969 let validator_stake_info = validator_list
2970 .find_mut::<ValidatorStakeInfo, _>(|x| {
2971 ValidatorStakeInfo::memcmp_pubkey(x, &vote_account_address)
2972 })
2973 .ok_or(StakePoolError::ValidatorNotFound)?;
2974
2975 let withdraw_source = if has_active_stake {
2976 check_validator_stake_address(
2979 program_id,
2980 stake_pool_info.key,
2981 stake_split_from.key,
2982 &vote_account_address,
2983 NonZeroU32::new(validator_stake_info.validator_seed_suffix.into()),
2984 )?;
2985 StakeWithdrawSource::Active
2986 } else if has_transient_stake
2987 || validator_stake_info.transient_stake_lamports != 0.into()
2988 {
2989 check_transient_stake_address(
2993 program_id,
2994 stake_pool_info.key,
2995 stake_split_from.key,
2996 &vote_account_address,
2997 validator_stake_info.transient_seed_suffix.into(),
2998 )?;
2999 StakeWithdrawSource::Transient
3000 } else {
3001 check_validator_stake_address(
3003 program_id,
3004 stake_pool_info.key,
3005 stake_split_from.key,
3006 &vote_account_address,
3007 NonZeroU32::new(validator_stake_info.validator_seed_suffix.into()),
3008 )?;
3009 StakeWithdrawSource::ValidatorRemoval
3010 };
3011
3012 if validator_stake_info.status != StakeStatus::Active.into() {
3013 msg!("Validator is marked for removal and no longer allowing withdrawals");
3014 return Err(StakePoolError::ValidatorNotFound.into());
3015 }
3016
3017 match withdraw_source {
3018 StakeWithdrawSource::Active | StakeWithdrawSource::Transient => {
3019 let remaining_lamports = stake_split_from
3020 .lamports()
3021 .saturating_sub(withdraw_lamports);
3022 if remaining_lamports < required_lamports {
3023 msg!("Attempting to withdraw {} lamports from validator account with {} stake lamports, {} must remain", withdraw_lamports, stake_split_from.lamports(), required_lamports);
3024 return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into());
3025 }
3026 }
3027 StakeWithdrawSource::ValidatorRemoval => {
3028 let split_from_lamports = stake_split_from.lamports();
3029 let upper_bound = split_from_lamports.saturating_add(lamports_per_pool_token);
3030 if withdraw_lamports < split_from_lamports || withdraw_lamports > upper_bound {
3031 msg!(
3032 "Cannot withdraw a whole account worth {} lamports, \
3033 must withdraw at least {} lamports worth of pool tokens \
3034 with a margin of {} lamports",
3035 withdraw_lamports,
3036 split_from_lamports,
3037 lamports_per_pool_token
3038 );
3039 return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into());
3040 }
3041 withdraw_lamports = split_from_lamports;
3043
3044 if stake_pool.preferred_deposit_validator_vote_address
3046 == Some(vote_account_address)
3047 {
3048 stake_pool.preferred_deposit_validator_vote_address = None;
3049 }
3050 if stake_pool.preferred_withdraw_validator_vote_address
3051 == Some(vote_account_address)
3052 {
3053 stake_pool.preferred_withdraw_validator_vote_address = None;
3054 }
3055 }
3056 }
3057 Some((validator_stake_info, withdraw_source))
3058 };
3059
3060 Self::token_burn(
3061 token_program_info.clone(),
3062 burn_from_pool_info.clone(),
3063 pool_mint_info.clone(),
3064 user_transfer_authority_info.clone(),
3065 pool_tokens_burnt,
3066 )?;
3067
3068 Self::stake_split(
3069 stake_pool_info.key,
3070 stake_split_from.clone(),
3071 withdraw_authority_info.clone(),
3072 AUTHORITY_WITHDRAW,
3073 stake_pool.stake_withdraw_bump_seed,
3074 withdraw_lamports,
3075 stake_split_to.clone(),
3076 )?;
3077
3078 Self::stake_authorize_signed(
3079 stake_pool_info.key,
3080 stake_split_to.clone(),
3081 withdraw_authority_info.clone(),
3082 AUTHORITY_WITHDRAW,
3083 stake_pool.stake_withdraw_bump_seed,
3084 user_stake_authority_info.key,
3085 clock_info.clone(),
3086 )?;
3087
3088 if pool_tokens_fee > 0 {
3089 Self::token_transfer(
3090 token_program_info.clone(),
3091 burn_from_pool_info.clone(),
3092 pool_mint_info.clone(),
3093 manager_fee_info.clone(),
3094 user_transfer_authority_info.clone(),
3095 pool_tokens_fee,
3096 decimals,
3097 )?;
3098 }
3099
3100 stake_pool.pool_token_supply = stake_pool
3101 .pool_token_supply
3102 .checked_sub(pool_tokens_burnt)
3103 .ok_or(StakePoolError::CalculationFailure)?;
3104 stake_pool.total_lamports = stake_pool
3105 .total_lamports
3106 .checked_sub(withdraw_lamports)
3107 .ok_or(StakePoolError::CalculationFailure)?;
3108 borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
3109
3110 if let Some((validator_list_item, withdraw_source)) = validator_list_item_info {
3111 match withdraw_source {
3112 StakeWithdrawSource::Active => {
3113 validator_list_item.active_stake_lamports =
3114 u64::from(validator_list_item.active_stake_lamports)
3115 .checked_sub(withdraw_lamports)
3116 .ok_or(StakePoolError::CalculationFailure)?
3117 .into()
3118 }
3119 StakeWithdrawSource::Transient => {
3120 validator_list_item.transient_stake_lamports =
3121 u64::from(validator_list_item.transient_stake_lamports)
3122 .checked_sub(withdraw_lamports)
3123 .ok_or(StakePoolError::CalculationFailure)?
3124 .into()
3125 }
3126 StakeWithdrawSource::ValidatorRemoval => {
3127 validator_list_item.active_stake_lamports =
3128 u64::from(validator_list_item.active_stake_lamports)
3129 .checked_sub(withdraw_lamports)
3130 .ok_or(StakePoolError::CalculationFailure)?
3131 .into();
3132 if u64::from(validator_list_item.active_stake_lamports) != 0 {
3133 msg!("Attempting to remove a validator from the pool, but withdrawal leaves {} lamports, update the pool to merge any unaccounted lamports",
3134 u64::from(validator_list_item.active_stake_lamports));
3135 return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
3136 }
3137 validator_list_item.status = StakeStatus::ReadyForRemoval.into();
3140 }
3141 }
3142 }
3143
3144 Ok(())
3145 }
3146
3147 #[inline(never)] fn process_withdraw_sol(
3150 program_id: &Pubkey,
3151 accounts: &[AccountInfo],
3152 pool_tokens: u64,
3153 minimum_lamports_out: Option<u64>,
3154 ) -> ProgramResult {
3155 let account_info_iter = &mut accounts.iter();
3156 let stake_pool_info = next_account_info(account_info_iter)?;
3157 let withdraw_authority_info = next_account_info(account_info_iter)?;
3158 let user_transfer_authority_info = next_account_info(account_info_iter)?;
3159 let burn_from_pool_info = next_account_info(account_info_iter)?;
3160 let reserve_stake_info = next_account_info(account_info_iter)?;
3161 let destination_lamports_info = next_account_info(account_info_iter)?;
3162 let manager_fee_info = next_account_info(account_info_iter)?;
3163 let pool_mint_info = next_account_info(account_info_iter)?;
3164 let clock_info = next_account_info(account_info_iter)?;
3165 let stake_history_info = next_account_info(account_info_iter)?;
3166 let stake_program_info = next_account_info(account_info_iter)?;
3167 let token_program_info = next_account_info(account_info_iter)?;
3168 let sol_withdraw_authority_info = next_account_info(account_info_iter);
3169
3170 let rent = Rent::get()?;
3171
3172 check_account_owner(stake_pool_info, program_id)?;
3173 let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
3174 if !stake_pool.is_valid() {
3175 return Err(StakePoolError::InvalidState.into());
3176 }
3177
3178 stake_pool.check_authority_withdraw(
3179 withdraw_authority_info.key,
3180 program_id,
3181 stake_pool_info.key,
3182 )?;
3183 stake_pool.check_sol_withdraw_authority(sol_withdraw_authority_info)?;
3184 let decimals = stake_pool.check_mint(pool_mint_info)?;
3185 stake_pool.check_reserve_stake(reserve_stake_info)?;
3186
3187 if stake_pool.token_program_id != *token_program_info.key {
3188 return Err(ProgramError::IncorrectProgramId);
3189 }
3190 check_stake_program(stake_program_info.key)?;
3191
3192 if stake_pool.manager_fee_account != *manager_fee_info.key {
3193 return Err(StakePoolError::InvalidFeeAccount.into());
3194 }
3195
3196 if stake_pool.last_update_epoch < Clock::get()?.epoch {
3199 return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
3200 }
3201
3202 let pool_tokens_fee = if stake_pool.manager_fee_account == *burn_from_pool_info.key
3206 || stake_pool.check_manager_fee_info(manager_fee_info).is_err()
3207 {
3208 0
3209 } else {
3210 stake_pool
3211 .calc_pool_tokens_sol_withdrawal_fee(pool_tokens)
3212 .ok_or(StakePoolError::CalculationFailure)?
3213 };
3214 let pool_tokens_burnt = pool_tokens
3215 .checked_sub(pool_tokens_fee)
3216 .ok_or(StakePoolError::CalculationFailure)?;
3217
3218 let withdraw_lamports = stake_pool
3219 .calc_lamports_withdraw_amount(pool_tokens_burnt)
3220 .ok_or(StakePoolError::CalculationFailure)?;
3221
3222 if withdraw_lamports == 0 {
3223 return Err(StakePoolError::WithdrawalTooSmall.into());
3224 }
3225
3226 if let Some(minimum_lamports_out) = minimum_lamports_out {
3227 if withdraw_lamports < minimum_lamports_out {
3228 return Err(StakePoolError::ExceededSlippage.into());
3229 }
3230 }
3231
3232 let reserve_rent = rent.minimum_balance(reserve_stake_info.data_len());
3233 let minimum_reserve_lamports = minimum_reserve_lamports(reserve_rent);
3234 let new_reserve_lamports = reserve_stake_info
3235 .lamports()
3236 .saturating_sub(withdraw_lamports);
3237
3238 if new_reserve_lamports < minimum_reserve_lamports {
3239 msg!("Attempting to withdraw {} lamports, maximum possible SOL withdrawal is {} lamports",
3240 withdraw_lamports,
3241 reserve_stake_info.lamports().saturating_sub(minimum_reserve_lamports)
3242 );
3243 return Err(StakePoolError::SolWithdrawalTooLarge.into());
3244 }
3245
3246 Self::token_burn(
3247 token_program_info.clone(),
3248 burn_from_pool_info.clone(),
3249 pool_mint_info.clone(),
3250 user_transfer_authority_info.clone(),
3251 pool_tokens_burnt,
3252 )?;
3253
3254 if pool_tokens_fee > 0 {
3255 Self::token_transfer(
3256 token_program_info.clone(),
3257 burn_from_pool_info.clone(),
3258 pool_mint_info.clone(),
3259 manager_fee_info.clone(),
3260 user_transfer_authority_info.clone(),
3261 pool_tokens_fee,
3262 decimals,
3263 )?;
3264 }
3265
3266 Self::stake_withdraw(
3267 stake_pool_info.key,
3268 reserve_stake_info.clone(),
3269 withdraw_authority_info.clone(),
3270 AUTHORITY_WITHDRAW,
3271 stake_pool.stake_withdraw_bump_seed,
3272 destination_lamports_info.clone(),
3273 clock_info.clone(),
3274 stake_history_info.clone(),
3275 withdraw_lamports,
3276 )?;
3277
3278 stake_pool.pool_token_supply = stake_pool
3279 .pool_token_supply
3280 .checked_sub(pool_tokens_burnt)
3281 .ok_or(StakePoolError::CalculationFailure)?;
3282 stake_pool.total_lamports = stake_pool
3283 .total_lamports
3284 .checked_sub(withdraw_lamports)
3285 .ok_or(StakePoolError::CalculationFailure)?;
3286 borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
3287
3288 Ok(())
3289 }
3290
3291 #[inline(never)]
3292 fn process_create_pool_token_metadata(
3293 program_id: &Pubkey,
3294 accounts: &[AccountInfo],
3295 name: String,
3296 symbol: String,
3297 uri: String,
3298 ) -> ProgramResult {
3299 let account_info_iter = &mut accounts.iter();
3300 let stake_pool_info = next_account_info(account_info_iter)?;
3301 let manager_info = next_account_info(account_info_iter)?;
3302 let withdraw_authority_info = next_account_info(account_info_iter)?;
3303 let pool_mint_info = next_account_info(account_info_iter)?;
3304 let payer_info = next_account_info(account_info_iter)?;
3305 let metadata_info = next_account_info(account_info_iter)?;
3306 let mpl_token_metadata_program_info = next_account_info(account_info_iter)?;
3307 let system_program_info = next_account_info(account_info_iter)?;
3308
3309 if !payer_info.is_signer {
3310 msg!("Payer did not sign metadata creation");
3311 return Err(StakePoolError::SignatureMissing.into());
3312 }
3313
3314 check_system_program(system_program_info.key)?;
3315 check_account_owner(payer_info, &system_program::id())?;
3316 check_account_owner(stake_pool_info, program_id)?;
3317 check_mpl_metadata_program(mpl_token_metadata_program_info.key)?;
3318
3319 let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
3320 if !stake_pool.is_valid() {
3321 return Err(StakePoolError::InvalidState.into());
3322 }
3323
3324 stake_pool.check_manager(manager_info)?;
3325 stake_pool.check_authority_withdraw(
3326 withdraw_authority_info.key,
3327 program_id,
3328 stake_pool_info.key,
3329 )?;
3330 stake_pool.check_mint(pool_mint_info)?;
3331 check_mpl_metadata_account_address(metadata_info.key, &stake_pool.pool_mint)?;
3332
3333 let token_mint_authority = withdraw_authority_info;
3335
3336 let new_metadata_instruction = create_metadata_accounts_v3(
3337 *mpl_token_metadata_program_info.key,
3338 *metadata_info.key,
3339 *pool_mint_info.key,
3340 *token_mint_authority.key,
3341 *payer_info.key,
3342 *token_mint_authority.key,
3343 name,
3344 symbol,
3345 uri,
3346 );
3347
3348 let (_, stake_withdraw_bump_seed) =
3349 crate::find_withdraw_authority_program_address(program_id, stake_pool_info.key);
3350
3351 let token_mint_authority_signer_seeds: &[&[_]] = &[
3352 stake_pool_info.key.as_ref(),
3353 AUTHORITY_WITHDRAW,
3354 &[stake_withdraw_bump_seed],
3355 ];
3356
3357 invoke_signed(
3358 &new_metadata_instruction,
3359 &[
3360 metadata_info.clone(),
3361 pool_mint_info.clone(),
3362 withdraw_authority_info.clone(),
3363 payer_info.clone(),
3364 withdraw_authority_info.clone(),
3365 system_program_info.clone(),
3366 ],
3367 &[token_mint_authority_signer_seeds],
3368 )?;
3369
3370 Ok(())
3371 }
3372
3373 #[inline(never)]
3374 fn process_update_pool_token_metadata(
3375 program_id: &Pubkey,
3376 accounts: &[AccountInfo],
3377 name: String,
3378 symbol: String,
3379 uri: String,
3380 ) -> ProgramResult {
3381 let account_info_iter = &mut accounts.iter();
3382
3383 let stake_pool_info = next_account_info(account_info_iter)?;
3384 let manager_info = next_account_info(account_info_iter)?;
3385 let withdraw_authority_info = next_account_info(account_info_iter)?;
3386 let metadata_info = next_account_info(account_info_iter)?;
3387 let mpl_token_metadata_program_info = next_account_info(account_info_iter)?;
3388
3389 check_account_owner(stake_pool_info, program_id)?;
3390
3391 check_mpl_metadata_program(mpl_token_metadata_program_info.key)?;
3392
3393 let stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
3394 if !stake_pool.is_valid() {
3395 return Err(StakePoolError::InvalidState.into());
3396 }
3397
3398 stake_pool.check_manager(manager_info)?;
3399 stake_pool.check_authority_withdraw(
3400 withdraw_authority_info.key,
3401 program_id,
3402 stake_pool_info.key,
3403 )?;
3404 check_mpl_metadata_account_address(metadata_info.key, &stake_pool.pool_mint)?;
3405
3406 let token_mint_authority = withdraw_authority_info;
3408
3409 let update_metadata_accounts_instruction = update_metadata_accounts_v2(
3410 *mpl_token_metadata_program_info.key,
3411 *metadata_info.key,
3412 *token_mint_authority.key,
3413 None,
3414 Some(DataV2 {
3415 name,
3416 symbol,
3417 uri,
3418 seller_fee_basis_points: 0,
3419 creators: None,
3420 collection: None,
3421 uses: None,
3422 }),
3423 None,
3424 Some(true),
3425 );
3426
3427 let (_, stake_withdraw_bump_seed) =
3428 crate::find_withdraw_authority_program_address(program_id, stake_pool_info.key);
3429
3430 let token_mint_authority_signer_seeds: &[&[_]] = &[
3431 stake_pool_info.key.as_ref(),
3432 AUTHORITY_WITHDRAW,
3433 &[stake_withdraw_bump_seed],
3434 ];
3435
3436 invoke_signed(
3437 &update_metadata_accounts_instruction,
3438 &[metadata_info.clone(), withdraw_authority_info.clone()],
3439 &[token_mint_authority_signer_seeds],
3440 )?;
3441
3442 Ok(())
3443 }
3444
3445 #[inline(never)] fn process_set_manager(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
3448 let account_info_iter = &mut accounts.iter();
3449 let stake_pool_info = next_account_info(account_info_iter)?;
3450 let manager_info = next_account_info(account_info_iter)?;
3451 let new_manager_info = next_account_info(account_info_iter)?;
3452 let new_manager_fee_info = next_account_info(account_info_iter)?;
3453
3454 check_account_owner(stake_pool_info, program_id)?;
3455 let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
3456 check_account_owner(new_manager_fee_info, &stake_pool.token_program_id)?;
3457 if !stake_pool.is_valid() {
3458 return Err(StakePoolError::InvalidState.into());
3459 }
3460
3461 stake_pool.check_manager(manager_info)?;
3462 if !new_manager_info.is_signer {
3463 msg!("New manager signature missing");
3464 return Err(StakePoolError::SignatureMissing.into());
3465 }
3466
3467 stake_pool.check_manager_fee_info(new_manager_fee_info)?;
3468
3469 stake_pool.manager = *new_manager_info.key;
3470 stake_pool.manager_fee_account = *new_manager_fee_info.key;
3471 borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
3472 Ok(())
3473 }
3474
3475 #[inline(never)] fn process_set_fee(
3478 program_id: &Pubkey,
3479 accounts: &[AccountInfo],
3480 fee: FeeType,
3481 ) -> ProgramResult {
3482 let account_info_iter = &mut accounts.iter();
3483 let stake_pool_info = next_account_info(account_info_iter)?;
3484 let manager_info = next_account_info(account_info_iter)?;
3485 let clock = Clock::get()?;
3486
3487 check_account_owner(stake_pool_info, program_id)?;
3488 let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
3489 if !stake_pool.is_valid() {
3490 return Err(StakePoolError::InvalidState.into());
3491 }
3492 stake_pool.check_manager(manager_info)?;
3493
3494 if fee.can_only_change_next_epoch() && stake_pool.last_update_epoch < clock.epoch {
3495 return Err(StakePoolError::StakeListAndPoolOutOfDate.into());
3496 }
3497
3498 fee.check_too_high()?;
3499 stake_pool.update_fee(&fee)?;
3500 borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
3501 Ok(())
3502 }
3503
3504 #[inline(never)] fn process_set_staker(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
3507 let account_info_iter = &mut accounts.iter();
3508 let stake_pool_info = next_account_info(account_info_iter)?;
3509 let set_staker_authority_info = next_account_info(account_info_iter)?;
3510 let new_staker_info = next_account_info(account_info_iter)?;
3511
3512 check_account_owner(stake_pool_info, program_id)?;
3513 let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
3514 if !stake_pool.is_valid() {
3515 return Err(StakePoolError::InvalidState.into());
3516 }
3517
3518 let staker_signed = stake_pool.check_staker(set_staker_authority_info);
3519 let manager_signed = stake_pool.check_manager(set_staker_authority_info);
3520 if staker_signed.is_err() && manager_signed.is_err() {
3521 return Err(StakePoolError::SignatureMissing.into());
3522 }
3523 stake_pool.staker = *new_staker_info.key;
3524 borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
3525 Ok(())
3526 }
3527
3528 #[inline(never)] fn process_set_funding_authority(
3531 program_id: &Pubkey,
3532 accounts: &[AccountInfo],
3533 funding_type: FundingType,
3534 ) -> ProgramResult {
3535 let account_info_iter = &mut accounts.iter();
3536 let stake_pool_info = next_account_info(account_info_iter)?;
3537 let manager_info = next_account_info(account_info_iter)?;
3538
3539 let new_authority = next_account_info(account_info_iter)
3540 .ok()
3541 .map(|new_authority_account_info| *new_authority_account_info.key);
3542
3543 check_account_owner(stake_pool_info, program_id)?;
3544 let mut stake_pool = try_from_slice_unchecked::<StakePool>(&stake_pool_info.data.borrow())?;
3545 if !stake_pool.is_valid() {
3546 return Err(StakePoolError::InvalidState.into());
3547 }
3548 stake_pool.check_manager(manager_info)?;
3549 match funding_type {
3550 FundingType::StakeDeposit => {
3551 stake_pool.stake_deposit_authority = new_authority.unwrap_or(
3552 find_deposit_authority_program_address(program_id, stake_pool_info.key).0,
3553 );
3554 }
3555 FundingType::SolDeposit => stake_pool.sol_deposit_authority = new_authority,
3556 FundingType::SolWithdraw => stake_pool.sol_withdraw_authority = new_authority,
3557 }
3558 borsh::to_writer(&mut stake_pool_info.data.borrow_mut()[..], &stake_pool)?;
3559 Ok(())
3560 }
3561
3562 pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
3564 let instruction = StakePoolInstruction::try_from_slice(input)?;
3565 match instruction {
3566 StakePoolInstruction::Initialize {
3567 fee,
3568 withdrawal_fee,
3569 deposit_fee,
3570 referral_fee,
3571 max_validators,
3572 } => {
3573 msg!("Instruction: Initialize stake pool");
3574 Self::process_initialize(
3575 program_id,
3576 accounts,
3577 fee,
3578 withdrawal_fee,
3579 deposit_fee,
3580 referral_fee,
3581 max_validators,
3582 )
3583 }
3584 StakePoolInstruction::AddValidatorToPool(seed) => {
3585 msg!("Instruction: AddValidatorToPool");
3586 Self::process_add_validator_to_pool(program_id, accounts, seed)
3587 }
3588 StakePoolInstruction::RemoveValidatorFromPool => {
3589 msg!("Instruction: RemoveValidatorFromPool");
3590 Self::process_remove_validator_from_pool(program_id, accounts)
3591 }
3592 StakePoolInstruction::DecreaseValidatorStake {
3593 lamports,
3594 transient_stake_seed,
3595 } => {
3596 msg!("Instruction: DecreaseValidatorStake");
3597 msg!("NOTE: This instruction is deprecated, please use `DecreaseValidatorStakeWithReserve`");
3598 Self::process_decrease_validator_stake(
3599 program_id,
3600 accounts,
3601 lamports,
3602 transient_stake_seed,
3603 None,
3604 false,
3605 )
3606 }
3607 StakePoolInstruction::DecreaseValidatorStakeWithReserve {
3608 lamports,
3609 transient_stake_seed,
3610 } => {
3611 msg!("Instruction: DecreaseValidatorStakeWithReserve");
3612 Self::process_decrease_validator_stake(
3613 program_id,
3614 accounts,
3615 lamports,
3616 transient_stake_seed,
3617 None,
3618 true,
3619 )
3620 }
3621 StakePoolInstruction::DecreaseAdditionalValidatorStake {
3622 lamports,
3623 transient_stake_seed,
3624 ephemeral_stake_seed,
3625 } => {
3626 msg!("Instruction: DecreaseAdditionalValidatorStake");
3627 Self::process_decrease_validator_stake(
3628 program_id,
3629 accounts,
3630 lamports,
3631 transient_stake_seed,
3632 Some(ephemeral_stake_seed),
3633 true,
3634 )
3635 }
3636 StakePoolInstruction::IncreaseValidatorStake {
3637 lamports,
3638 transient_stake_seed,
3639 } => {
3640 msg!("Instruction: IncreaseValidatorStake");
3641 Self::process_increase_validator_stake(
3642 program_id,
3643 accounts,
3644 lamports,
3645 transient_stake_seed,
3646 None,
3647 )
3648 }
3649 StakePoolInstruction::IncreaseAdditionalValidatorStake {
3650 lamports,
3651 transient_stake_seed,
3652 ephemeral_stake_seed,
3653 } => {
3654 msg!("Instruction: IncreaseAdditionalValidatorStake");
3655 Self::process_increase_validator_stake(
3656 program_id,
3657 accounts,
3658 lamports,
3659 transient_stake_seed,
3660 Some(ephemeral_stake_seed),
3661 )
3662 }
3663 StakePoolInstruction::SetPreferredValidator {
3664 validator_type,
3665 validator_vote_address,
3666 } => {
3667 msg!("Instruction: SetPreferredValidator");
3668 Self::process_set_preferred_validator(
3669 program_id,
3670 accounts,
3671 validator_type,
3672 validator_vote_address,
3673 )
3674 }
3675 StakePoolInstruction::UpdateValidatorListBalance {
3676 start_index,
3677 no_merge,
3678 } => {
3679 msg!("Instruction: UpdateValidatorListBalance");
3680 Self::process_update_validator_list_balance(
3681 program_id,
3682 accounts,
3683 start_index,
3684 no_merge,
3685 )
3686 }
3687 StakePoolInstruction::UpdateStakePoolBalance => {
3688 msg!("Instruction: UpdateStakePoolBalance");
3689 Self::process_update_stake_pool_balance(program_id, accounts)
3690 }
3691 StakePoolInstruction::CleanupRemovedValidatorEntries => {
3692 msg!("Instruction: CleanupRemovedValidatorEntries");
3693 Self::process_cleanup_removed_validator_entries(program_id, accounts)
3694 }
3695 StakePoolInstruction::DepositStake => {
3696 msg!("Instruction: DepositStake");
3697 Self::process_deposit_stake(program_id, accounts, None)
3698 }
3699 StakePoolInstruction::WithdrawStake(amount) => {
3700 msg!("Instruction: WithdrawStake");
3701 Self::process_withdraw_stake(program_id, accounts, amount, None)
3702 }
3703 StakePoolInstruction::SetFee { fee } => {
3704 msg!("Instruction: SetFee");
3705 Self::process_set_fee(program_id, accounts, fee)
3706 }
3707 StakePoolInstruction::SetManager => {
3708 msg!("Instruction: SetManager");
3709 Self::process_set_manager(program_id, accounts)
3710 }
3711 StakePoolInstruction::SetStaker => {
3712 msg!("Instruction: SetStaker");
3713 Self::process_set_staker(program_id, accounts)
3714 }
3715 StakePoolInstruction::SetFundingAuthority(funding_type) => {
3716 msg!("Instruction: SetFundingAuthority");
3717 Self::process_set_funding_authority(program_id, accounts, funding_type)
3718 }
3719 StakePoolInstruction::DepositSol(lamports) => {
3720 msg!("Instruction: DepositSol");
3721 Self::process_deposit_sol(program_id, accounts, lamports, None)
3722 }
3723 StakePoolInstruction::WithdrawSol(pool_tokens) => {
3724 msg!("Instruction: WithdrawSol");
3725 Self::process_withdraw_sol(program_id, accounts, pool_tokens, None)
3726 }
3727 StakePoolInstruction::CreateTokenMetadata { name, symbol, uri } => {
3728 msg!("Instruction: CreateTokenMetadata");
3729 Self::process_create_pool_token_metadata(program_id, accounts, name, symbol, uri)
3730 }
3731 StakePoolInstruction::UpdateTokenMetadata { name, symbol, uri } => {
3732 msg!("Instruction: UpdateTokenMetadata");
3733 Self::process_update_pool_token_metadata(program_id, accounts, name, symbol, uri)
3734 }
3735 #[allow(deprecated)]
3736 StakePoolInstruction::Redelegate { .. } => {
3737 msg!("Instruction: Redelegate will not be enabled");
3738 Err(ProgramError::InvalidInstructionData)
3739 }
3740 StakePoolInstruction::DepositStakeWithSlippage {
3741 minimum_pool_tokens_out,
3742 } => {
3743 msg!("Instruction: DepositStakeWithSlippage");
3744 Self::process_deposit_stake(program_id, accounts, Some(minimum_pool_tokens_out))
3745 }
3746 StakePoolInstruction::WithdrawStakeWithSlippage {
3747 pool_tokens_in,
3748 minimum_lamports_out,
3749 } => {
3750 msg!("Instruction: WithdrawStakeWithSlippage");
3751 Self::process_withdraw_stake(
3752 program_id,
3753 accounts,
3754 pool_tokens_in,
3755 Some(minimum_lamports_out),
3756 )
3757 }
3758 StakePoolInstruction::DepositSolWithSlippage {
3759 lamports_in,
3760 minimum_pool_tokens_out,
3761 } => {
3762 msg!("Instruction: DepositSolWithSlippage");
3763 Self::process_deposit_sol(
3764 program_id,
3765 accounts,
3766 lamports_in,
3767 Some(minimum_pool_tokens_out),
3768 )
3769 }
3770 StakePoolInstruction::WithdrawSolWithSlippage {
3771 pool_tokens_in,
3772 minimum_lamports_out,
3773 } => {
3774 msg!("Instruction: WithdrawSolWithSlippage");
3775 Self::process_withdraw_sol(
3776 program_id,
3777 accounts,
3778 pool_tokens_in,
3779 Some(minimum_lamports_out),
3780 )
3781 }
3782 }
3783 }
3784}