1use {
2 crate::{helpers::*, id, PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH, PSEUDO_RENT_EXEMPT_RESERVE},
3 solana_account_info::{next_account_info, AccountInfo},
4 solana_clock::Clock,
5 solana_cpi::set_return_data,
6 solana_msg::msg,
7 solana_program_error::{ProgramError, ProgramResult},
8 solana_pubkey::Pubkey,
9 solana_rent::Rent,
10 solana_stake_interface::{
11 error::StakeError,
12 instruction::{
13 AuthorizeCheckedWithSeedArgs, AuthorizeWithSeedArgs, LockupArgs, LockupCheckedArgs,
14 StakeInstruction,
15 },
16 stake_flags::StakeFlags,
17 state::{Authorized, Lockup, Meta, StakeAuthorize, StakeStateV2},
18 sysvar::stake_history::StakeHistorySysvar,
19 tools::{acceptable_reference_epoch_credits, eligible_for_deactivate_delinquent},
20 },
21 solana_sysvar::{epoch_rewards::EpochRewards, Sysvar},
22 solana_sysvar_id::SysvarId,
23 solana_vote_interface::{program as solana_vote_program, state::VoteStateV4},
24 std::{collections::HashSet, mem::MaybeUninit},
25};
26
27fn get_vote_state(vote_account_info: &AccountInfo) -> Result<Box<VoteStateV4>, ProgramError> {
28 if *vote_account_info.owner != solana_vote_program::id() {
29 return Err(ProgramError::IncorrectProgramId);
30 }
31
32 let mut vote_state = Box::new(MaybeUninit::uninit());
33 VoteStateV4::deserialize_into_uninit(
34 &vote_account_info.try_borrow_data()?,
35 vote_state.as_mut(),
36 vote_account_info.key,
37 )
38 .map_err(|_| ProgramError::InvalidAccountData)?;
39 let vote_state = unsafe { vote_state.assume_init() };
40
41 Ok(vote_state)
42}
43
44fn get_stake_state(stake_account_info: &AccountInfo) -> Result<StakeStateV2, ProgramError> {
45 if *stake_account_info.owner != id() {
46 return Err(ProgramError::InvalidAccountOwner);
47 }
48
49 stake_account_info
50 .deserialize_data()
51 .map_err(|_| ProgramError::InvalidAccountData)
52}
53
54fn set_stake_state(stake_account_info: &AccountInfo, new_state: &StakeStateV2) -> ProgramResult {
55 let serialized_size =
56 bincode::serialized_size(new_state).map_err(|_| ProgramError::InvalidAccountData)?;
57 if serialized_size > stake_account_info.data_len() as u64 {
58 return Err(ProgramError::AccountDataTooSmall);
59 }
60
61 bincode::serialize_into(&mut stake_account_info.data.borrow_mut()[..], new_state)
62 .map_err(|_| ProgramError::InvalidAccountData)
63}
64
65fn relocate_lamports(
67 source_account_info: &AccountInfo,
68 destination_account_info: &AccountInfo,
69 lamports: u64,
70) -> ProgramResult {
71 {
72 let mut source_lamports = source_account_info.try_borrow_mut_lamports()?;
73 **source_lamports = source_lamports
74 .checked_sub(lamports)
75 .ok_or(ProgramError::InsufficientFunds)?;
76 }
77
78 {
79 let mut destination_lamports = destination_account_info.try_borrow_mut_lamports()?;
80 **destination_lamports = destination_lamports
81 .checked_add(lamports)
82 .ok_or(ProgramError::ArithmeticOverflow)?;
83 }
84
85 Ok(())
86}
87
88fn collect_signers(accounts: &[AccountInfo]) -> HashSet<Pubkey> {
94 let mut signers = HashSet::new();
95
96 for account in accounts {
97 if account.is_signer {
98 signers.insert(*account.key);
99 }
100 }
101
102 signers
103}
104
105fn collect_signers_checked<'a>(
107 authority_info: Option<&'a AccountInfo>,
108 custodian_info: Option<&'a AccountInfo>,
109) -> Result<(HashSet<Pubkey>, Option<&'a Pubkey>), ProgramError> {
110 let mut signers = HashSet::new();
111
112 if let Some(authority_info) = authority_info {
113 if authority_info.is_signer {
114 signers.insert(*authority_info.key);
115 } else {
116 return Err(ProgramError::MissingRequiredSignature);
117 }
118 }
119
120 let custodian = if let Some(custodian_info) = custodian_info {
121 if custodian_info.is_signer {
122 signers.insert(*custodian_info.key);
123 Some(custodian_info.key)
124 } else {
125 return Err(ProgramError::MissingRequiredSignature);
126 }
127 } else {
128 None
129 };
130
131 Ok((signers, custodian))
132}
133
134fn do_initialize(
135 stake_account_info: &AccountInfo,
136 authorized: Authorized,
137 lockup: Lockup,
138) -> ProgramResult {
139 if stake_account_info.data_len() != StakeStateV2::size_of() {
140 return Err(ProgramError::InvalidAccountData);
141 }
142
143 if let StakeStateV2::Uninitialized = get_stake_state(stake_account_info)? {
144 let rent = Rent::get()?;
145 let rent_exempt_reserve = rent.minimum_balance(stake_account_info.data_len());
146 if stake_account_info.lamports() >= rent_exempt_reserve {
147 let stake_state = StakeStateV2::Initialized(Meta {
148 authorized,
149 lockup,
150 #[allow(deprecated)]
151 rent_exempt_reserve: PSEUDO_RENT_EXEMPT_RESERVE,
152 });
153
154 set_stake_state(stake_account_info, &stake_state)
155 } else {
156 Err(ProgramError::InsufficientFunds)
157 }
158 } else {
159 Err(ProgramError::InvalidAccountData)
160 }
161}
162
163fn do_authorize(
164 stake_account_info: &AccountInfo,
165 signers: &HashSet<Pubkey>,
166 new_authority: &Pubkey,
167 authority_type: StakeAuthorize,
168 custodian: Option<&Pubkey>,
169) -> ProgramResult {
170 let clock = &Clock::get()?;
171
172 match get_stake_state(stake_account_info)? {
173 StakeStateV2::Initialized(mut meta) => {
174 meta.authorized
175 .authorize(
176 signers,
177 new_authority,
178 authority_type,
179 Some((&meta.lockup, clock, custodian)),
180 )
181 .map_err(to_program_error)?;
182
183 set_stake_state(stake_account_info, &StakeStateV2::Initialized(meta))
184 }
185 StakeStateV2::Stake(mut meta, stake, stake_flags) => {
186 meta.authorized
187 .authorize(
188 signers,
189 new_authority,
190 authority_type,
191 Some((&meta.lockup, clock, custodian)),
192 )
193 .map_err(to_program_error)?;
194
195 set_stake_state(
196 stake_account_info,
197 &StakeStateV2::Stake(meta, stake, stake_flags),
198 )
199 }
200 _ => Err(ProgramError::InvalidAccountData),
201 }
202}
203
204fn do_set_lockup(
205 stake_account_info: &AccountInfo,
206 signers: &HashSet<Pubkey>,
207 lockup: &LockupArgs,
208 clock: &Clock,
209) -> ProgramResult {
210 match get_stake_state(stake_account_info)? {
211 StakeStateV2::Initialized(mut meta) => {
212 meta.set_lockup(lockup, signers, clock)
213 .map_err(to_program_error)?;
214
215 set_stake_state(stake_account_info, &StakeStateV2::Initialized(meta))
216 }
217 StakeStateV2::Stake(mut meta, stake, stake_flags) => {
218 meta.set_lockup(lockup, signers, clock)
219 .map_err(to_program_error)?;
220
221 set_stake_state(
222 stake_account_info,
223 &StakeStateV2::Stake(meta, stake, stake_flags),
224 )
225 }
226 _ => Err(ProgramError::InvalidAccountData),
227 }
228}
229
230fn move_stake_or_lamports_shared_checks(
231 source_stake_account_info: &AccountInfo,
232 move_amount: u64,
233 destination_stake_account_info: &AccountInfo,
234 stake_authority_info: &AccountInfo,
235) -> Result<(MergeKind, MergeKind), ProgramError> {
236 let (signers, _) = collect_signers_checked(Some(stake_authority_info), None)?;
238
239 if *source_stake_account_info.key == *destination_stake_account_info.key {
241 return Err(ProgramError::InvalidInstructionData);
242 }
243
244 if !source_stake_account_info.is_writable || !destination_stake_account_info.is_writable {
248 return Err(ProgramError::InvalidInstructionData);
249 }
250
251 if move_amount == 0 {
253 return Err(ProgramError::InvalidArgument);
254 }
255
256 let clock = Clock::get()?;
257 let stake_history = StakeHistorySysvar(clock.epoch);
258
259 let source_merge_kind = MergeKind::get_if_mergeable(
262 &get_stake_state(source_stake_account_info)?,
263 source_stake_account_info.lamports(),
264 &clock,
265 &stake_history,
266 )?;
267
268 source_merge_kind
270 .meta()
271 .authorized
272 .check(&signers, StakeAuthorize::Staker)
273 .map_err(to_program_error)?;
274
275 let destination_merge_kind = MergeKind::get_if_mergeable(
277 &get_stake_state(destination_stake_account_info)?,
278 destination_stake_account_info.lamports(),
279 &clock,
280 &stake_history,
281 )?;
282
283 MergeKind::metas_can_merge(
285 source_merge_kind.meta(),
286 destination_merge_kind.meta(),
287 &clock,
288 )?;
289
290 Ok((source_merge_kind, destination_merge_kind))
291}
292
293pub struct Processor {}
324impl Processor {
325 fn process_initialize(
326 accounts: &[AccountInfo],
327 authorized: Authorized,
328 lockup: Lockup,
329 ) -> ProgramResult {
330 let account_info_iter = &mut accounts.iter();
331
332 let stake_account_info = next_account_info(account_info_iter)?;
334
335 do_initialize(stake_account_info, authorized, lockup)?;
337
338 Ok(())
339 }
340
341 fn process_authorize(
342 accounts: &[AccountInfo],
343 new_authority: Pubkey,
344 authority_type: StakeAuthorize,
345 ) -> ProgramResult {
346 let signers = collect_signers(accounts);
347 let account_info_iter = &mut accounts.iter();
348
349 let stake_account_info = next_account_info(account_info_iter)?;
351
352 {
354 let branch_account = next_account_info(account_info_iter)?;
355 if Clock::check_id(branch_account.key) {
356 let _stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
357 } else {
358 let stake_or_withdraw_authority_info = branch_account;
359 if !stake_or_withdraw_authority_info.is_signer {
360 return Err(ProgramError::MissingRequiredSignature);
361 }
362 }
363 }
364
365 let option_lockup_authority_info = next_account_info(account_info_iter).ok();
367
368 let custodian = option_lockup_authority_info
369 .filter(|a| a.is_signer)
370 .map(|a| a.key);
371
372 do_authorize(
374 stake_account_info,
375 &signers,
376 &new_authority,
377 authority_type,
378 custodian,
379 )?;
380
381 Ok(())
382 }
383
384 fn process_delegate(accounts: &[AccountInfo]) -> ProgramResult {
385 let signers = collect_signers(accounts);
386 let account_info_iter = &mut accounts.iter();
387
388 let stake_account_info = next_account_info(account_info_iter)?;
390 let vote_account_info = next_account_info(account_info_iter)?;
391
392 {
394 let branch_account = next_account_info(account_info_iter)?;
395 if Clock::check_id(branch_account.key) {
396 let _stake_history_info = next_account_info(account_info_iter)?;
397 let _stake_config_info = next_account_info(account_info_iter)?;
398 } else {
400 let stake_authority_info = branch_account;
401 if !stake_authority_info.is_signer {
402 return Err(ProgramError::MissingRequiredSignature);
403 }
404 }
405 };
406
407 let rent = &Rent::get()?;
408 let clock = &Clock::get()?;
409 let stake_history = &StakeHistorySysvar(clock.epoch);
410
411 let vote_state = get_vote_state(vote_account_info)?;
412
413 let rent_exempt_reserve = rent.minimum_balance(stake_account_info.data_len());
414
415 match get_stake_state(stake_account_info)? {
416 StakeStateV2::Initialized(meta) => {
417 meta.authorized
418 .check(&signers, StakeAuthorize::Staker)
419 .map_err(to_program_error)?;
420
421 let ValidatedDelegatedInfo { stake_amount } =
422 validate_delegated_amount(stake_account_info, rent_exempt_reserve)?;
423
424 let stake = new_stake(
425 stake_amount,
426 vote_account_info.key,
427 vote_state.credits(),
428 clock.epoch,
429 );
430
431 set_stake_state(
432 stake_account_info,
433 &StakeStateV2::Stake(meta, stake, StakeFlags::empty()),
434 )
435 }
436 StakeStateV2::Stake(meta, mut stake, flags) => {
437 meta.authorized
439 .check(&signers, StakeAuthorize::Staker)
440 .map_err(to_program_error)?;
441
442 let ValidatedDelegatedInfo { stake_amount } =
444 validate_delegated_amount(stake_account_info, rent_exempt_reserve)?;
445
446 let effective_stake = stake.delegation.stake_v2(
448 clock.epoch,
449 stake_history,
450 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
451 );
452
453 if effective_stake == 0 {
454 stake = new_stake(
458 stake_amount,
459 vote_account_info.key,
460 vote_state.credits(),
461 clock.epoch,
462 );
463 } else if clock.epoch == stake.delegation.deactivation_epoch
464 && stake.delegation.voter_pubkey == *vote_account_info.key
465 {
466 if stake_amount < stake.delegation.stake {
468 return Err(StakeError::InsufficientDelegation.into());
469 }
470 stake.delegation.deactivation_epoch = u64::MAX;
471 } else {
472 return Err(StakeError::TooSoonToRedelegate.into());
474 }
475
476 set_stake_state(stake_account_info, &StakeStateV2::Stake(meta, stake, flags))
478 }
479 _ => Err(ProgramError::InvalidAccountData),
480 }?;
481
482 Ok(())
483 }
484
485 fn process_split(accounts: &[AccountInfo], split_lamports: u64) -> ProgramResult {
486 let signers = collect_signers(accounts);
487 let account_info_iter = &mut accounts.iter();
488
489 let source_stake_account_info = next_account_info(account_info_iter)?;
491 let destination_stake_account_info = next_account_info(account_info_iter)?;
492
493 let rent = Rent::get()?;
498 let clock = Clock::get()?;
499 let stake_history = &StakeHistorySysvar(clock.epoch);
500 let minimum_delegation = crate::get_minimum_delegation();
501
502 if source_stake_account_info.key == destination_stake_account_info.key {
503 return Err(ProgramError::InvalidArgument);
504 }
505
506 if let StakeStateV2::Uninitialized = get_stake_state(destination_stake_account_info)? {
507 } else {
509 return Err(ProgramError::InvalidAccountData);
510 }
511
512 let source_lamport_balance = source_stake_account_info.lamports();
513 let destination_lamport_balance = destination_stake_account_info.lamports();
514
515 if split_lamports > source_lamport_balance {
516 return Err(ProgramError::InsufficientFunds);
517 }
518
519 if split_lamports == 0 {
520 return Err(ProgramError::InsufficientFunds);
521 }
522
523 let source_rent_exempt_reserve = rent.minimum_balance(source_stake_account_info.data_len());
524
525 let destination_data_len = destination_stake_account_info.data_len();
526 if destination_data_len != StakeStateV2::size_of() {
527 return Err(ProgramError::InvalidAccountData);
528 }
529 let destination_rent_exempt_reserve = rent.minimum_balance(destination_data_len);
530
531 let source_stake_state = get_stake_state(source_stake_account_info)?;
533 let (is_active_or_activating, option_dest_meta) = match source_stake_state {
534 StakeStateV2::Stake(source_meta, source_stake, _) => {
535 source_meta
536 .authorized
537 .check(&signers, StakeAuthorize::Staker)
538 .map_err(to_program_error)?;
539
540 let source_status = source_stake
541 .delegation
542 .stake_activating_and_deactivating_v2(
543 clock.epoch,
544 stake_history,
545 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
546 );
547 let is_active_or_activating =
548 source_status.effective > 0 || source_status.activating > 0;
549
550 let dest_meta = Meta {
551 #[allow(deprecated)]
552 rent_exempt_reserve: PSEUDO_RENT_EXEMPT_RESERVE,
553 ..source_meta
554 };
555
556 (is_active_or_activating, Some(dest_meta))
557 }
558 StakeStateV2::Initialized(source_meta) => {
559 source_meta
560 .authorized
561 .check(&signers, StakeAuthorize::Staker)
562 .map_err(to_program_error)?;
563
564 let dest_meta = Meta {
565 #[allow(deprecated)]
566 rent_exempt_reserve: PSEUDO_RENT_EXEMPT_RESERVE,
567 ..source_meta
568 };
569
570 (false, Some(dest_meta))
571 }
572 StakeStateV2::Uninitialized => {
573 if !source_stake_account_info.is_signer {
574 return Err(ProgramError::MissingRequiredSignature);
575 }
576
577 (false, None)
578 }
579 StakeStateV2::RewardsPool => return Err(ProgramError::InvalidAccountData),
580 };
581
582 if split_lamports == source_lamport_balance {
586 let mut destination_stake_state = source_stake_state;
587 let delegation = match (&mut destination_stake_state, option_dest_meta) {
588 (StakeStateV2::Stake(meta, stake, _), Some(dest_meta)) => {
589 *meta = dest_meta;
590
591 if is_active_or_activating {
592 stake.delegation.stake
593 } else {
594 0
595 }
596 }
597 (StakeStateV2::Initialized(meta), Some(dest_meta)) => {
598 *meta = dest_meta;
599
600 0
601 }
602 (StakeStateV2::Uninitialized, None) => 0,
603 _ => unreachable!(),
604 };
605
606 if destination_lamport_balance
607 .saturating_add(split_lamports)
608 .saturating_sub(delegation)
609 < destination_rent_exempt_reserve
610 {
611 return Err(ProgramError::InsufficientFunds);
612 }
613
614 if is_active_or_activating && delegation < minimum_delegation {
615 return Err(StakeError::InsufficientDelegation.into());
616 }
617
618 set_stake_state(destination_stake_account_info, &destination_stake_state)?;
619 source_stake_account_info.resize(0)?;
620
621 relocate_lamports(
622 source_stake_account_info,
623 destination_stake_account_info,
624 split_lamports,
625 )?;
626
627 return Ok(());
628 }
629
630 if !is_active_or_activating {
632 let mut destination_stake_state = source_stake_state;
633 match (&mut destination_stake_state, option_dest_meta) {
634 (StakeStateV2::Stake(meta, _, _), Some(dest_meta))
635 | (StakeStateV2::Initialized(meta), Some(dest_meta)) => {
636 *meta = dest_meta;
637 }
638 (StakeStateV2::Uninitialized, None) => (),
639 _ => unreachable!(),
640 }
641
642 let post_source_lamports = source_lamport_balance
643 .checked_sub(split_lamports)
644 .ok_or(ProgramError::InsufficientFunds)?;
645
646 let post_destination_lamports = destination_lamport_balance
647 .checked_add(split_lamports)
648 .ok_or(ProgramError::ArithmeticOverflow)?;
649
650 if post_source_lamports < source_rent_exempt_reserve
651 || post_destination_lamports < destination_rent_exempt_reserve
652 {
653 return Err(ProgramError::InsufficientFunds);
654 }
655
656 set_stake_state(destination_stake_account_info, &destination_stake_state)?;
657
658 relocate_lamports(
659 source_stake_account_info,
660 destination_stake_account_info,
661 split_lamports,
662 )?;
663
664 return Ok(());
665 }
666
667 match (source_stake_state, option_dest_meta) {
674 (StakeStateV2::Stake(source_meta, mut source_stake, stake_flags), Some(dest_meta)) => {
675 if destination_lamport_balance < destination_rent_exempt_reserve {
676 return Err(ProgramError::InsufficientFunds);
677 }
678
679 let mut dest_stake = source_stake;
680
681 source_stake.delegation.stake = source_stake
682 .delegation
683 .stake
684 .checked_sub(split_lamports)
685 .ok_or::<ProgramError>(StakeError::InsufficientDelegation.into())?;
686
687 if source_stake.delegation.stake < minimum_delegation {
688 return Err(StakeError::InsufficientDelegation.into());
689 }
690
691 if source_lamport_balance
695 .saturating_sub(split_lamports)
696 .saturating_sub(source_stake.delegation.stake)
697 < source_rent_exempt_reserve
698 {
699 return Err(ProgramError::InsufficientFunds);
700 }
701
702 dest_stake.delegation.stake = split_lamports;
703 if dest_stake.delegation.stake < minimum_delegation {
704 return Err(StakeError::InsufficientDelegation.into());
705 }
706
707 set_stake_state(
708 source_stake_account_info,
709 &StakeStateV2::Stake(source_meta, source_stake, stake_flags),
710 )?;
711
712 set_stake_state(
713 destination_stake_account_info,
714 &StakeStateV2::Stake(dest_meta, dest_stake, stake_flags),
715 )?;
716
717 relocate_lamports(
718 source_stake_account_info,
719 destination_stake_account_info,
720 split_lamports,
721 )?;
722 }
723 _ => unreachable!(),
724 }
725
726 Ok(())
727 }
728
729 fn process_withdraw(accounts: &[AccountInfo], withdraw_lamports: u64) -> ProgramResult {
730 let account_info_iter = &mut accounts.iter();
731
732 let source_stake_account_info = next_account_info(account_info_iter)?;
734 let destination_info = next_account_info(account_info_iter)?;
735
736 let withdraw_authority_info = {
738 let branch_account = next_account_info(account_info_iter)?;
739 if Clock::check_id(branch_account.key) {
740 let _stake_history_info = next_account_info(account_info_iter)?;
741 next_account_info(account_info_iter)?
742 } else {
743 let withdraw_authority_info = branch_account;
744 if !withdraw_authority_info.is_signer {
745 return Err(ProgramError::MissingRequiredSignature);
746 }
747 withdraw_authority_info
748 }
749 };
750
751 let option_lockup_authority_info = next_account_info(account_info_iter).ok();
753
754 let rent = &Rent::get()?;
755 let clock = &Clock::get()?;
756 let stake_history = &StakeHistorySysvar(clock.epoch);
757
758 if source_stake_account_info.key == destination_info.key {
759 return Err(ProgramError::InvalidArgument);
760 }
761
762 let source_rent_exempt_reserve = rent.minimum_balance(source_stake_account_info.data_len());
763
764 let (signers, custodian) =
767 collect_signers_checked(Some(withdraw_authority_info), option_lockup_authority_info)?;
768
769 let (lockup, reserve, is_staked) = match get_stake_state(source_stake_account_info) {
770 Ok(StakeStateV2::Stake(meta, stake, _stake_flag)) => {
771 meta.authorized
772 .check(&signers, StakeAuthorize::Withdrawer)
773 .map_err(to_program_error)?;
774 let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
776 stake.delegation.stake_v2(
777 clock.epoch,
778 stake_history,
779 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
780 )
781 } else {
782 stake.delegation.stake
786 };
787
788 let staked_and_reserve = checked_add(staked, source_rent_exempt_reserve)?;
789 (meta.lockup, staked_and_reserve, staked != 0)
790 }
791 Ok(StakeStateV2::Initialized(meta)) => {
792 meta.authorized
793 .check(&signers, StakeAuthorize::Withdrawer)
794 .map_err(to_program_error)?;
795 (meta.lockup, source_rent_exempt_reserve, false)
797 }
798 Ok(StakeStateV2::Uninitialized) => {
799 if !signers.contains(source_stake_account_info.key) {
800 return Err(ProgramError::MissingRequiredSignature);
801 }
802 (Lockup::default(), 0, false) }
804 Err(e)
805 if e == ProgramError::InvalidAccountData
806 && source_stake_account_info.data_len() == 0 =>
807 {
808 if !signers.contains(source_stake_account_info.key) {
809 return Err(ProgramError::MissingRequiredSignature);
810 }
811 (Lockup::default(), 0, false) }
813 Ok(StakeStateV2::RewardsPool) => return Err(ProgramError::InvalidAccountData),
814 Err(e) => return Err(e),
815 };
816
817 if lockup.is_in_force(clock, custodian) {
820 return Err(StakeError::LockupInForce.into());
821 }
822
823 let stake_account_lamports = source_stake_account_info.lamports();
824 if withdraw_lamports == stake_account_lamports {
825 if is_staked {
827 return Err(ProgramError::InsufficientFunds);
828 }
829
830 source_stake_account_info.resize(0)?;
832 } else {
833 let withdraw_lamports_and_reserve = checked_add(withdraw_lamports, reserve)?;
835 if withdraw_lamports_and_reserve > stake_account_lamports {
836 return Err(ProgramError::InsufficientFunds);
837 }
838 }
839
840 relocate_lamports(
841 source_stake_account_info,
842 destination_info,
843 withdraw_lamports,
844 )?;
845
846 Ok(())
847 }
848
849 fn process_deactivate(accounts: &[AccountInfo]) -> ProgramResult {
850 let signers = collect_signers(accounts);
851 let account_info_iter = &mut accounts.iter();
852
853 let stake_account_info = next_account_info(account_info_iter)?;
855
856 {
858 let branch_account = next_account_info(account_info_iter)?;
859 if Clock::check_id(branch_account.key) {
860 } else {
862 let stake_authority_info = branch_account;
863 if !stake_authority_info.is_signer {
864 return Err(ProgramError::MissingRequiredSignature);
865 }
866 }
867 }
868
869 let clock = &Clock::get()?;
870
871 match get_stake_state(stake_account_info)? {
872 StakeStateV2::Stake(meta, mut stake, stake_flags) => {
873 meta.authorized
874 .check(&signers, StakeAuthorize::Staker)
875 .map_err(to_program_error)?;
876
877 stake.deactivate(clock.epoch)?;
878
879 set_stake_state(
880 stake_account_info,
881 &StakeStateV2::Stake(meta, stake, stake_flags),
882 )
883 }
884 _ => Err(ProgramError::InvalidAccountData),
885 }?;
886
887 Ok(())
888 }
889
890 fn process_set_lockup(accounts: &[AccountInfo], lockup: LockupArgs) -> ProgramResult {
891 let signers = collect_signers(accounts);
892 let account_info_iter = &mut accounts.iter();
893
894 let stake_account_info = next_account_info(account_info_iter)?;
896
897 let clock = Clock::get()?;
902
903 do_set_lockup(stake_account_info, &signers, &lockup, &clock)?;
905
906 Ok(())
907 }
908
909 fn process_merge(accounts: &[AccountInfo]) -> ProgramResult {
910 let signers = collect_signers(accounts);
911 let account_info_iter = &mut accounts.iter();
912
913 let destination_stake_account_info = next_account_info(account_info_iter)?;
915 let source_stake_account_info = next_account_info(account_info_iter)?;
916
917 {
919 let branch_account = next_account_info(account_info_iter)?;
920 if Clock::check_id(branch_account.key) {
921 let _stake_history_info = next_account_info(account_info_iter)?;
922 } else {
924 let stake_authority_info = branch_account;
925 if !stake_authority_info.is_signer {
926 return Err(ProgramError::MissingRequiredSignature);
927 }
928 }
929 }
930
931 let clock = &Clock::get()?;
932 let stake_history = &StakeHistorySysvar(clock.epoch);
933
934 if source_stake_account_info.key == destination_stake_account_info.key {
935 return Err(ProgramError::InvalidArgument);
936 }
937
938 let source_lamports = source_stake_account_info.lamports();
939
940 msg!("Checking if destination stake is mergeable");
941 let destination_merge_kind = MergeKind::get_if_mergeable(
942 &get_stake_state(destination_stake_account_info)?,
943 destination_stake_account_info.lamports(),
944 clock,
945 stake_history,
946 )?;
947
948 destination_merge_kind
950 .meta()
951 .authorized
952 .check(&signers, StakeAuthorize::Staker)
953 .map_err(|_| ProgramError::MissingRequiredSignature)?;
954
955 msg!("Checking if source stake is mergeable");
956 let source_merge_kind = MergeKind::get_if_mergeable(
957 &get_stake_state(source_stake_account_info)?,
958 source_lamports,
959 clock,
960 stake_history,
961 )?;
962
963 msg!("Merging stake accounts");
964 if let Some(merged_state) = destination_merge_kind.merge(source_merge_kind, clock)? {
965 set_stake_state(destination_stake_account_info, &merged_state)?;
966 }
967
968 source_stake_account_info.resize(0)?;
970
971 relocate_lamports(
973 source_stake_account_info,
974 destination_stake_account_info,
975 source_lamports,
976 )?;
977
978 Ok(())
979 }
980
981 fn process_authorize_with_seed(
982 accounts: &[AccountInfo],
983 authorize_args: AuthorizeWithSeedArgs,
984 ) -> ProgramResult {
985 let account_info_iter = &mut accounts.iter();
986
987 let stake_account_info = next_account_info(account_info_iter)?;
989 let stake_or_withdraw_authority_base_info = next_account_info(account_info_iter)?;
990
991 let option_lockup_authority_info = {
993 let branch_account = next_account_info(account_info_iter).ok();
994 if branch_account.is_some_and(|account| Clock::check_id(account.key)) {
995 next_account_info(account_info_iter).ok()
996 } else {
997 branch_account
998 }
999 };
1000
1001 let (mut signers, custodian) = collect_signers_checked(None, option_lockup_authority_info)?;
1002
1003 if stake_or_withdraw_authority_base_info.is_signer {
1004 signers.insert(Pubkey::create_with_seed(
1005 stake_or_withdraw_authority_base_info.key,
1006 &authorize_args.authority_seed,
1007 &authorize_args.authority_owner,
1008 )?);
1009 }
1010
1011 do_authorize(
1013 stake_account_info,
1014 &signers,
1015 &authorize_args.new_authorized_pubkey,
1016 authorize_args.stake_authorize,
1017 custodian,
1018 )?;
1019
1020 Ok(())
1021 }
1022
1023 fn process_initialize_checked(accounts: &[AccountInfo]) -> ProgramResult {
1024 let account_info_iter = &mut accounts.iter();
1025
1026 let stake_account_info = next_account_info(account_info_iter)?;
1028
1029 let stake_authority_info = {
1031 let branch_account = next_account_info(account_info_iter)?;
1032 if Rent::check_id(branch_account.key) {
1033 next_account_info(account_info_iter)?
1034 } else {
1035 branch_account
1037 }
1038 };
1039
1040 let withdraw_authority_info = next_account_info(account_info_iter)?;
1042
1043 if !withdraw_authority_info.is_signer {
1044 return Err(ProgramError::MissingRequiredSignature);
1045 }
1046
1047 let authorized = Authorized {
1048 staker: *stake_authority_info.key,
1049 withdrawer: *withdraw_authority_info.key,
1050 };
1051
1052 do_initialize(stake_account_info, authorized, Lockup::default())?;
1054
1055 Ok(())
1056 }
1057
1058 fn process_authorize_checked(
1059 accounts: &[AccountInfo],
1060 authority_type: StakeAuthorize,
1061 ) -> ProgramResult {
1062 let signers = collect_signers(accounts);
1063 let account_info_iter = &mut accounts.iter();
1064
1065 let stake_account_info = next_account_info(account_info_iter)?;
1067
1068 {
1070 let branch_account = next_account_info(account_info_iter)?;
1071 if Clock::check_id(branch_account.key) {
1072 let _old_stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
1073 } else {
1074 let old_stake_or_withdraw_authority_info = branch_account;
1075 if !old_stake_or_withdraw_authority_info.is_signer {
1076 return Err(ProgramError::MissingRequiredSignature);
1077 }
1078 }
1079 }
1080
1081 let new_stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
1083 let option_lockup_authority_info = next_account_info(account_info_iter).ok();
1084
1085 if !new_stake_or_withdraw_authority_info.is_signer {
1086 return Err(ProgramError::MissingRequiredSignature);
1087 }
1088
1089 let custodian = option_lockup_authority_info
1090 .filter(|a| a.is_signer)
1091 .map(|a| a.key);
1092
1093 do_authorize(
1095 stake_account_info,
1096 &signers,
1097 new_stake_or_withdraw_authority_info.key,
1098 authority_type,
1099 custodian,
1100 )?;
1101
1102 Ok(())
1103 }
1104
1105 fn process_authorize_checked_with_seed(
1106 accounts: &[AccountInfo],
1107 authorize_args: AuthorizeCheckedWithSeedArgs,
1108 ) -> ProgramResult {
1109 let account_info_iter = &mut accounts.iter();
1110
1111 let stake_account_info = next_account_info(account_info_iter)?;
1113 let old_stake_or_withdraw_authority_base_info = next_account_info(account_info_iter)?;
1114
1115 let new_stake_or_withdraw_authority_info = {
1117 let branch_account = next_account_info(account_info_iter)?;
1118 if Clock::check_id(branch_account.key) {
1119 next_account_info(account_info_iter)?
1120 } else {
1121 let new_stake_or_withdraw_authority_info = branch_account;
1122 if !new_stake_or_withdraw_authority_info.is_signer {
1123 return Err(ProgramError::MissingRequiredSignature);
1124 }
1125 new_stake_or_withdraw_authority_info
1126 }
1127 };
1128
1129 let option_lockup_authority_info = next_account_info(account_info_iter).ok();
1131
1132 let (mut signers, custodian) = collect_signers_checked(
1133 Some(new_stake_or_withdraw_authority_info),
1134 option_lockup_authority_info,
1135 )?;
1136
1137 if old_stake_or_withdraw_authority_base_info.is_signer {
1138 signers.insert(Pubkey::create_with_seed(
1139 old_stake_or_withdraw_authority_base_info.key,
1140 &authorize_args.authority_seed,
1141 &authorize_args.authority_owner,
1142 )?);
1143 }
1144
1145 do_authorize(
1147 stake_account_info,
1148 &signers,
1149 new_stake_or_withdraw_authority_info.key,
1150 authorize_args.stake_authorize,
1151 custodian,
1152 )?;
1153
1154 Ok(())
1155 }
1156
1157 fn process_set_lockup_checked(
1158 accounts: &[AccountInfo],
1159 lockup_checked: LockupCheckedArgs,
1160 ) -> ProgramResult {
1161 let signers = collect_signers(accounts);
1162 let account_info_iter = &mut accounts.iter();
1163
1164 let stake_account_info = next_account_info(account_info_iter)?;
1166
1167 let _old_withdraw_or_lockup_authority_info = next_account_info(account_info_iter);
1170 let option_new_lockup_authority_info = next_account_info(account_info_iter).ok();
1171
1172 let clock = Clock::get()?;
1173
1174 let custodian = match option_new_lockup_authority_info {
1175 Some(new_lockup_authority_info) if new_lockup_authority_info.is_signer => {
1176 Some(new_lockup_authority_info.key)
1177 }
1178 Some(_) => return Err(ProgramError::MissingRequiredSignature),
1179 None => None,
1180 };
1181
1182 let lockup = LockupArgs {
1183 unix_timestamp: lockup_checked.unix_timestamp,
1184 epoch: lockup_checked.epoch,
1185 custodian: custodian.copied(),
1186 };
1187
1188 do_set_lockup(stake_account_info, &signers, &lockup, &clock)?;
1190
1191 Ok(())
1192 }
1193
1194 fn process_deactivate_delinquent(accounts: &[AccountInfo]) -> ProgramResult {
1195 let account_info_iter = &mut accounts.iter();
1196
1197 let stake_account_info = next_account_info(account_info_iter)?;
1199 let delinquent_vote_account_info = next_account_info(account_info_iter)?;
1200 let reference_vote_account_info = next_account_info(account_info_iter)?;
1201
1202 let clock = Clock::get()?;
1203
1204 let delinquent_vote_state = get_vote_state(delinquent_vote_account_info)?;
1205 let reference_vote_state = get_vote_state(reference_vote_account_info)?;
1206
1207 if !acceptable_reference_epoch_credits(&reference_vote_state.epoch_credits, clock.epoch) {
1208 return Err(StakeError::InsufficientReferenceVotes.into());
1209 }
1210
1211 if let StakeStateV2::Stake(meta, mut stake, stake_flags) =
1212 get_stake_state(stake_account_info)?
1213 {
1214 if stake.delegation.voter_pubkey != *delinquent_vote_account_info.key {
1215 return Err(StakeError::VoteAddressMismatch.into());
1216 }
1217
1218 if eligible_for_deactivate_delinquent(&delinquent_vote_state.epoch_credits, clock.epoch)
1222 {
1223 stake.deactivate(clock.epoch)?;
1224
1225 set_stake_state(
1226 stake_account_info,
1227 &StakeStateV2::Stake(meta, stake, stake_flags),
1228 )
1229 } else {
1230 Err(StakeError::MinimumDelinquentEpochsForDeactivationNotMet.into())
1231 }
1232 } else {
1233 Err(ProgramError::InvalidAccountData)
1234 }?;
1235
1236 Ok(())
1237 }
1238
1239 fn process_move_stake(accounts: &[AccountInfo], move_amount: u64) -> ProgramResult {
1240 let account_info_iter = &mut accounts.iter();
1241
1242 let source_stake_account_info = next_account_info(account_info_iter)?;
1244 let destination_stake_account_info = next_account_info(account_info_iter)?;
1245 let stake_authority_info = next_account_info(account_info_iter)?;
1246
1247 let rent = &Rent::get()?;
1248
1249 let (source_merge_kind, destination_merge_kind) = move_stake_or_lamports_shared_checks(
1250 source_stake_account_info,
1251 move_amount,
1252 destination_stake_account_info,
1253 stake_authority_info,
1254 )?;
1255
1256 let source_rent_exempt_reserve = rent.minimum_balance(source_stake_account_info.data_len());
1257
1258 let destination_rent_exempt_reserve =
1259 rent.minimum_balance(destination_stake_account_info.data_len());
1260
1261 if source_stake_account_info.data_len() != StakeStateV2::size_of()
1264 || destination_stake_account_info.data_len() != StakeStateV2::size_of()
1265 {
1266 return Err(ProgramError::InvalidAccountData);
1267 }
1268
1269 let MergeKind::FullyActive(source_meta, mut source_stake) = source_merge_kind else {
1271 return Err(ProgramError::InvalidAccountData);
1272 };
1273
1274 let minimum_delegation = crate::get_minimum_delegation();
1275 let source_effective_stake = source_stake.delegation.stake;
1276
1277 let source_final_stake = source_effective_stake
1279 .checked_sub(move_amount)
1280 .ok_or(ProgramError::InvalidArgument)?;
1281
1282 if source_final_stake != 0 && source_final_stake < minimum_delegation {
1284 return Err(ProgramError::InvalidArgument);
1285 }
1286
1287 match destination_merge_kind {
1289 MergeKind::FullyActive(destination_meta, mut destination_stake) => {
1290 if source_stake.delegation.voter_pubkey != destination_stake.delegation.voter_pubkey
1292 {
1293 return Err(StakeError::VoteAddressMismatch.into());
1294 }
1295
1296 let destination_effective_stake = destination_stake.delegation.stake;
1297 let destination_final_stake = destination_effective_stake
1298 .checked_add(move_amount)
1299 .ok_or(ProgramError::ArithmeticOverflow)?;
1300
1301 if destination_final_stake < minimum_delegation {
1304 return Err(ProgramError::InvalidArgument);
1305 }
1306
1307 merge_delegation_stake_and_credits_observed(
1308 &mut destination_stake,
1309 move_amount,
1310 source_stake.credits_observed,
1311 )?;
1312
1313 set_stake_state(
1317 destination_stake_account_info,
1318 &StakeStateV2::Stake(destination_meta, destination_stake, StakeFlags::empty()),
1319 )?;
1320 }
1321 MergeKind::Inactive(destination_meta, _, _) => {
1322 if move_amount < minimum_delegation {
1324 return Err(ProgramError::InvalidArgument);
1325 }
1326
1327 let mut destination_stake = source_stake;
1328 destination_stake.delegation.stake = move_amount;
1329
1330 set_stake_state(
1334 destination_stake_account_info,
1335 &StakeStateV2::Stake(destination_meta, destination_stake, StakeFlags::empty()),
1336 )?;
1337 }
1338 _ => return Err(ProgramError::InvalidAccountData),
1339 }
1340
1341 if source_final_stake == 0 {
1342 set_stake_state(
1343 source_stake_account_info,
1344 &StakeStateV2::Initialized(source_meta),
1345 )?;
1346 } else {
1347 source_stake.delegation.stake = source_final_stake;
1348
1349 set_stake_state(
1353 source_stake_account_info,
1354 &StakeStateV2::Stake(source_meta, source_stake, StakeFlags::empty()),
1355 )?;
1356 }
1357
1358 relocate_lamports(
1359 source_stake_account_info,
1360 destination_stake_account_info,
1361 move_amount,
1362 )?;
1363
1364 if source_stake_account_info.lamports() < source_rent_exempt_reserve
1367 || destination_stake_account_info.lamports() < destination_rent_exempt_reserve
1368 {
1369 msg!("Delegation calculations violated lamport balance assumptions");
1370 return Err(ProgramError::InvalidArgument);
1371 }
1372
1373 Ok(())
1374 }
1375
1376 fn process_move_lamports(accounts: &[AccountInfo], move_amount: u64) -> ProgramResult {
1377 let account_info_iter = &mut accounts.iter();
1378
1379 let source_stake_account_info = next_account_info(account_info_iter)?;
1381 let destination_stake_account_info = next_account_info(account_info_iter)?;
1382 let stake_authority_info = next_account_info(account_info_iter)?;
1383
1384 let rent = &Rent::get()?;
1385
1386 let (source_merge_kind, _) = move_stake_or_lamports_shared_checks(
1387 source_stake_account_info,
1388 move_amount,
1389 destination_stake_account_info,
1390 stake_authority_info,
1391 )?;
1392
1393 let source_rent_exempt_reserve = rent.minimum_balance(source_stake_account_info.data_len());
1394
1395 let source_free_lamports = match source_merge_kind {
1396 MergeKind::FullyActive(_, source_stake) => source_stake_account_info
1397 .lamports()
1398 .saturating_sub(source_stake.delegation.stake)
1399 .saturating_sub(source_rent_exempt_reserve),
1400 MergeKind::Inactive(_, source_lamports, _) => {
1401 source_lamports.saturating_sub(source_rent_exempt_reserve)
1402 }
1403 _ => return Err(ProgramError::InvalidAccountData),
1404 };
1405
1406 if move_amount > source_free_lamports {
1407 return Err(ProgramError::InvalidArgument);
1408 }
1409
1410 relocate_lamports(
1411 source_stake_account_info,
1412 destination_stake_account_info,
1413 move_amount,
1414 )?;
1415
1416 Ok(())
1417 }
1418
1419 pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
1421 if *program_id != id() {
1423 return Err(ProgramError::IncorrectProgramId);
1424 }
1425
1426 let epoch_rewards_active = EpochRewards::get()
1427 .map(|epoch_rewards| epoch_rewards.active)
1428 .unwrap_or(false);
1429
1430 let instruction =
1431 bincode::deserialize(data).map_err(|_| ProgramError::InvalidInstructionData)?;
1432
1433 if epoch_rewards_active && !matches!(instruction, StakeInstruction::GetMinimumDelegation) {
1434 return Err(StakeError::EpochRewardsActive.into());
1435 }
1436
1437 match instruction {
1438 StakeInstruction::Initialize(authorize, lockup) => {
1439 msg!("Instruction: Initialize");
1440 Self::process_initialize(accounts, authorize, lockup)
1441 }
1442 StakeInstruction::Authorize(new_authority, authority_type) => {
1443 msg!("Instruction: Authorize");
1444 Self::process_authorize(accounts, new_authority, authority_type)
1445 }
1446 StakeInstruction::DelegateStake => {
1447 msg!("Instruction: DelegateStake");
1448 Self::process_delegate(accounts)
1449 }
1450 StakeInstruction::Split(lamports) => {
1451 msg!("Instruction: Split");
1452 Self::process_split(accounts, lamports)
1453 }
1454 StakeInstruction::Withdraw(lamports) => {
1455 msg!("Instruction: Withdraw");
1456 Self::process_withdraw(accounts, lamports)
1457 }
1458 StakeInstruction::Deactivate => {
1459 msg!("Instruction: Deactivate");
1460 Self::process_deactivate(accounts)
1461 }
1462 StakeInstruction::SetLockup(lockup) => {
1463 msg!("Instruction: SetLockup");
1464 Self::process_set_lockup(accounts, lockup)
1465 }
1466 StakeInstruction::Merge => {
1467 msg!("Instruction: Merge");
1468 Self::process_merge(accounts)
1469 }
1470 StakeInstruction::AuthorizeWithSeed(args) => {
1471 msg!("Instruction: AuthorizeWithSeed");
1472 Self::process_authorize_with_seed(accounts, args)
1473 }
1474 StakeInstruction::InitializeChecked => {
1475 msg!("Instruction: InitializeChecked");
1476 Self::process_initialize_checked(accounts)
1477 }
1478 StakeInstruction::AuthorizeChecked(authority_type) => {
1479 msg!("Instruction: AuthorizeChecked");
1480 Self::process_authorize_checked(accounts, authority_type)
1481 }
1482 StakeInstruction::AuthorizeCheckedWithSeed(args) => {
1483 msg!("Instruction: AuthorizeCheckedWithSeed");
1484 Self::process_authorize_checked_with_seed(accounts, args)
1485 }
1486 StakeInstruction::SetLockupChecked(lockup_checked) => {
1487 msg!("Instruction: SetLockupChecked");
1488 Self::process_set_lockup_checked(accounts, lockup_checked)
1489 }
1490 StakeInstruction::GetMinimumDelegation => {
1491 msg!("Instruction: GetMinimumDelegation");
1492 let minimum_delegation = crate::get_minimum_delegation();
1493 set_return_data(&minimum_delegation.to_le_bytes());
1494 Ok(())
1495 }
1496 StakeInstruction::DeactivateDelinquent => {
1497 msg!("Instruction: DeactivateDelinquent");
1498 Self::process_deactivate_delinquent(accounts)
1499 }
1500 #[allow(deprecated)]
1501 StakeInstruction::Redelegate => Err(ProgramError::InvalidInstructionData),
1502 StakeInstruction::MoveStake(lamports) => {
1503 msg!("Instruction: MoveStake");
1504 Self::process_move_stake(accounts, lamports)
1505 }
1506 StakeInstruction::MoveLamports(lamports) => {
1507 msg!("Instruction: MoveLamports");
1508 Self::process_move_lamports(accounts, lamports)
1509 }
1510 }
1511 }
1512}