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