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 rent_exempt_reserve: PSEUDO_RENT_EXEMPT_RESERVE,
151 });
152
153 set_stake_state(stake_account_info, &stake_state)
154 } else {
155 Err(ProgramError::InsufficientFunds)
156 }
157 } else {
158 Err(ProgramError::InvalidAccountData)
159 }
160}
161
162fn do_authorize(
163 stake_account_info: &AccountInfo,
164 signers: &HashSet<Pubkey>,
165 new_authority: &Pubkey,
166 authority_type: StakeAuthorize,
167 custodian: Option<&Pubkey>,
168) -> ProgramResult {
169 let clock = &Clock::get()?;
170
171 match get_stake_state(stake_account_info)? {
172 StakeStateV2::Initialized(mut meta) => {
173 meta.authorized
174 .authorize(
175 signers,
176 new_authority,
177 authority_type,
178 Some((&meta.lockup, clock, custodian)),
179 )
180 .map_err(to_program_error)?;
181
182 set_stake_state(stake_account_info, &StakeStateV2::Initialized(meta))
183 }
184 StakeStateV2::Stake(mut meta, stake, stake_flags) => {
185 meta.authorized
186 .authorize(
187 signers,
188 new_authority,
189 authority_type,
190 Some((&meta.lockup, clock, custodian)),
191 )
192 .map_err(to_program_error)?;
193
194 set_stake_state(
195 stake_account_info,
196 &StakeStateV2::Stake(meta, stake, stake_flags),
197 )
198 }
199 _ => Err(ProgramError::InvalidAccountData),
200 }
201}
202
203fn do_set_lockup(
204 stake_account_info: &AccountInfo,
205 signers: &HashSet<Pubkey>,
206 lockup: &LockupArgs,
207 clock: &Clock,
208) -> ProgramResult {
209 match get_stake_state(stake_account_info)? {
210 StakeStateV2::Initialized(mut meta) => {
211 meta.set_lockup(lockup, signers, clock)
212 .map_err(to_program_error)?;
213
214 set_stake_state(stake_account_info, &StakeStateV2::Initialized(meta))
215 }
216 StakeStateV2::Stake(mut meta, stake, stake_flags) => {
217 meta.set_lockup(lockup, signers, clock)
218 .map_err(to_program_error)?;
219
220 set_stake_state(
221 stake_account_info,
222 &StakeStateV2::Stake(meta, stake, stake_flags),
223 )
224 }
225 _ => Err(ProgramError::InvalidAccountData),
226 }
227}
228
229fn move_stake_or_lamports_shared_checks(
230 source_stake_account_info: &AccountInfo,
231 move_amount: u64,
232 destination_stake_account_info: &AccountInfo,
233 stake_authority_info: &AccountInfo,
234) -> Result<(MergeKind, MergeKind), ProgramError> {
235 let (signers, _) = collect_signers_checked(Some(stake_authority_info), None)?;
237
238 if *source_stake_account_info.key == *destination_stake_account_info.key {
240 return Err(ProgramError::InvalidInstructionData);
241 }
242
243 if !source_stake_account_info.is_writable || !destination_stake_account_info.is_writable {
247 return Err(ProgramError::InvalidInstructionData);
248 }
249
250 if move_amount == 0 {
252 return Err(ProgramError::InvalidArgument);
253 }
254
255 let clock = Clock::get()?;
256 let stake_history = StakeHistorySysvar(clock.epoch);
257
258 let source_merge_kind = MergeKind::get_if_mergeable(
261 &get_stake_state(source_stake_account_info)?,
262 source_stake_account_info.lamports(),
263 &clock,
264 &stake_history,
265 )?;
266
267 source_merge_kind
269 .meta()
270 .authorized
271 .check(&signers, StakeAuthorize::Staker)
272 .map_err(to_program_error)?;
273
274 let destination_merge_kind = MergeKind::get_if_mergeable(
276 &get_stake_state(destination_stake_account_info)?,
277 destination_stake_account_info.lamports(),
278 &clock,
279 &stake_history,
280 )?;
281
282 MergeKind::metas_can_merge(
284 source_merge_kind.meta(),
285 destination_merge_kind.meta(),
286 &clock,
287 )?;
288
289 Ok((source_merge_kind, destination_merge_kind))
290}
291
292pub struct Processor {}
323impl Processor {
324 fn process_initialize(
325 accounts: &[AccountInfo],
326 authorized: Authorized,
327 lockup: Lockup,
328 ) -> ProgramResult {
329 let account_info_iter = &mut accounts.iter();
330
331 let stake_account_info = next_account_info(account_info_iter)?;
333
334 do_initialize(stake_account_info, authorized, lockup)?;
336
337 Ok(())
338 }
339
340 fn process_authorize(
341 accounts: &[AccountInfo],
342 new_authority: Pubkey,
343 authority_type: StakeAuthorize,
344 ) -> ProgramResult {
345 let signers = collect_signers(accounts);
346 let account_info_iter = &mut accounts.iter();
347
348 let stake_account_info = next_account_info(account_info_iter)?;
350
351 {
353 let branch_account = next_account_info(account_info_iter)?;
354 if Clock::check_id(branch_account.key) {
355 let _stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
356 } else {
357 let stake_or_withdraw_authority_info = branch_account;
358 if !stake_or_withdraw_authority_info.is_signer {
359 return Err(ProgramError::MissingRequiredSignature);
360 }
361 }
362 }
363
364 let option_lockup_authority_info = next_account_info(account_info_iter).ok();
366
367 let custodian = option_lockup_authority_info
368 .filter(|a| a.is_signer)
369 .map(|a| a.key);
370
371 do_authorize(
373 stake_account_info,
374 &signers,
375 &new_authority,
376 authority_type,
377 custodian,
378 )?;
379
380 Ok(())
381 }
382
383 fn process_delegate(accounts: &[AccountInfo]) -> ProgramResult {
384 let signers = collect_signers(accounts);
385 let account_info_iter = &mut accounts.iter();
386
387 let stake_account_info = next_account_info(account_info_iter)?;
389 let vote_account_info = next_account_info(account_info_iter)?;
390
391 {
393 let branch_account = next_account_info(account_info_iter)?;
394 if Clock::check_id(branch_account.key) {
395 let _stake_history_info = next_account_info(account_info_iter)?;
396 let _stake_config_info = next_account_info(account_info_iter)?;
397 } else {
399 let stake_authority_info = branch_account;
400 if !stake_authority_info.is_signer {
401 return Err(ProgramError::MissingRequiredSignature);
402 }
403 }
404 };
405
406 let rent = &Rent::get()?;
407 let clock = &Clock::get()?;
408 let stake_history = &StakeHistorySysvar(clock.epoch);
409
410 let vote_state = get_vote_state(vote_account_info)?;
411
412 let rent_exempt_reserve = rent.minimum_balance(stake_account_info.data_len());
413
414 match get_stake_state(stake_account_info)? {
415 StakeStateV2::Initialized(meta) => {
416 meta.authorized
417 .check(&signers, StakeAuthorize::Staker)
418 .map_err(to_program_error)?;
419
420 let ValidatedDelegatedInfo { stake_amount } =
421 validate_delegated_amount(stake_account_info, rent_exempt_reserve)?;
422
423 let stake = new_stake(
424 stake_amount,
425 vote_account_info.key,
426 vote_state.credits(),
427 clock.epoch,
428 );
429
430 set_stake_state(
431 stake_account_info,
432 &StakeStateV2::Stake(meta, stake, StakeFlags::empty()),
433 )
434 }
435 StakeStateV2::Stake(meta, mut stake, flags) => {
436 meta.authorized
438 .check(&signers, StakeAuthorize::Staker)
439 .map_err(to_program_error)?;
440
441 let ValidatedDelegatedInfo { stake_amount } =
443 validate_delegated_amount(stake_account_info, rent_exempt_reserve)?;
444
445 let effective_stake = stake.delegation.stake(
447 clock.epoch,
448 stake_history,
449 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
450 );
451
452 if effective_stake == 0 {
453 stake = new_stake(
457 stake_amount,
458 vote_account_info.key,
459 vote_state.credits(),
460 clock.epoch,
461 );
462 } else if clock.epoch == stake.delegation.deactivation_epoch
463 && stake.delegation.voter_pubkey == *vote_account_info.key
464 {
465 if stake_amount < stake.delegation.stake {
466 return Err(StakeError::InsufficientDelegation.into());
467 }
468 stake.delegation.deactivation_epoch = u64::MAX;
469 } else {
470 return Err(StakeError::TooSoonToRedelegate.into());
472 }
473
474 set_stake_state(stake_account_info, &StakeStateV2::Stake(meta, stake, flags))
476 }
477 _ => Err(ProgramError::InvalidAccountData),
478 }?;
479
480 Ok(())
481 }
482
483 fn process_split(accounts: &[AccountInfo], split_lamports: u64) -> ProgramResult {
484 let signers = collect_signers(accounts);
485 let account_info_iter = &mut accounts.iter();
486
487 let source_stake_account_info = next_account_info(account_info_iter)?;
489 let destination_stake_account_info = next_account_info(account_info_iter)?;
490
491 let rent = Rent::get()?;
496 let clock = Clock::get()?;
497 let stake_history = &StakeHistorySysvar(clock.epoch);
498 let minimum_delegation = crate::get_minimum_delegation();
499
500 if source_stake_account_info.key == destination_stake_account_info.key {
501 return Err(ProgramError::InvalidArgument);
502 }
503
504 if let StakeStateV2::Uninitialized = get_stake_state(destination_stake_account_info)? {
505 } else {
507 return Err(ProgramError::InvalidAccountData);
508 }
509
510 let source_lamport_balance = source_stake_account_info.lamports();
511 let destination_lamport_balance = destination_stake_account_info.lamports();
512
513 if split_lamports > source_lamport_balance {
514 return Err(ProgramError::InsufficientFunds);
515 }
516
517 if split_lamports == 0 {
518 return Err(ProgramError::InsufficientFunds);
519 }
520
521 let source_rent_exempt_reserve = rent.minimum_balance(source_stake_account_info.data_len());
522
523 let destination_data_len = destination_stake_account_info.data_len();
524 if destination_data_len != StakeStateV2::size_of() {
525 return Err(ProgramError::InvalidAccountData);
526 }
527 let destination_rent_exempt_reserve = rent.minimum_balance(destination_data_len);
528
529 let source_stake_state = get_stake_state(source_stake_account_info)?;
531 let (is_active_or_activating, option_dest_meta) = match source_stake_state {
532 StakeStateV2::Stake(source_meta, source_stake, _) => {
533 source_meta
534 .authorized
535 .check(&signers, StakeAuthorize::Staker)
536 .map_err(to_program_error)?;
537
538 let source_status = source_stake.delegation.stake_activating_and_deactivating(
539 clock.epoch,
540 stake_history,
541 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
542 );
543 let is_active_or_activating =
544 source_status.effective > 0 || source_status.activating > 0;
545
546 let mut dest_meta = source_meta;
547 dest_meta.rent_exempt_reserve = PSEUDO_RENT_EXEMPT_RESERVE;
548
549 (is_active_or_activating, Some(dest_meta))
550 }
551 StakeStateV2::Initialized(source_meta) => {
552 source_meta
553 .authorized
554 .check(&signers, StakeAuthorize::Staker)
555 .map_err(to_program_error)?;
556
557 let mut dest_meta = source_meta;
558 dest_meta.rent_exempt_reserve = PSEUDO_RENT_EXEMPT_RESERVE;
559
560 (false, Some(dest_meta))
561 }
562 StakeStateV2::Uninitialized => {
563 if !source_stake_account_info.is_signer {
564 return Err(ProgramError::MissingRequiredSignature);
565 }
566
567 (false, None)
568 }
569 StakeStateV2::RewardsPool => return Err(ProgramError::InvalidAccountData),
570 };
571
572 if split_lamports == source_lamport_balance {
576 let mut destination_stake_state = source_stake_state;
577 let delegation = match (&mut destination_stake_state, option_dest_meta) {
578 (StakeStateV2::Stake(meta, stake, _), Some(dest_meta)) => {
579 *meta = dest_meta;
580
581 if is_active_or_activating {
582 stake.delegation.stake
583 } else {
584 0
585 }
586 }
587 (StakeStateV2::Initialized(meta), Some(dest_meta)) => {
588 *meta = dest_meta;
589
590 0
591 }
592 (StakeStateV2::Uninitialized, None) => 0,
593 _ => unreachable!(),
594 };
595
596 if destination_lamport_balance
597 .saturating_add(split_lamports)
598 .saturating_sub(delegation)
599 < destination_rent_exempt_reserve
600 {
601 return Err(ProgramError::InsufficientFunds);
602 }
603
604 if is_active_or_activating && delegation < minimum_delegation {
605 return Err(StakeError::InsufficientDelegation.into());
606 }
607
608 set_stake_state(destination_stake_account_info, &destination_stake_state)?;
609 source_stake_account_info.resize(0)?;
610
611 relocate_lamports(
612 source_stake_account_info,
613 destination_stake_account_info,
614 split_lamports,
615 )?;
616
617 return Ok(());
618 }
619
620 if !is_active_or_activating {
622 let mut destination_stake_state = source_stake_state;
623 match (&mut destination_stake_state, option_dest_meta) {
624 (StakeStateV2::Stake(meta, _, _), Some(dest_meta))
625 | (StakeStateV2::Initialized(meta), Some(dest_meta)) => {
626 *meta = dest_meta;
627 }
628 (StakeStateV2::Uninitialized, None) => (),
629 _ => unreachable!(),
630 }
631
632 let post_source_lamports = source_lamport_balance
633 .checked_sub(split_lamports)
634 .ok_or(ProgramError::InsufficientFunds)?;
635
636 let post_destination_lamports = destination_lamport_balance
637 .checked_add(split_lamports)
638 .ok_or(ProgramError::ArithmeticOverflow)?;
639
640 if post_source_lamports < source_rent_exempt_reserve
641 || post_destination_lamports < destination_rent_exempt_reserve
642 {
643 return Err(ProgramError::InsufficientFunds);
644 }
645
646 set_stake_state(destination_stake_account_info, &destination_stake_state)?;
647
648 relocate_lamports(
649 source_stake_account_info,
650 destination_stake_account_info,
651 split_lamports,
652 )?;
653
654 return Ok(());
655 }
656
657 match (source_stake_state, option_dest_meta) {
664 (StakeStateV2::Stake(source_meta, mut source_stake, stake_flags), Some(dest_meta)) => {
665 if destination_lamport_balance < destination_rent_exempt_reserve {
666 return Err(ProgramError::InsufficientFunds);
667 }
668
669 let mut dest_stake = source_stake;
670
671 source_stake.delegation.stake = source_stake
672 .delegation
673 .stake
674 .checked_sub(split_lamports)
675 .ok_or::<ProgramError>(StakeError::InsufficientDelegation.into())?;
676
677 if source_stake.delegation.stake < minimum_delegation {
678 return Err(StakeError::InsufficientDelegation.into());
679 }
680
681 if source_lamport_balance
685 .saturating_sub(split_lamports)
686 .saturating_sub(source_stake.delegation.stake)
687 < source_rent_exempt_reserve
688 {
689 return Err(ProgramError::InsufficientFunds);
690 }
691
692 dest_stake.delegation.stake = split_lamports;
693 if dest_stake.delegation.stake < minimum_delegation {
694 return Err(StakeError::InsufficientDelegation.into());
695 }
696
697 set_stake_state(
698 source_stake_account_info,
699 &StakeStateV2::Stake(source_meta, source_stake, stake_flags),
700 )?;
701
702 set_stake_state(
703 destination_stake_account_info,
704 &StakeStateV2::Stake(dest_meta, dest_stake, stake_flags),
705 )?;
706
707 relocate_lamports(
708 source_stake_account_info,
709 destination_stake_account_info,
710 split_lamports,
711 )?;
712 }
713 _ => unreachable!(),
714 }
715
716 Ok(())
717 }
718
719 fn process_withdraw(accounts: &[AccountInfo], withdraw_lamports: u64) -> ProgramResult {
720 let account_info_iter = &mut accounts.iter();
721
722 let source_stake_account_info = next_account_info(account_info_iter)?;
724 let destination_info = next_account_info(account_info_iter)?;
725
726 let withdraw_authority_info = {
728 let branch_account = next_account_info(account_info_iter)?;
729 if Clock::check_id(branch_account.key) {
730 let _stake_history_info = next_account_info(account_info_iter)?;
731 next_account_info(account_info_iter)?
732 } else {
733 let withdraw_authority_info = branch_account;
734 if !withdraw_authority_info.is_signer {
735 return Err(ProgramError::MissingRequiredSignature);
736 }
737 withdraw_authority_info
738 }
739 };
740
741 let option_lockup_authority_info = next_account_info(account_info_iter).ok();
743
744 let rent = &Rent::get()?;
745 let clock = &Clock::get()?;
746 let stake_history = &StakeHistorySysvar(clock.epoch);
747
748 if source_stake_account_info.key == destination_info.key {
749 return Err(ProgramError::InvalidArgument);
750 }
751
752 let source_rent_exempt_reserve = rent.minimum_balance(source_stake_account_info.data_len());
753
754 let (signers, custodian) =
757 collect_signers_checked(Some(withdraw_authority_info), option_lockup_authority_info)?;
758
759 let (lockup, reserve, is_staked) = match get_stake_state(source_stake_account_info) {
760 Ok(StakeStateV2::Stake(meta, stake, _stake_flag)) => {
761 meta.authorized
762 .check(&signers, StakeAuthorize::Withdrawer)
763 .map_err(to_program_error)?;
764 let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
766 stake.delegation.stake(
767 clock.epoch,
768 stake_history,
769 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
770 )
771 } else {
772 stake.delegation.stake
776 };
777
778 let staked_and_reserve = checked_add(staked, source_rent_exempt_reserve)?;
779 (meta.lockup, staked_and_reserve, staked != 0)
780 }
781 Ok(StakeStateV2::Initialized(meta)) => {
782 meta.authorized
783 .check(&signers, StakeAuthorize::Withdrawer)
784 .map_err(to_program_error)?;
785 (meta.lockup, source_rent_exempt_reserve, false)
787 }
788 Ok(StakeStateV2::Uninitialized) => {
789 if !signers.contains(source_stake_account_info.key) {
790 return Err(ProgramError::MissingRequiredSignature);
791 }
792 (Lockup::default(), 0, false) }
794 Err(e)
795 if e == ProgramError::InvalidAccountData
796 && source_stake_account_info.data_len() == 0 =>
797 {
798 if !signers.contains(source_stake_account_info.key) {
799 return Err(ProgramError::MissingRequiredSignature);
800 }
801 (Lockup::default(), 0, false) }
803 Ok(StakeStateV2::RewardsPool) => return Err(ProgramError::InvalidAccountData),
804 Err(e) => return Err(e),
805 };
806
807 if lockup.is_in_force(clock, custodian) {
810 return Err(StakeError::LockupInForce.into());
811 }
812
813 let stake_account_lamports = source_stake_account_info.lamports();
814 if withdraw_lamports == stake_account_lamports {
815 if is_staked {
817 return Err(ProgramError::InsufficientFunds);
818 }
819
820 source_stake_account_info.resize(0)?;
822 } else {
823 let withdraw_lamports_and_reserve = checked_add(withdraw_lamports, reserve)?;
825 if withdraw_lamports_and_reserve > stake_account_lamports {
826 return Err(ProgramError::InsufficientFunds);
827 }
828 }
829
830 relocate_lamports(
831 source_stake_account_info,
832 destination_info,
833 withdraw_lamports,
834 )?;
835
836 Ok(())
837 }
838
839 fn process_deactivate(accounts: &[AccountInfo]) -> ProgramResult {
840 let signers = collect_signers(accounts);
841 let account_info_iter = &mut accounts.iter();
842
843 let stake_account_info = next_account_info(account_info_iter)?;
845
846 {
848 let branch_account = next_account_info(account_info_iter)?;
849 if Clock::check_id(branch_account.key) {
850 } else {
852 let stake_authority_info = branch_account;
853 if !stake_authority_info.is_signer {
854 return Err(ProgramError::MissingRequiredSignature);
855 }
856 }
857 }
858
859 let clock = &Clock::get()?;
860
861 match get_stake_state(stake_account_info)? {
862 StakeStateV2::Stake(meta, mut stake, stake_flags) => {
863 meta.authorized
864 .check(&signers, StakeAuthorize::Staker)
865 .map_err(to_program_error)?;
866
867 stake.deactivate(clock.epoch)?;
868
869 set_stake_state(
870 stake_account_info,
871 &StakeStateV2::Stake(meta, stake, stake_flags),
872 )
873 }
874 _ => Err(ProgramError::InvalidAccountData),
875 }?;
876
877 Ok(())
878 }
879
880 fn process_set_lockup(accounts: &[AccountInfo], lockup: LockupArgs) -> ProgramResult {
881 let signers = collect_signers(accounts);
882 let account_info_iter = &mut accounts.iter();
883
884 let stake_account_info = next_account_info(account_info_iter)?;
886
887 let clock = Clock::get()?;
892
893 do_set_lockup(stake_account_info, &signers, &lockup, &clock)?;
895
896 Ok(())
897 }
898
899 fn process_merge(accounts: &[AccountInfo]) -> ProgramResult {
900 let signers = collect_signers(accounts);
901 let account_info_iter = &mut accounts.iter();
902
903 let destination_stake_account_info = next_account_info(account_info_iter)?;
905 let source_stake_account_info = next_account_info(account_info_iter)?;
906
907 {
909 let branch_account = next_account_info(account_info_iter)?;
910 if Clock::check_id(branch_account.key) {
911 let _stake_history_info = next_account_info(account_info_iter)?;
912 } else {
914 let stake_authority_info = branch_account;
915 if !stake_authority_info.is_signer {
916 return Err(ProgramError::MissingRequiredSignature);
917 }
918 }
919 }
920
921 let clock = &Clock::get()?;
922 let stake_history = &StakeHistorySysvar(clock.epoch);
923
924 if source_stake_account_info.key == destination_stake_account_info.key {
925 return Err(ProgramError::InvalidArgument);
926 }
927
928 let source_lamports = source_stake_account_info.lamports();
929
930 msg!("Checking if destination stake is mergeable");
931 let destination_merge_kind = MergeKind::get_if_mergeable(
932 &get_stake_state(destination_stake_account_info)?,
933 destination_stake_account_info.lamports(),
934 clock,
935 stake_history,
936 )?;
937
938 destination_merge_kind
940 .meta()
941 .authorized
942 .check(&signers, StakeAuthorize::Staker)
943 .map_err(|_| ProgramError::MissingRequiredSignature)?;
944
945 msg!("Checking if source stake is mergeable");
946 let source_merge_kind = MergeKind::get_if_mergeable(
947 &get_stake_state(source_stake_account_info)?,
948 source_lamports,
949 clock,
950 stake_history,
951 )?;
952
953 msg!("Merging stake accounts");
954 if let Some(merged_state) = destination_merge_kind.merge(source_merge_kind, clock)? {
955 set_stake_state(destination_stake_account_info, &merged_state)?;
956 }
957
958 source_stake_account_info.resize(0)?;
960
961 relocate_lamports(
963 source_stake_account_info,
964 destination_stake_account_info,
965 source_lamports,
966 )?;
967
968 Ok(())
969 }
970
971 fn process_authorize_with_seed(
972 accounts: &[AccountInfo],
973 authorize_args: AuthorizeWithSeedArgs,
974 ) -> ProgramResult {
975 let account_info_iter = &mut accounts.iter();
976
977 let stake_account_info = next_account_info(account_info_iter)?;
979 let stake_or_withdraw_authority_base_info = next_account_info(account_info_iter)?;
980
981 let option_lockup_authority_info = {
983 let branch_account = next_account_info(account_info_iter).ok();
984 if branch_account.is_some_and(|account| Clock::check_id(account.key)) {
985 next_account_info(account_info_iter).ok()
986 } else {
987 branch_account
988 }
989 };
990
991 let (mut signers, custodian) = collect_signers_checked(None, option_lockup_authority_info)?;
992
993 if stake_or_withdraw_authority_base_info.is_signer {
994 signers.insert(Pubkey::create_with_seed(
995 stake_or_withdraw_authority_base_info.key,
996 &authorize_args.authority_seed,
997 &authorize_args.authority_owner,
998 )?);
999 }
1000
1001 do_authorize(
1003 stake_account_info,
1004 &signers,
1005 &authorize_args.new_authorized_pubkey,
1006 authorize_args.stake_authorize,
1007 custodian,
1008 )?;
1009
1010 Ok(())
1011 }
1012
1013 fn process_initialize_checked(accounts: &[AccountInfo]) -> ProgramResult {
1014 let account_info_iter = &mut accounts.iter();
1015
1016 let stake_account_info = next_account_info(account_info_iter)?;
1018
1019 let stake_authority_info = {
1021 let branch_account = next_account_info(account_info_iter)?;
1022 if Rent::check_id(branch_account.key) {
1023 next_account_info(account_info_iter)?
1024 } else {
1025 branch_account
1027 }
1028 };
1029
1030 let withdraw_authority_info = next_account_info(account_info_iter)?;
1032
1033 if !withdraw_authority_info.is_signer {
1034 return Err(ProgramError::MissingRequiredSignature);
1035 }
1036
1037 let authorized = Authorized {
1038 staker: *stake_authority_info.key,
1039 withdrawer: *withdraw_authority_info.key,
1040 };
1041
1042 do_initialize(stake_account_info, authorized, Lockup::default())?;
1044
1045 Ok(())
1046 }
1047
1048 fn process_authorize_checked(
1049 accounts: &[AccountInfo],
1050 authority_type: StakeAuthorize,
1051 ) -> ProgramResult {
1052 let signers = collect_signers(accounts);
1053 let account_info_iter = &mut accounts.iter();
1054
1055 let stake_account_info = next_account_info(account_info_iter)?;
1057
1058 {
1060 let branch_account = next_account_info(account_info_iter)?;
1061 if Clock::check_id(branch_account.key) {
1062 let _old_stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
1063 } else {
1064 let old_stake_or_withdraw_authority_info = branch_account;
1065 if !old_stake_or_withdraw_authority_info.is_signer {
1066 return Err(ProgramError::MissingRequiredSignature);
1067 }
1068 }
1069 }
1070
1071 let new_stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
1073 let option_lockup_authority_info = next_account_info(account_info_iter).ok();
1074
1075 if !new_stake_or_withdraw_authority_info.is_signer {
1076 return Err(ProgramError::MissingRequiredSignature);
1077 }
1078
1079 let custodian = option_lockup_authority_info
1080 .filter(|a| a.is_signer)
1081 .map(|a| a.key);
1082
1083 do_authorize(
1085 stake_account_info,
1086 &signers,
1087 new_stake_or_withdraw_authority_info.key,
1088 authority_type,
1089 custodian,
1090 )?;
1091
1092 Ok(())
1093 }
1094
1095 fn process_authorize_checked_with_seed(
1096 accounts: &[AccountInfo],
1097 authorize_args: AuthorizeCheckedWithSeedArgs,
1098 ) -> ProgramResult {
1099 let account_info_iter = &mut accounts.iter();
1100
1101 let stake_account_info = next_account_info(account_info_iter)?;
1103 let old_stake_or_withdraw_authority_base_info = next_account_info(account_info_iter)?;
1104
1105 let new_stake_or_withdraw_authority_info = {
1107 let branch_account = next_account_info(account_info_iter)?;
1108 if Clock::check_id(branch_account.key) {
1109 next_account_info(account_info_iter)?
1110 } else {
1111 let new_stake_or_withdraw_authority_info = branch_account;
1112 if !new_stake_or_withdraw_authority_info.is_signer {
1113 return Err(ProgramError::MissingRequiredSignature);
1114 }
1115 new_stake_or_withdraw_authority_info
1116 }
1117 };
1118
1119 let option_lockup_authority_info = next_account_info(account_info_iter).ok();
1121
1122 let (mut signers, custodian) = collect_signers_checked(
1123 Some(new_stake_or_withdraw_authority_info),
1124 option_lockup_authority_info,
1125 )?;
1126
1127 if old_stake_or_withdraw_authority_base_info.is_signer {
1128 signers.insert(Pubkey::create_with_seed(
1129 old_stake_or_withdraw_authority_base_info.key,
1130 &authorize_args.authority_seed,
1131 &authorize_args.authority_owner,
1132 )?);
1133 }
1134
1135 do_authorize(
1137 stake_account_info,
1138 &signers,
1139 new_stake_or_withdraw_authority_info.key,
1140 authorize_args.stake_authorize,
1141 custodian,
1142 )?;
1143
1144 Ok(())
1145 }
1146
1147 fn process_set_lockup_checked(
1148 accounts: &[AccountInfo],
1149 lockup_checked: LockupCheckedArgs,
1150 ) -> ProgramResult {
1151 let signers = collect_signers(accounts);
1152 let account_info_iter = &mut accounts.iter();
1153
1154 let stake_account_info = next_account_info(account_info_iter)?;
1156
1157 let _old_withdraw_or_lockup_authority_info = next_account_info(account_info_iter);
1160 let option_new_lockup_authority_info = next_account_info(account_info_iter).ok();
1161
1162 let clock = Clock::get()?;
1163
1164 let custodian = match option_new_lockup_authority_info {
1165 Some(new_lockup_authority_info) if new_lockup_authority_info.is_signer => {
1166 Some(new_lockup_authority_info.key)
1167 }
1168 Some(_) => return Err(ProgramError::MissingRequiredSignature),
1169 None => None,
1170 };
1171
1172 let lockup = LockupArgs {
1173 unix_timestamp: lockup_checked.unix_timestamp,
1174 epoch: lockup_checked.epoch,
1175 custodian: custodian.copied(),
1176 };
1177
1178 do_set_lockup(stake_account_info, &signers, &lockup, &clock)?;
1180
1181 Ok(())
1182 }
1183
1184 fn process_deactivate_delinquent(accounts: &[AccountInfo]) -> ProgramResult {
1185 let account_info_iter = &mut accounts.iter();
1186
1187 let stake_account_info = next_account_info(account_info_iter)?;
1189 let delinquent_vote_account_info = next_account_info(account_info_iter)?;
1190 let reference_vote_account_info = next_account_info(account_info_iter)?;
1191
1192 let clock = Clock::get()?;
1193
1194 let delinquent_vote_state = get_vote_state(delinquent_vote_account_info)?;
1195 let reference_vote_state = get_vote_state(reference_vote_account_info)?;
1196
1197 if !acceptable_reference_epoch_credits(&reference_vote_state.epoch_credits, clock.epoch) {
1198 return Err(StakeError::InsufficientReferenceVotes.into());
1199 }
1200
1201 if let StakeStateV2::Stake(meta, mut stake, stake_flags) =
1202 get_stake_state(stake_account_info)?
1203 {
1204 if stake.delegation.voter_pubkey != *delinquent_vote_account_info.key {
1205 return Err(StakeError::VoteAddressMismatch.into());
1206 }
1207
1208 if eligible_for_deactivate_delinquent(&delinquent_vote_state.epoch_credits, clock.epoch)
1212 {
1213 stake.deactivate(clock.epoch)?;
1214
1215 set_stake_state(
1216 stake_account_info,
1217 &StakeStateV2::Stake(meta, stake, stake_flags),
1218 )
1219 } else {
1220 Err(StakeError::MinimumDelinquentEpochsForDeactivationNotMet.into())
1221 }
1222 } else {
1223 Err(ProgramError::InvalidAccountData)
1224 }?;
1225
1226 Ok(())
1227 }
1228
1229 fn process_move_stake(accounts: &[AccountInfo], move_amount: u64) -> ProgramResult {
1230 let account_info_iter = &mut accounts.iter();
1231
1232 let source_stake_account_info = next_account_info(account_info_iter)?;
1234 let destination_stake_account_info = next_account_info(account_info_iter)?;
1235 let stake_authority_info = next_account_info(account_info_iter)?;
1236
1237 let rent = &Rent::get()?;
1238
1239 let (source_merge_kind, destination_merge_kind) = move_stake_or_lamports_shared_checks(
1240 source_stake_account_info,
1241 move_amount,
1242 destination_stake_account_info,
1243 stake_authority_info,
1244 )?;
1245
1246 let source_rent_exempt_reserve = rent.minimum_balance(source_stake_account_info.data_len());
1247
1248 let destination_rent_exempt_reserve =
1249 rent.minimum_balance(destination_stake_account_info.data_len());
1250
1251 if source_stake_account_info.data_len() != StakeStateV2::size_of()
1254 || destination_stake_account_info.data_len() != StakeStateV2::size_of()
1255 {
1256 return Err(ProgramError::InvalidAccountData);
1257 }
1258
1259 let MergeKind::FullyActive(source_meta, mut source_stake) = source_merge_kind else {
1261 return Err(ProgramError::InvalidAccountData);
1262 };
1263
1264 let minimum_delegation = crate::get_minimum_delegation();
1265 let source_effective_stake = source_stake.delegation.stake;
1266
1267 let source_final_stake = source_effective_stake
1269 .checked_sub(move_amount)
1270 .ok_or(ProgramError::InvalidArgument)?;
1271
1272 if source_final_stake != 0 && source_final_stake < minimum_delegation {
1274 return Err(ProgramError::InvalidArgument);
1275 }
1276
1277 match destination_merge_kind {
1279 MergeKind::FullyActive(destination_meta, mut destination_stake) => {
1280 if source_stake.delegation.voter_pubkey != destination_stake.delegation.voter_pubkey
1282 {
1283 return Err(StakeError::VoteAddressMismatch.into());
1284 }
1285
1286 let destination_effective_stake = destination_stake.delegation.stake;
1287 let destination_final_stake = destination_effective_stake
1288 .checked_add(move_amount)
1289 .ok_or(ProgramError::ArithmeticOverflow)?;
1290
1291 if destination_final_stake < minimum_delegation {
1294 return Err(ProgramError::InvalidArgument);
1295 }
1296
1297 merge_delegation_stake_and_credits_observed(
1298 &mut destination_stake,
1299 move_amount,
1300 source_stake.credits_observed,
1301 )?;
1302
1303 set_stake_state(
1307 destination_stake_account_info,
1308 &StakeStateV2::Stake(destination_meta, destination_stake, StakeFlags::empty()),
1309 )?;
1310 }
1311 MergeKind::Inactive(destination_meta, _, _) => {
1312 if move_amount < minimum_delegation {
1314 return Err(ProgramError::InvalidArgument);
1315 }
1316
1317 let mut destination_stake = source_stake;
1318 destination_stake.delegation.stake = move_amount;
1319
1320 set_stake_state(
1324 destination_stake_account_info,
1325 &StakeStateV2::Stake(destination_meta, destination_stake, StakeFlags::empty()),
1326 )?;
1327 }
1328 _ => return Err(ProgramError::InvalidAccountData),
1329 }
1330
1331 if source_final_stake == 0 {
1332 set_stake_state(
1333 source_stake_account_info,
1334 &StakeStateV2::Initialized(source_meta),
1335 )?;
1336 } else {
1337 source_stake.delegation.stake = source_final_stake;
1338
1339 set_stake_state(
1343 source_stake_account_info,
1344 &StakeStateV2::Stake(source_meta, source_stake, StakeFlags::empty()),
1345 )?;
1346 }
1347
1348 relocate_lamports(
1349 source_stake_account_info,
1350 destination_stake_account_info,
1351 move_amount,
1352 )?;
1353
1354 if source_stake_account_info.lamports() < source_rent_exempt_reserve
1357 || destination_stake_account_info.lamports() < destination_rent_exempt_reserve
1358 {
1359 msg!("Delegation calculations violated lamport balance assumptions");
1360 return Err(ProgramError::InvalidArgument);
1361 }
1362
1363 Ok(())
1364 }
1365
1366 fn process_move_lamports(accounts: &[AccountInfo], move_amount: u64) -> ProgramResult {
1367 let account_info_iter = &mut accounts.iter();
1368
1369 let source_stake_account_info = next_account_info(account_info_iter)?;
1371 let destination_stake_account_info = next_account_info(account_info_iter)?;
1372 let stake_authority_info = next_account_info(account_info_iter)?;
1373
1374 let rent = &Rent::get()?;
1375
1376 let (source_merge_kind, _) = move_stake_or_lamports_shared_checks(
1377 source_stake_account_info,
1378 move_amount,
1379 destination_stake_account_info,
1380 stake_authority_info,
1381 )?;
1382
1383 let source_rent_exempt_reserve = rent.minimum_balance(source_stake_account_info.data_len());
1384
1385 let source_free_lamports = match source_merge_kind {
1386 MergeKind::FullyActive(_, source_stake) => source_stake_account_info
1387 .lamports()
1388 .saturating_sub(source_stake.delegation.stake)
1389 .saturating_sub(source_rent_exempt_reserve),
1390 MergeKind::Inactive(_, source_lamports, _) => {
1391 source_lamports.saturating_sub(source_rent_exempt_reserve)
1392 }
1393 _ => return Err(ProgramError::InvalidAccountData),
1394 };
1395
1396 if move_amount > source_free_lamports {
1397 return Err(ProgramError::InvalidArgument);
1398 }
1399
1400 relocate_lamports(
1401 source_stake_account_info,
1402 destination_stake_account_info,
1403 move_amount,
1404 )?;
1405
1406 Ok(())
1407 }
1408
1409 pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
1411 if *program_id != id() {
1413 return Err(ProgramError::IncorrectProgramId);
1414 }
1415
1416 let epoch_rewards_active = EpochRewards::get()
1417 .map(|epoch_rewards| epoch_rewards.active)
1418 .unwrap_or(false);
1419
1420 let instruction =
1421 bincode::deserialize(data).map_err(|_| ProgramError::InvalidInstructionData)?;
1422
1423 if epoch_rewards_active && !matches!(instruction, StakeInstruction::GetMinimumDelegation) {
1424 return Err(StakeError::EpochRewardsActive.into());
1425 }
1426
1427 match instruction {
1428 StakeInstruction::Initialize(authorize, lockup) => {
1429 msg!("Instruction: Initialize");
1430 Self::process_initialize(accounts, authorize, lockup)
1431 }
1432 StakeInstruction::Authorize(new_authority, authority_type) => {
1433 msg!("Instruction: Authorize");
1434 Self::process_authorize(accounts, new_authority, authority_type)
1435 }
1436 StakeInstruction::DelegateStake => {
1437 msg!("Instruction: DelegateStake");
1438 Self::process_delegate(accounts)
1439 }
1440 StakeInstruction::Split(lamports) => {
1441 msg!("Instruction: Split");
1442 Self::process_split(accounts, lamports)
1443 }
1444 StakeInstruction::Withdraw(lamports) => {
1445 msg!("Instruction: Withdraw");
1446 Self::process_withdraw(accounts, lamports)
1447 }
1448 StakeInstruction::Deactivate => {
1449 msg!("Instruction: Deactivate");
1450 Self::process_deactivate(accounts)
1451 }
1452 StakeInstruction::SetLockup(lockup) => {
1453 msg!("Instruction: SetLockup");
1454 Self::process_set_lockup(accounts, lockup)
1455 }
1456 StakeInstruction::Merge => {
1457 msg!("Instruction: Merge");
1458 Self::process_merge(accounts)
1459 }
1460 StakeInstruction::AuthorizeWithSeed(args) => {
1461 msg!("Instruction: AuthorizeWithSeed");
1462 Self::process_authorize_with_seed(accounts, args)
1463 }
1464 StakeInstruction::InitializeChecked => {
1465 msg!("Instruction: InitializeChecked");
1466 Self::process_initialize_checked(accounts)
1467 }
1468 StakeInstruction::AuthorizeChecked(authority_type) => {
1469 msg!("Instruction: AuthorizeChecked");
1470 Self::process_authorize_checked(accounts, authority_type)
1471 }
1472 StakeInstruction::AuthorizeCheckedWithSeed(args) => {
1473 msg!("Instruction: AuthorizeCheckedWithSeed");
1474 Self::process_authorize_checked_with_seed(accounts, args)
1475 }
1476 StakeInstruction::SetLockupChecked(lockup_checked) => {
1477 msg!("Instruction: SetLockupChecked");
1478 Self::process_set_lockup_checked(accounts, lockup_checked)
1479 }
1480 StakeInstruction::GetMinimumDelegation => {
1481 msg!("Instruction: GetMinimumDelegation");
1482 let minimum_delegation = crate::get_minimum_delegation();
1483 set_return_data(&minimum_delegation.to_le_bytes());
1484 Ok(())
1485 }
1486 StakeInstruction::DeactivateDelinquent => {
1487 msg!("Instruction: DeactivateDelinquent");
1488 Self::process_deactivate_delinquent(accounts)
1489 }
1490 #[allow(deprecated)]
1491 StakeInstruction::Redelegate => Err(ProgramError::InvalidInstructionData),
1492 StakeInstruction::MoveStake(lamports) => {
1493 msg!("Instruction: MoveStake");
1494 Self::process_move_stake(accounts, lamports)
1495 }
1496 StakeInstruction::MoveLamports(lamports) => {
1497 msg!("Instruction: MoveLamports");
1498 Self::process_move_lamports(accounts, lamports)
1499 }
1500 }
1501 }
1502}