1use {
2 crate::{helpers::*, id, PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH},
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, SysvarSerialize},
22 solana_vote_interface::{program as solana_vote_program, state::VoteStateV4},
23 std::{collections::HashSet, mem::MaybeUninit},
24};
25
26fn get_vote_state(vote_account_info: &AccountInfo) -> Result<Box<VoteStateV4>, ProgramError> {
27 if *vote_account_info.owner != solana_vote_program::id() {
28 return Err(ProgramError::IncorrectProgramId);
29 }
30
31 let mut vote_state = Box::new(MaybeUninit::uninit());
32 VoteStateV4::deserialize_into_uninit(
33 &vote_account_info.try_borrow_data()?,
34 vote_state.as_mut(),
35 vote_account_info.key,
36 )
37 .map_err(|_| ProgramError::InvalidAccountData)?;
38 let vote_state = unsafe { vote_state.assume_init() };
39
40 Ok(vote_state)
41}
42
43fn get_stake_state(stake_account_info: &AccountInfo) -> Result<StakeStateV2, ProgramError> {
44 if *stake_account_info.owner != id() {
45 return Err(ProgramError::InvalidAccountOwner);
46 }
47
48 stake_account_info
49 .deserialize_data()
50 .map_err(|_| ProgramError::InvalidAccountData)
51}
52
53fn set_stake_state(stake_account_info: &AccountInfo, new_state: &StakeStateV2) -> ProgramResult {
54 let serialized_size =
55 bincode::serialized_size(new_state).map_err(|_| ProgramError::InvalidAccountData)?;
56 if serialized_size > stake_account_info.data_len() as u64 {
57 return Err(ProgramError::AccountDataTooSmall);
58 }
59
60 bincode::serialize_into(&mut stake_account_info.data.borrow_mut()[..], new_state)
61 .map_err(|_| ProgramError::InvalidAccountData)
62}
63
64fn relocate_lamports(
66 source_account_info: &AccountInfo,
67 destination_account_info: &AccountInfo,
68 lamports: u64,
69) -> ProgramResult {
70 {
71 let mut source_lamports = source_account_info.try_borrow_mut_lamports()?;
72 **source_lamports = source_lamports
73 .checked_sub(lamports)
74 .ok_or(ProgramError::InsufficientFunds)?;
75 }
76
77 {
78 let mut destination_lamports = destination_account_info.try_borrow_mut_lamports()?;
79 **destination_lamports = destination_lamports
80 .checked_add(lamports)
81 .ok_or(ProgramError::ArithmeticOverflow)?;
82 }
83
84 Ok(())
85}
86
87fn collect_signers(accounts: &[AccountInfo]) -> HashSet<Pubkey> {
93 let mut signers = HashSet::new();
94
95 for account in accounts {
96 if account.is_signer {
97 signers.insert(*account.key);
98 }
99 }
100
101 signers
102}
103
104fn collect_signers_checked<'a>(
106 authority_info: Option<&'a AccountInfo>,
107 custodian_info: Option<&'a AccountInfo>,
108) -> Result<(HashSet<Pubkey>, Option<&'a Pubkey>), ProgramError> {
109 let mut signers = HashSet::new();
110
111 if let Some(authority_info) = authority_info {
112 if authority_info.is_signer {
113 signers.insert(*authority_info.key);
114 } else {
115 return Err(ProgramError::MissingRequiredSignature);
116 }
117 }
118
119 let custodian = if let Some(custodian_info) = custodian_info {
120 if custodian_info.is_signer {
121 signers.insert(*custodian_info.key);
122 Some(custodian_info.key)
123 } else {
124 return Err(ProgramError::MissingRequiredSignature);
125 }
126 } else {
127 None
128 };
129
130 Ok((signers, custodian))
131}
132
133fn do_initialize(
134 stake_account_info: &AccountInfo,
135 authorized: Authorized,
136 lockup: Lockup,
137 rent: &Rent,
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_exempt_reserve = rent.minimum_balance(stake_account_info.data_len());
145 if stake_account_info.lamports() >= rent_exempt_reserve {
146 let stake_state = StakeStateV2::Initialized(Meta {
147 rent_exempt_reserve,
148 authorized,
149 lockup,
150 });
151
152 set_stake_state(stake_account_info, &stake_state)
153 } else {
154 Err(ProgramError::InsufficientFunds)
155 }
156 } else {
157 Err(ProgramError::InvalidAccountData)
158 }
159}
160
161fn do_authorize(
162 stake_account_info: &AccountInfo,
163 signers: &HashSet<Pubkey>,
164 new_authority: &Pubkey,
165 authority_type: StakeAuthorize,
166 custodian: Option<&Pubkey>,
167 clock: &Clock,
168) -> ProgramResult {
169 match get_stake_state(stake_account_info)? {
170 StakeStateV2::Initialized(mut meta) => {
171 meta.authorized
172 .authorize(
173 signers,
174 new_authority,
175 authority_type,
176 Some((&meta.lockup, clock, custodian)),
177 )
178 .map_err(to_program_error)?;
179
180 set_stake_state(stake_account_info, &StakeStateV2::Initialized(meta))
181 }
182 StakeStateV2::Stake(mut meta, stake, stake_flags) => {
183 meta.authorized
184 .authorize(
185 signers,
186 new_authority,
187 authority_type,
188 Some((&meta.lockup, clock, custodian)),
189 )
190 .map_err(to_program_error)?;
191
192 set_stake_state(
193 stake_account_info,
194 &StakeStateV2::Stake(meta, stake, stake_flags),
195 )
196 }
197 _ => Err(ProgramError::InvalidAccountData),
198 }
199}
200
201fn do_set_lockup(
202 stake_account_info: &AccountInfo,
203 signers: &HashSet<Pubkey>,
204 lockup: &LockupArgs,
205 clock: &Clock,
206) -> ProgramResult {
207 match get_stake_state(stake_account_info)? {
208 StakeStateV2::Initialized(mut meta) => {
209 meta.set_lockup(lockup, signers, clock)
210 .map_err(to_program_error)?;
211
212 set_stake_state(stake_account_info, &StakeStateV2::Initialized(meta))
213 }
214 StakeStateV2::Stake(mut meta, stake, stake_flags) => {
215 meta.set_lockup(lockup, signers, clock)
216 .map_err(to_program_error)?;
217
218 set_stake_state(
219 stake_account_info,
220 &StakeStateV2::Stake(meta, stake, stake_flags),
221 )
222 }
223 _ => Err(ProgramError::InvalidAccountData),
224 }
225}
226
227fn move_stake_or_lamports_shared_checks(
228 source_stake_account_info: &AccountInfo,
229 lamports: u64,
230 destination_stake_account_info: &AccountInfo,
231 stake_authority_info: &AccountInfo,
232) -> Result<(MergeKind, MergeKind), ProgramError> {
233 let (signers, _) = collect_signers_checked(Some(stake_authority_info), None)?;
235
236 if *source_stake_account_info.key == *destination_stake_account_info.key {
238 return Err(ProgramError::InvalidInstructionData);
239 }
240
241 if !source_stake_account_info.is_writable || !destination_stake_account_info.is_writable {
245 return Err(ProgramError::InvalidInstructionData);
246 }
247
248 if lamports == 0 {
250 return Err(ProgramError::InvalidArgument);
251 }
252
253 let clock = Clock::get()?;
254 let stake_history = StakeHistorySysvar(clock.epoch);
255
256 let source_merge_kind = MergeKind::get_if_mergeable(
259 &get_stake_state(source_stake_account_info)?,
260 source_stake_account_info.lamports(),
261 &clock,
262 &stake_history,
263 )?;
264
265 source_merge_kind
267 .meta()
268 .authorized
269 .check(&signers, StakeAuthorize::Staker)
270 .map_err(to_program_error)?;
271
272 let destination_merge_kind = MergeKind::get_if_mergeable(
274 &get_stake_state(destination_stake_account_info)?,
275 destination_stake_account_info.lamports(),
276 &clock,
277 &stake_history,
278 )?;
279
280 MergeKind::metas_can_merge(
282 source_merge_kind.meta(),
283 destination_merge_kind.meta(),
284 &clock,
285 )?;
286
287 Ok((source_merge_kind, destination_merge_kind))
288}
289
290pub struct Processor {}
311impl Processor {
312 fn process_initialize(
313 accounts: &[AccountInfo],
314 authorized: Authorized,
315 lockup: Lockup,
316 ) -> ProgramResult {
317 let account_info_iter = &mut accounts.iter();
318
319 let stake_account_info = next_account_info(account_info_iter)?;
321 let rent_info = next_account_info(account_info_iter)?;
322
323 let rent = &Rent::from_account_info(rent_info)?;
324
325 do_initialize(stake_account_info, authorized, lockup, rent)?;
327
328 Ok(())
329 }
330
331 fn process_authorize(
332 accounts: &[AccountInfo],
333 new_authority: Pubkey,
334 authority_type: StakeAuthorize,
335 ) -> ProgramResult {
336 let signers = collect_signers(accounts);
337 let account_info_iter = &mut accounts.iter();
338
339 let stake_account_info = next_account_info(account_info_iter)?;
341 let clock_info = next_account_info(account_info_iter)?;
342 let _stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
343
344 let option_lockup_authority_info = next_account_info(account_info_iter).ok();
346
347 let clock = &Clock::from_account_info(clock_info)?;
348
349 let custodian = option_lockup_authority_info
350 .filter(|a| a.is_signer)
351 .map(|a| a.key);
352
353 do_authorize(
355 stake_account_info,
356 &signers,
357 &new_authority,
358 authority_type,
359 custodian,
360 clock,
361 )?;
362
363 Ok(())
364 }
365
366 fn process_delegate(accounts: &[AccountInfo]) -> ProgramResult {
367 let signers = collect_signers(accounts);
368 let account_info_iter = &mut accounts.iter();
369
370 let stake_account_info = next_account_info(account_info_iter)?;
372 let vote_account_info = next_account_info(account_info_iter)?;
373 let clock_info = next_account_info(account_info_iter)?;
374 let _stake_history_info = next_account_info(account_info_iter)?;
375 let _stake_config_info = next_account_info(account_info_iter)?;
376
377 let clock = &Clock::from_account_info(clock_info)?;
381 let stake_history = &StakeHistorySysvar(clock.epoch);
382
383 let vote_state = get_vote_state(vote_account_info)?;
384
385 match get_stake_state(stake_account_info)? {
386 StakeStateV2::Initialized(meta) => {
387 meta.authorized
388 .check(&signers, StakeAuthorize::Staker)
389 .map_err(to_program_error)?;
390
391 let ValidatedDelegatedInfo { stake_amount } =
392 validate_delegated_amount(stake_account_info, &meta)?;
393
394 let stake = new_stake(
395 stake_amount,
396 vote_account_info.key,
397 vote_state.credits(),
398 clock.epoch,
399 );
400
401 set_stake_state(
402 stake_account_info,
403 &StakeStateV2::Stake(meta, stake, StakeFlags::empty()),
404 )
405 }
406 StakeStateV2::Stake(meta, mut stake, flags) => {
407 meta.authorized
409 .check(&signers, StakeAuthorize::Staker)
410 .map_err(to_program_error)?;
411
412 let ValidatedDelegatedInfo { stake_amount } =
414 validate_delegated_amount(stake_account_info, &meta)?;
415
416 let effective_stake = stake.delegation.stake(
418 clock.epoch,
419 stake_history,
420 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
421 );
422
423 if effective_stake == 0 {
424 stake = new_stake(
428 stake_amount,
429 vote_account_info.key,
430 vote_state.credits(),
431 clock.epoch,
432 );
433 } else if clock.epoch == stake.delegation.deactivation_epoch
434 && stake.delegation.voter_pubkey == *vote_account_info.key
435 {
436 if stake_amount < stake.delegation.stake {
437 return Err(StakeError::InsufficientDelegation.into());
438 }
439 stake.delegation.deactivation_epoch = u64::MAX;
440 } else {
441 return Err(StakeError::TooSoonToRedelegate.into());
443 }
444
445 set_stake_state(stake_account_info, &StakeStateV2::Stake(meta, stake, flags))
447 }
448 _ => Err(ProgramError::InvalidAccountData),
449 }?;
450
451 Ok(())
452 }
453
454 fn process_split(accounts: &[AccountInfo], split_lamports: u64) -> ProgramResult {
455 let signers = collect_signers(accounts);
456 let account_info_iter = &mut accounts.iter();
457
458 let source_stake_account_info = next_account_info(account_info_iter)?;
460 let destination_stake_account_info = next_account_info(account_info_iter)?;
461
462 let clock = Clock::get()?;
466 let stake_history = &StakeHistorySysvar(clock.epoch);
467
468 let destination_data_len = destination_stake_account_info.data_len();
469 if destination_data_len != StakeStateV2::size_of() {
470 return Err(ProgramError::InvalidAccountData);
471 }
472
473 if let StakeStateV2::Uninitialized = get_stake_state(destination_stake_account_info)? {
474 } else {
476 return Err(ProgramError::InvalidAccountData);
477 }
478
479 let source_lamport_balance = source_stake_account_info.lamports();
480 let destination_lamport_balance = destination_stake_account_info.lamports();
481
482 if split_lamports > source_lamport_balance {
483 return Err(ProgramError::InsufficientFunds);
484 }
485
486 match get_stake_state(source_stake_account_info)? {
487 StakeStateV2::Stake(source_meta, mut source_stake, stake_flags) => {
488 source_meta
489 .authorized
490 .check(&signers, StakeAuthorize::Staker)
491 .map_err(to_program_error)?;
492
493 let minimum_delegation = crate::get_minimum_delegation();
494
495 let status = source_stake.delegation.stake_activating_and_deactivating(
496 clock.epoch,
497 stake_history,
498 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
499 );
500
501 let is_active = status.effective > 0;
502
503 let validated_split_info = validate_split_amount(
505 source_lamport_balance,
506 destination_lamport_balance,
507 split_lamports,
508 &source_meta,
509 destination_data_len,
510 minimum_delegation,
511 is_active,
512 )?;
513
514 let (remaining_stake_delta, split_stake_amount) =
520 if validated_split_info.source_remaining_balance == 0 {
521 let remaining_stake_delta =
532 split_lamports.saturating_sub(source_meta.rent_exempt_reserve);
533 (remaining_stake_delta, remaining_stake_delta)
534 } else {
535 if source_stake.delegation.stake.saturating_sub(split_lamports)
539 < minimum_delegation
540 {
541 return Err(StakeError::InsufficientDelegation.into());
542 }
543
544 (
545 split_lamports,
546 split_lamports.saturating_sub(
547 validated_split_info
548 .destination_rent_exempt_reserve
549 .saturating_sub(destination_lamport_balance),
550 ),
551 )
552 };
553
554 if split_stake_amount < minimum_delegation {
555 return Err(StakeError::InsufficientDelegation.into());
556 }
557
558 let destination_stake =
559 source_stake.split(remaining_stake_delta, split_stake_amount)?;
560
561 let mut destination_meta = source_meta;
562 destination_meta.rent_exempt_reserve =
563 validated_split_info.destination_rent_exempt_reserve;
564
565 set_stake_state(
566 source_stake_account_info,
567 &StakeStateV2::Stake(source_meta, source_stake, stake_flags),
568 )?;
569
570 set_stake_state(
571 destination_stake_account_info,
572 &StakeStateV2::Stake(destination_meta, destination_stake, stake_flags),
573 )?;
574 }
575 StakeStateV2::Initialized(source_meta) => {
576 source_meta
577 .authorized
578 .check(&signers, StakeAuthorize::Staker)
579 .map_err(to_program_error)?;
580
581 let validated_split_info = validate_split_amount(
583 source_lamport_balance,
584 destination_lamport_balance,
585 split_lamports,
586 &source_meta,
587 destination_data_len,
588 0, false, )?;
591
592 let mut destination_meta = source_meta;
593 destination_meta.rent_exempt_reserve =
594 validated_split_info.destination_rent_exempt_reserve;
595
596 set_stake_state(
597 destination_stake_account_info,
598 &StakeStateV2::Initialized(destination_meta),
599 )?;
600 }
601 StakeStateV2::Uninitialized => {
602 if !source_stake_account_info.is_signer {
603 return Err(ProgramError::MissingRequiredSignature);
604 }
605 }
606 _ => return Err(ProgramError::InvalidAccountData),
607 }
608
609 if source_stake_account_info.key != destination_stake_account_info.key
611 && split_lamports == source_lamport_balance
612 {
613 source_stake_account_info.resize(0)?;
614 }
615
616 relocate_lamports(
617 source_stake_account_info,
618 destination_stake_account_info,
619 split_lamports,
620 )?;
621
622 Ok(())
623 }
624
625 fn process_withdraw(accounts: &[AccountInfo], withdraw_lamports: u64) -> ProgramResult {
626 let account_info_iter = &mut accounts.iter();
627
628 let source_stake_account_info = next_account_info(account_info_iter)?;
630 let destination_info = next_account_info(account_info_iter)?;
631 let clock_info = next_account_info(account_info_iter)?;
632 let _stake_history_info = next_account_info(account_info_iter)?;
633 let withdraw_authority_info = next_account_info(account_info_iter)?;
634
635 let option_lockup_authority_info = next_account_info(account_info_iter).ok();
637
638 let clock = &Clock::from_account_info(clock_info)?;
639 let stake_history = &StakeHistorySysvar(clock.epoch);
640
641 if source_stake_account_info.key == destination_info.key {
642 return Err(ProgramError::InvalidArgument);
643 }
644
645 let (signers, custodian) =
648 collect_signers_checked(Some(withdraw_authority_info), option_lockup_authority_info)?;
649
650 let (lockup, reserve, is_staked) = match get_stake_state(source_stake_account_info) {
651 Ok(StakeStateV2::Stake(meta, stake, _stake_flag)) => {
652 meta.authorized
653 .check(&signers, StakeAuthorize::Withdrawer)
654 .map_err(to_program_error)?;
655 let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
657 stake.delegation.stake(
658 clock.epoch,
659 stake_history,
660 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
661 )
662 } else {
663 stake.delegation.stake
667 };
668
669 let staked_and_reserve = checked_add(staked, meta.rent_exempt_reserve)?;
670 (meta.lockup, staked_and_reserve, staked != 0)
671 }
672 Ok(StakeStateV2::Initialized(meta)) => {
673 meta.authorized
674 .check(&signers, StakeAuthorize::Withdrawer)
675 .map_err(to_program_error)?;
676 (meta.lockup, meta.rent_exempt_reserve, false)
678 }
679 Ok(StakeStateV2::Uninitialized) => {
680 if !signers.contains(source_stake_account_info.key) {
681 return Err(ProgramError::MissingRequiredSignature);
682 }
683 (Lockup::default(), 0, false) }
685 Err(e)
686 if e == ProgramError::InvalidAccountData
687 && source_stake_account_info.data_len() == 0 =>
688 {
689 if !signers.contains(source_stake_account_info.key) {
690 return Err(ProgramError::MissingRequiredSignature);
691 }
692 (Lockup::default(), 0, false) }
694 Ok(StakeStateV2::RewardsPool) => return Err(ProgramError::InvalidAccountData),
695 Err(e) => return Err(e),
696 };
697
698 if lockup.is_in_force(clock, custodian) {
701 return Err(StakeError::LockupInForce.into());
702 }
703
704 let stake_account_lamports = source_stake_account_info.lamports();
705 if withdraw_lamports == stake_account_lamports {
706 if is_staked {
708 return Err(ProgramError::InsufficientFunds);
709 }
710
711 source_stake_account_info.resize(0)?;
713 } else {
714 let withdraw_lamports_and_reserve = checked_add(withdraw_lamports, reserve)?;
716 if withdraw_lamports_and_reserve > stake_account_lamports {
717 return Err(ProgramError::InsufficientFunds);
718 }
719 }
720
721 relocate_lamports(
722 source_stake_account_info,
723 destination_info,
724 withdraw_lamports,
725 )?;
726
727 Ok(())
728 }
729
730 fn process_deactivate(accounts: &[AccountInfo]) -> ProgramResult {
731 let signers = collect_signers(accounts);
732 let account_info_iter = &mut accounts.iter();
733
734 let stake_account_info = next_account_info(account_info_iter)?;
736 let clock_info = next_account_info(account_info_iter)?;
737
738 let clock = &Clock::from_account_info(clock_info)?;
742
743 match get_stake_state(stake_account_info)? {
744 StakeStateV2::Stake(meta, mut stake, stake_flags) => {
745 meta.authorized
746 .check(&signers, StakeAuthorize::Staker)
747 .map_err(to_program_error)?;
748
749 stake.deactivate(clock.epoch)?;
750
751 set_stake_state(
752 stake_account_info,
753 &StakeStateV2::Stake(meta, stake, stake_flags),
754 )
755 }
756 _ => Err(ProgramError::InvalidAccountData),
757 }?;
758
759 Ok(())
760 }
761
762 fn process_set_lockup(accounts: &[AccountInfo], lockup: LockupArgs) -> ProgramResult {
763 let signers = collect_signers(accounts);
764 let account_info_iter = &mut accounts.iter();
765
766 let stake_account_info = next_account_info(account_info_iter)?;
768
769 let clock = Clock::get()?;
773
774 do_set_lockup(stake_account_info, &signers, &lockup, &clock)?;
776
777 Ok(())
778 }
779
780 fn process_merge(accounts: &[AccountInfo]) -> ProgramResult {
781 let signers = collect_signers(accounts);
782 let account_info_iter = &mut accounts.iter();
783
784 let destination_stake_account_info = next_account_info(account_info_iter)?;
786 let source_stake_account_info = next_account_info(account_info_iter)?;
787 let clock_info = next_account_info(account_info_iter)?;
788 let _stake_history_info = next_account_info(account_info_iter)?;
789
790 let clock = &Clock::from_account_info(clock_info)?;
794 let stake_history = &StakeHistorySysvar(clock.epoch);
795
796 if source_stake_account_info.key == destination_stake_account_info.key {
797 return Err(ProgramError::InvalidArgument);
798 }
799
800 msg!("Checking if destination stake is mergeable");
801 let destination_merge_kind = MergeKind::get_if_mergeable(
802 &get_stake_state(destination_stake_account_info)?,
803 destination_stake_account_info.lamports(),
804 clock,
805 stake_history,
806 )?;
807
808 destination_merge_kind
810 .meta()
811 .authorized
812 .check(&signers, StakeAuthorize::Staker)
813 .map_err(|_| ProgramError::MissingRequiredSignature)?;
814
815 msg!("Checking if source stake is mergeable");
816 let source_merge_kind = MergeKind::get_if_mergeable(
817 &get_stake_state(source_stake_account_info)?,
818 source_stake_account_info.lamports(),
819 clock,
820 stake_history,
821 )?;
822
823 msg!("Merging stake accounts");
824 if let Some(merged_state) = destination_merge_kind.merge(source_merge_kind, clock)? {
825 set_stake_state(destination_stake_account_info, &merged_state)?;
826 }
827
828 source_stake_account_info.resize(0)?;
830
831 relocate_lamports(
833 source_stake_account_info,
834 destination_stake_account_info,
835 source_stake_account_info.lamports(),
836 )?;
837
838 Ok(())
839 }
840
841 fn process_authorize_with_seed(
842 accounts: &[AccountInfo],
843 authorize_args: AuthorizeWithSeedArgs,
844 ) -> ProgramResult {
845 let account_info_iter = &mut accounts.iter();
846
847 let stake_account_info = next_account_info(account_info_iter)?;
849 let stake_or_withdraw_authority_base_info = next_account_info(account_info_iter)?;
850 let clock_info = next_account_info(account_info_iter)?;
851
852 let option_lockup_authority_info = next_account_info(account_info_iter).ok();
854
855 let clock = &Clock::from_account_info(clock_info)?;
856
857 let (mut signers, custodian) = collect_signers_checked(None, option_lockup_authority_info)?;
858
859 if stake_or_withdraw_authority_base_info.is_signer {
860 signers.insert(Pubkey::create_with_seed(
861 stake_or_withdraw_authority_base_info.key,
862 &authorize_args.authority_seed,
863 &authorize_args.authority_owner,
864 )?);
865 }
866
867 do_authorize(
869 stake_account_info,
870 &signers,
871 &authorize_args.new_authorized_pubkey,
872 authorize_args.stake_authorize,
873 custodian,
874 clock,
875 )?;
876
877 Ok(())
878 }
879
880 fn process_initialize_checked(accounts: &[AccountInfo]) -> ProgramResult {
881 let account_info_iter = &mut accounts.iter();
882
883 let stake_account_info = next_account_info(account_info_iter)?;
885 let rent_info = next_account_info(account_info_iter)?;
886 let stake_authority_info = next_account_info(account_info_iter)?;
887 let withdraw_authority_info = next_account_info(account_info_iter)?;
888
889 let rent = &Rent::from_account_info(rent_info)?;
890
891 if !withdraw_authority_info.is_signer {
892 return Err(ProgramError::MissingRequiredSignature);
893 }
894
895 let authorized = Authorized {
896 staker: *stake_authority_info.key,
897 withdrawer: *withdraw_authority_info.key,
898 };
899
900 do_initialize(stake_account_info, authorized, Lockup::default(), rent)?;
902
903 Ok(())
904 }
905
906 fn process_authorize_checked(
907 accounts: &[AccountInfo],
908 authority_type: StakeAuthorize,
909 ) -> ProgramResult {
910 let signers = collect_signers(accounts);
911 let account_info_iter = &mut accounts.iter();
912
913 let stake_account_info = next_account_info(account_info_iter)?;
915 let clock_info = next_account_info(account_info_iter)?;
916 let _old_stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
917 let new_stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
918
919 let option_lockup_authority_info = next_account_info(account_info_iter).ok();
921
922 let clock = &Clock::from_account_info(clock_info)?;
923
924 if !new_stake_or_withdraw_authority_info.is_signer {
925 return Err(ProgramError::MissingRequiredSignature);
926 }
927
928 let custodian = option_lockup_authority_info
929 .filter(|a| a.is_signer)
930 .map(|a| a.key);
931
932 do_authorize(
934 stake_account_info,
935 &signers,
936 new_stake_or_withdraw_authority_info.key,
937 authority_type,
938 custodian,
939 clock,
940 )?;
941
942 Ok(())
943 }
944
945 fn process_authorize_checked_with_seed(
946 accounts: &[AccountInfo],
947 authorize_args: AuthorizeCheckedWithSeedArgs,
948 ) -> ProgramResult {
949 let account_info_iter = &mut accounts.iter();
950
951 let stake_account_info = next_account_info(account_info_iter)?;
953 let old_stake_or_withdraw_authority_base_info = next_account_info(account_info_iter)?;
954 let clock_info = next_account_info(account_info_iter)?;
955 let new_stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
956
957 let option_lockup_authority_info = next_account_info(account_info_iter).ok();
959
960 let clock = &Clock::from_account_info(clock_info)?;
961
962 let (mut signers, custodian) = collect_signers_checked(
963 Some(new_stake_or_withdraw_authority_info),
964 option_lockup_authority_info,
965 )?;
966
967 if old_stake_or_withdraw_authority_base_info.is_signer {
968 signers.insert(Pubkey::create_with_seed(
969 old_stake_or_withdraw_authority_base_info.key,
970 &authorize_args.authority_seed,
971 &authorize_args.authority_owner,
972 )?);
973 }
974
975 do_authorize(
977 stake_account_info,
978 &signers,
979 new_stake_or_withdraw_authority_info.key,
980 authorize_args.stake_authorize,
981 custodian,
982 clock,
983 )?;
984
985 Ok(())
986 }
987
988 fn process_set_lockup_checked(
989 accounts: &[AccountInfo],
990 lockup_checked: LockupCheckedArgs,
991 ) -> ProgramResult {
992 let signers = collect_signers(accounts);
993 let account_info_iter = &mut accounts.iter();
994
995 let stake_account_info = next_account_info(account_info_iter)?;
997
998 let _old_withdraw_or_lockup_authority_info = next_account_info(account_info_iter);
1000 let option_new_lockup_authority_info = next_account_info(account_info_iter).ok();
1001
1002 let clock = Clock::get()?;
1003
1004 let custodian = match option_new_lockup_authority_info {
1005 Some(new_lockup_authority_info) if new_lockup_authority_info.is_signer => {
1006 Some(new_lockup_authority_info.key)
1007 }
1008 Some(_) => return Err(ProgramError::MissingRequiredSignature),
1009 None => None,
1010 };
1011
1012 let lockup = LockupArgs {
1013 unix_timestamp: lockup_checked.unix_timestamp,
1014 epoch: lockup_checked.epoch,
1015 custodian: custodian.copied(),
1016 };
1017
1018 do_set_lockup(stake_account_info, &signers, &lockup, &clock)?;
1020
1021 Ok(())
1022 }
1023
1024 fn process_deactivate_delinquent(accounts: &[AccountInfo]) -> ProgramResult {
1025 let account_info_iter = &mut accounts.iter();
1026
1027 let stake_account_info = next_account_info(account_info_iter)?;
1029 let delinquent_vote_account_info = next_account_info(account_info_iter)?;
1030 let reference_vote_account_info = next_account_info(account_info_iter)?;
1031
1032 let clock = Clock::get()?;
1033
1034 let delinquent_vote_state = get_vote_state(delinquent_vote_account_info)?;
1035 let reference_vote_state = get_vote_state(reference_vote_account_info)?;
1036
1037 if !acceptable_reference_epoch_credits(&reference_vote_state.epoch_credits, clock.epoch) {
1038 return Err(StakeError::InsufficientReferenceVotes.into());
1039 }
1040
1041 if let StakeStateV2::Stake(meta, mut stake, stake_flags) =
1042 get_stake_state(stake_account_info)?
1043 {
1044 if stake.delegation.voter_pubkey != *delinquent_vote_account_info.key {
1045 return Err(StakeError::VoteAddressMismatch.into());
1046 }
1047
1048 if eligible_for_deactivate_delinquent(&delinquent_vote_state.epoch_credits, clock.epoch)
1052 {
1053 stake.deactivate(clock.epoch)?;
1054
1055 set_stake_state(
1056 stake_account_info,
1057 &StakeStateV2::Stake(meta, stake, stake_flags),
1058 )
1059 } else {
1060 Err(StakeError::MinimumDelinquentEpochsForDeactivationNotMet.into())
1061 }
1062 } else {
1063 Err(ProgramError::InvalidAccountData)
1064 }?;
1065
1066 Ok(())
1067 }
1068
1069 fn process_move_stake(accounts: &[AccountInfo], lamports: u64) -> ProgramResult {
1070 let account_info_iter = &mut accounts.iter();
1071
1072 let source_stake_account_info = next_account_info(account_info_iter)?;
1074 let destination_stake_account_info = next_account_info(account_info_iter)?;
1075 let stake_authority_info = next_account_info(account_info_iter)?;
1076
1077 let (source_merge_kind, destination_merge_kind) = move_stake_or_lamports_shared_checks(
1078 source_stake_account_info,
1079 lamports,
1080 destination_stake_account_info,
1081 stake_authority_info,
1082 )?;
1083
1084 if source_stake_account_info.data_len() != StakeStateV2::size_of()
1088 || destination_stake_account_info.data_len() != StakeStateV2::size_of()
1089 {
1090 return Err(ProgramError::InvalidAccountData);
1091 }
1092
1093 let MergeKind::FullyActive(source_meta, mut source_stake) = source_merge_kind else {
1095 return Err(ProgramError::InvalidAccountData);
1096 };
1097
1098 let minimum_delegation = crate::get_minimum_delegation();
1099 let source_effective_stake = source_stake.delegation.stake;
1100
1101 let source_final_stake = source_effective_stake
1104 .checked_sub(lamports)
1105 .ok_or(ProgramError::InvalidArgument)?;
1106
1107 if source_final_stake != 0 && source_final_stake < minimum_delegation {
1110 return Err(ProgramError::InvalidArgument);
1111 }
1112
1113 let destination_meta = match destination_merge_kind {
1115 MergeKind::FullyActive(destination_meta, mut destination_stake) => {
1116 if source_stake.delegation.voter_pubkey != destination_stake.delegation.voter_pubkey
1118 {
1119 return Err(StakeError::VoteAddressMismatch.into());
1120 }
1121
1122 let destination_effective_stake = destination_stake.delegation.stake;
1123 let destination_final_stake = destination_effective_stake
1124 .checked_add(lamports)
1125 .ok_or(ProgramError::ArithmeticOverflow)?;
1126
1127 if destination_final_stake < minimum_delegation {
1130 return Err(ProgramError::InvalidArgument);
1131 }
1132
1133 merge_delegation_stake_and_credits_observed(
1134 &mut destination_stake,
1135 lamports,
1136 source_stake.credits_observed,
1137 )?;
1138
1139 set_stake_state(
1143 destination_stake_account_info,
1144 &StakeStateV2::Stake(destination_meta, destination_stake, StakeFlags::empty()),
1145 )?;
1146
1147 destination_meta
1148 }
1149 MergeKind::Inactive(destination_meta, _, _) => {
1150 if lamports < minimum_delegation {
1152 return Err(ProgramError::InvalidArgument);
1153 }
1154
1155 let mut destination_stake = source_stake;
1156 destination_stake.delegation.stake = lamports;
1157
1158 set_stake_state(
1162 destination_stake_account_info,
1163 &StakeStateV2::Stake(destination_meta, destination_stake, StakeFlags::empty()),
1164 )?;
1165
1166 destination_meta
1167 }
1168 _ => return Err(ProgramError::InvalidAccountData),
1169 };
1170
1171 if source_final_stake == 0 {
1172 set_stake_state(
1173 source_stake_account_info,
1174 &StakeStateV2::Initialized(source_meta),
1175 )?;
1176 } else {
1177 source_stake.delegation.stake = source_final_stake;
1178
1179 set_stake_state(
1183 source_stake_account_info,
1184 &StakeStateV2::Stake(source_meta, source_stake, StakeFlags::empty()),
1185 )?;
1186 }
1187
1188 relocate_lamports(
1189 source_stake_account_info,
1190 destination_stake_account_info,
1191 lamports,
1192 )?;
1193
1194 if source_stake_account_info.lamports() < source_meta.rent_exempt_reserve
1197 || destination_stake_account_info.lamports() < destination_meta.rent_exempt_reserve
1198 {
1199 msg!("Delegation calculations violated lamport balance assumptions");
1200 return Err(ProgramError::InvalidArgument);
1201 }
1202
1203 Ok(())
1204 }
1205
1206 fn process_move_lamports(accounts: &[AccountInfo], lamports: u64) -> ProgramResult {
1207 let account_info_iter = &mut accounts.iter();
1208
1209 let source_stake_account_info = next_account_info(account_info_iter)?;
1211 let destination_stake_account_info = next_account_info(account_info_iter)?;
1212 let stake_authority_info = next_account_info(account_info_iter)?;
1213
1214 let (source_merge_kind, _) = move_stake_or_lamports_shared_checks(
1215 source_stake_account_info,
1216 lamports,
1217 destination_stake_account_info,
1218 stake_authority_info,
1219 )?;
1220
1221 let source_free_lamports = match source_merge_kind {
1222 MergeKind::FullyActive(source_meta, source_stake) => source_stake_account_info
1223 .lamports()
1224 .saturating_sub(source_stake.delegation.stake)
1225 .saturating_sub(source_meta.rent_exempt_reserve),
1226 MergeKind::Inactive(source_meta, source_lamports, _) => {
1227 source_lamports.saturating_sub(source_meta.rent_exempt_reserve)
1228 }
1229 _ => return Err(ProgramError::InvalidAccountData),
1230 };
1231
1232 if lamports > source_free_lamports {
1233 return Err(ProgramError::InvalidArgument);
1234 }
1235
1236 relocate_lamports(
1237 source_stake_account_info,
1238 destination_stake_account_info,
1239 lamports,
1240 )?;
1241
1242 Ok(())
1243 }
1244
1245 pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
1247 if *program_id != id() {
1249 return Err(ProgramError::IncorrectProgramId);
1250 }
1251
1252 let epoch_rewards_active = EpochRewards::get()
1253 .map(|epoch_rewards| epoch_rewards.active)
1254 .unwrap_or(false);
1255
1256 let instruction =
1257 bincode::deserialize(data).map_err(|_| ProgramError::InvalidInstructionData)?;
1258
1259 if epoch_rewards_active && !matches!(instruction, StakeInstruction::GetMinimumDelegation) {
1260 return Err(StakeError::EpochRewardsActive.into());
1261 }
1262
1263 match instruction {
1264 StakeInstruction::Initialize(authorize, lockup) => {
1265 msg!("Instruction: Initialize");
1266 Self::process_initialize(accounts, authorize, lockup)
1267 }
1268 StakeInstruction::Authorize(new_authority, authority_type) => {
1269 msg!("Instruction: Authorize");
1270 Self::process_authorize(accounts, new_authority, authority_type)
1271 }
1272 StakeInstruction::DelegateStake => {
1273 msg!("Instruction: DelegateStake");
1274 Self::process_delegate(accounts)
1275 }
1276 StakeInstruction::Split(lamports) => {
1277 msg!("Instruction: Split");
1278 Self::process_split(accounts, lamports)
1279 }
1280 StakeInstruction::Withdraw(lamports) => {
1281 msg!("Instruction: Withdraw");
1282 Self::process_withdraw(accounts, lamports)
1283 }
1284 StakeInstruction::Deactivate => {
1285 msg!("Instruction: Deactivate");
1286 Self::process_deactivate(accounts)
1287 }
1288 StakeInstruction::SetLockup(lockup) => {
1289 msg!("Instruction: SetLockup");
1290 Self::process_set_lockup(accounts, lockup)
1291 }
1292 StakeInstruction::Merge => {
1293 msg!("Instruction: Merge");
1294 Self::process_merge(accounts)
1295 }
1296 StakeInstruction::AuthorizeWithSeed(args) => {
1297 msg!("Instruction: AuthorizeWithSeed");
1298 Self::process_authorize_with_seed(accounts, args)
1299 }
1300 StakeInstruction::InitializeChecked => {
1301 msg!("Instruction: InitializeChecked");
1302 Self::process_initialize_checked(accounts)
1303 }
1304 StakeInstruction::AuthorizeChecked(authority_type) => {
1305 msg!("Instruction: AuthorizeChecked");
1306 Self::process_authorize_checked(accounts, authority_type)
1307 }
1308 StakeInstruction::AuthorizeCheckedWithSeed(args) => {
1309 msg!("Instruction: AuthorizeCheckedWithSeed");
1310 Self::process_authorize_checked_with_seed(accounts, args)
1311 }
1312 StakeInstruction::SetLockupChecked(lockup_checked) => {
1313 msg!("Instruction: SetLockupChecked");
1314 Self::process_set_lockup_checked(accounts, lockup_checked)
1315 }
1316 StakeInstruction::GetMinimumDelegation => {
1317 msg!("Instruction: GetMinimumDelegation");
1318 let minimum_delegation = crate::get_minimum_delegation();
1319 set_return_data(&minimum_delegation.to_le_bytes());
1320 Ok(())
1321 }
1322 StakeInstruction::DeactivateDelinquent => {
1323 msg!("Instruction: DeactivateDelinquent");
1324 Self::process_deactivate_delinquent(accounts)
1325 }
1326 #[allow(deprecated)]
1327 StakeInstruction::Redelegate => Err(ProgramError::InvalidInstructionData),
1328 StakeInstruction::MoveStake(lamports) => {
1331 msg!("Instruction: MoveStake");
1332 Self::process_move_stake(accounts, lamports)
1333 }
1334 StakeInstruction::MoveLamports(lamports) => {
1335 msg!("Instruction: MoveLamports");
1336 Self::process_move_lamports(accounts, lamports)
1337 }
1338 }
1339 }
1340}