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