1use {
2 crate::{helpers::*, id, PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH},
3 clone_solana_program::{
4 account_info::{next_account_info, AccountInfo},
5 clock::Clock,
6 entrypoint::ProgramResult,
7 msg,
8 program::set_return_data,
9 program_error::ProgramError,
10 pubkey::Pubkey,
11 rent::Rent,
12 stake::{
13 instruction::{
14 AuthorizeCheckedWithSeedArgs, AuthorizeWithSeedArgs, LockupArgs, LockupCheckedArgs,
15 StakeError, StakeInstruction,
16 },
17 stake_flags::StakeFlags,
18 state::{Authorized, Lockup, Meta, StakeAuthorize, StakeStateV2},
19 tools::{acceptable_reference_epoch_credits, eligible_for_deactivate_delinquent},
20 },
21 sysvar::{epoch_rewards::EpochRewards, stake_history::StakeHistorySysvar, Sysvar},
22 vote::{program as clone_solana_vote_program, state::VoteState},
23 },
24 std::{collections::HashSet, mem::MaybeUninit},
25};
26
27fn get_vote_state(vote_account_info: &AccountInfo) -> Result<Box<VoteState>, ProgramError> {
28 if *vote_account_info.owner != clone_solana_vote_program::id() {
29 return Err(ProgramError::IncorrectProgramId);
30 }
31
32 let mut vote_state = Box::new(MaybeUninit::uninit());
33 VoteState::deserialize_into_uninit(&vote_account_info.try_borrow_data()?, vote_state.as_mut())
34 .map_err(|_| ProgramError::InvalidAccountData)?;
35 let vote_state = unsafe { Box::from_raw(Box::into_raw(vote_state) as *mut VoteState) };
36
37 Ok(vote_state)
38}
39
40fn get_stake_state(stake_account_info: &AccountInfo) -> Result<StakeStateV2, ProgramError> {
41 if *stake_account_info.owner != id() {
42 return Err(ProgramError::InvalidAccountOwner);
43 }
44
45 stake_account_info
46 .deserialize_data()
47 .map_err(|_| ProgramError::InvalidAccountData)
48}
49
50fn set_stake_state(stake_account_info: &AccountInfo, new_state: &StakeStateV2) -> ProgramResult {
51 let serialized_size =
52 bincode::serialized_size(new_state).map_err(|_| ProgramError::InvalidAccountData)?;
53 if serialized_size > stake_account_info.data_len() as u64 {
54 return Err(ProgramError::AccountDataTooSmall);
55 }
56
57 bincode::serialize_into(&mut stake_account_info.data.borrow_mut()[..], new_state)
58 .map_err(|_| ProgramError::InvalidAccountData)
59}
60
61fn relocate_lamports(
63 source_account_info: &AccountInfo,
64 destination_account_info: &AccountInfo,
65 lamports: u64,
66) -> ProgramResult {
67 {
68 let mut source_lamports = source_account_info.try_borrow_mut_lamports()?;
69 **source_lamports = source_lamports
70 .checked_sub(lamports)
71 .ok_or(ProgramError::InsufficientFunds)?;
72 }
73
74 {
75 let mut destination_lamports = destination_account_info.try_borrow_mut_lamports()?;
76 **destination_lamports = destination_lamports
77 .checked_add(lamports)
78 .ok_or(ProgramError::ArithmeticOverflow)?;
79 }
80
81 Ok(())
82}
83
84fn collect_signers(accounts: &[AccountInfo]) -> HashSet<Pubkey> {
90 let mut signers = HashSet::new();
91
92 for account in accounts {
93 if account.is_signer {
94 signers.insert(*account.key);
95 }
96 }
97
98 signers
99}
100
101fn collect_signers_checked<'a>(
103 authority_info: Option<&'a AccountInfo>,
104 custodian_info: Option<&'a AccountInfo>,
105) -> Result<(HashSet<Pubkey>, Option<&'a Pubkey>), ProgramError> {
106 let mut signers = HashSet::new();
107
108 if let Some(authority_info) = authority_info {
109 if authority_info.is_signer {
110 signers.insert(*authority_info.key);
111 } else {
112 return Err(ProgramError::MissingRequiredSignature);
113 }
114 }
115
116 let custodian = if let Some(custodian_info) = custodian_info {
117 if custodian_info.is_signer {
118 signers.insert(*custodian_info.key);
119 Some(custodian_info.key)
120 } else {
121 return Err(ProgramError::MissingRequiredSignature);
122 }
123 } else {
124 None
125 };
126
127 Ok((signers, custodian))
128}
129
130fn do_initialize(
131 stake_account_info: &AccountInfo,
132 authorized: Authorized,
133 lockup: Lockup,
134 rent: &Rent,
135) -> ProgramResult {
136 if stake_account_info.data_len() != StakeStateV2::size_of() {
137 return Err(ProgramError::InvalidAccountData);
138 }
139
140 if let StakeStateV2::Uninitialized = get_stake_state(stake_account_info)? {
141 let rent_exempt_reserve = rent.minimum_balance(stake_account_info.data_len());
142 if stake_account_info.lamports() >= rent_exempt_reserve {
143 let stake_state = StakeStateV2::Initialized(Meta {
144 rent_exempt_reserve,
145 authorized,
146 lockup,
147 });
148
149 set_stake_state(stake_account_info, &stake_state)
150 } else {
151 Err(ProgramError::InsufficientFunds)
152 }
153 } else {
154 Err(ProgramError::InvalidAccountData)
155 }
156}
157
158fn do_authorize(
159 stake_account_info: &AccountInfo,
160 signers: &HashSet<Pubkey>,
161 new_authority: &Pubkey,
162 authority_type: StakeAuthorize,
163 custodian: Option<&Pubkey>,
164 clock: &Clock,
165) -> ProgramResult {
166 match get_stake_state(stake_account_info)? {
167 StakeStateV2::Initialized(mut meta) => {
168 meta.authorized
169 .authorize(
170 signers,
171 new_authority,
172 authority_type,
173 Some((&meta.lockup, clock, custodian)),
174 )
175 .map_err(to_program_error)?;
176
177 set_stake_state(stake_account_info, &StakeStateV2::Initialized(meta))
178 }
179 StakeStateV2::Stake(mut meta, stake, stake_flags) => {
180 meta.authorized
181 .authorize(
182 signers,
183 new_authority,
184 authority_type,
185 Some((&meta.lockup, clock, custodian)),
186 )
187 .map_err(to_program_error)?;
188
189 set_stake_state(
190 stake_account_info,
191 &StakeStateV2::Stake(meta, stake, stake_flags),
192 )
193 }
194 _ => Err(ProgramError::InvalidAccountData),
195 }
196}
197
198fn do_set_lockup(
199 stake_account_info: &AccountInfo,
200 signers: &HashSet<Pubkey>,
201 lockup: &LockupArgs,
202 clock: &Clock,
203) -> ProgramResult {
204 match get_stake_state(stake_account_info)? {
205 StakeStateV2::Initialized(mut meta) => {
206 meta.set_lockup(lockup, signers, clock)
207 .map_err(to_program_error)?;
208
209 set_stake_state(stake_account_info, &StakeStateV2::Initialized(meta))
210 }
211 StakeStateV2::Stake(mut meta, stake, stake_flags) => {
212 meta.set_lockup(lockup, signers, clock)
213 .map_err(to_program_error)?;
214
215 set_stake_state(
216 stake_account_info,
217 &StakeStateV2::Stake(meta, stake, stake_flags),
218 )
219 }
220 _ => Err(ProgramError::InvalidAccountData),
221 }
222}
223
224fn move_stake_or_lamports_shared_checks(
225 source_stake_account_info: &AccountInfo,
226 lamports: u64,
227 destination_stake_account_info: &AccountInfo,
228 stake_authority_info: &AccountInfo,
229) -> Result<(MergeKind, MergeKind), ProgramError> {
230 let (signers, _) = collect_signers_checked(Some(stake_authority_info), None)?;
232
233 if *source_stake_account_info.key == *destination_stake_account_info.key {
235 return Err(ProgramError::InvalidInstructionData);
236 }
237
238 if !source_stake_account_info.is_writable || !destination_stake_account_info.is_writable {
242 return Err(ProgramError::InvalidInstructionData);
243 }
244
245 if lamports == 0 {
247 return Err(ProgramError::InvalidArgument);
248 }
249
250 let clock = Clock::get()?;
251 let stake_history = StakeHistorySysvar(clock.epoch);
252
253 let source_merge_kind = MergeKind::get_if_mergeable(
256 &get_stake_state(source_stake_account_info)?,
257 source_stake_account_info.lamports(),
258 &clock,
259 &stake_history,
260 )?;
261
262 source_merge_kind
264 .meta()
265 .authorized
266 .check(&signers, StakeAuthorize::Staker)
267 .map_err(to_program_error)?;
268
269 let destination_merge_kind = MergeKind::get_if_mergeable(
271 &get_stake_state(destination_stake_account_info)?,
272 destination_stake_account_info.lamports(),
273 &clock,
274 &stake_history,
275 )?;
276
277 MergeKind::metas_can_merge(
279 source_merge_kind.meta(),
280 destination_merge_kind.meta(),
281 &clock,
282 )?;
283
284 Ok((source_merge_kind, destination_merge_kind))
285}
286
287pub struct Processor {}
308impl Processor {
309 fn process_initialize(
310 accounts: &[AccountInfo],
311 authorized: Authorized,
312 lockup: Lockup,
313 ) -> ProgramResult {
314 let account_info_iter = &mut accounts.iter();
315
316 let stake_account_info = next_account_info(account_info_iter)?;
318 let rent_info = next_account_info(account_info_iter)?;
319
320 let rent = &Rent::from_account_info(rent_info)?;
321
322 do_initialize(stake_account_info, authorized, lockup, rent)?;
324
325 Ok(())
326 }
327
328 fn process_authorize(
329 accounts: &[AccountInfo],
330 new_authority: Pubkey,
331 authority_type: StakeAuthorize,
332 ) -> ProgramResult {
333 let signers = collect_signers(accounts);
334 let account_info_iter = &mut accounts.iter();
335
336 let stake_account_info = next_account_info(account_info_iter)?;
338 let clock_info = next_account_info(account_info_iter)?;
339 let _stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
340
341 let option_lockup_authority_info = next_account_info(account_info_iter).ok();
343
344 let clock = &Clock::from_account_info(clock_info)?;
345
346 let custodian = option_lockup_authority_info
347 .filter(|a| a.is_signer)
348 .map(|a| a.key);
349
350 do_authorize(
352 stake_account_info,
353 &signers,
354 &new_authority,
355 authority_type,
356 custodian,
357 clock,
358 )?;
359
360 Ok(())
361 }
362
363 fn process_delegate(accounts: &[AccountInfo]) -> ProgramResult {
364 let signers = collect_signers(accounts);
365 let account_info_iter = &mut accounts.iter();
366
367 let stake_account_info = next_account_info(account_info_iter)?;
369 let vote_account_info = next_account_info(account_info_iter)?;
370 let clock_info = next_account_info(account_info_iter)?;
371 let _stake_history_info = next_account_info(account_info_iter)?;
372 let _stake_config_info = next_account_info(account_info_iter)?;
373
374 let clock = &Clock::from_account_info(clock_info)?;
378 let stake_history = &StakeHistorySysvar(clock.epoch);
379
380 let vote_state = get_vote_state(vote_account_info)?;
381
382 match get_stake_state(stake_account_info)? {
383 StakeStateV2::Initialized(meta) => {
384 meta.authorized
385 .check(&signers, StakeAuthorize::Staker)
386 .map_err(to_program_error)?;
387
388 let ValidatedDelegatedInfo { stake_amount } =
389 validate_delegated_amount(stake_account_info, &meta)?;
390
391 let stake = new_stake(
392 stake_amount,
393 vote_account_info.key,
394 &vote_state,
395 clock.epoch,
396 );
397
398 set_stake_state(
399 stake_account_info,
400 &StakeStateV2::Stake(meta, stake, StakeFlags::empty()),
401 )
402 }
403 StakeStateV2::Stake(meta, mut stake, flags) => {
404 meta.authorized
405 .check(&signers, StakeAuthorize::Staker)
406 .map_err(to_program_error)?;
407
408 let ValidatedDelegatedInfo { stake_amount } =
409 validate_delegated_amount(stake_account_info, &meta)?;
410
411 redelegate_stake(
412 &mut stake,
413 stake_amount,
414 vote_account_info.key,
415 &vote_state,
416 clock.epoch,
417 stake_history,
418 )?;
419
420 set_stake_state(stake_account_info, &StakeStateV2::Stake(meta, stake, flags))
421 }
422 _ => Err(ProgramError::InvalidAccountData),
423 }?;
424
425 Ok(())
426 }
427
428 fn process_split(accounts: &[AccountInfo], split_lamports: u64) -> ProgramResult {
429 let signers = collect_signers(accounts);
430 let account_info_iter = &mut accounts.iter();
431
432 let source_stake_account_info = next_account_info(account_info_iter)?;
434 let destination_stake_account_info = next_account_info(account_info_iter)?;
435
436 let clock = Clock::get()?;
440 let stake_history = &StakeHistorySysvar(clock.epoch);
441
442 let destination_data_len = destination_stake_account_info.data_len();
443 if destination_data_len != StakeStateV2::size_of() {
444 return Err(ProgramError::InvalidAccountData);
445 }
446
447 if let StakeStateV2::Uninitialized = get_stake_state(destination_stake_account_info)? {
448 } else {
450 return Err(ProgramError::InvalidAccountData);
451 }
452
453 let source_lamport_balance = source_stake_account_info.lamports();
454 let destination_lamport_balance = destination_stake_account_info.lamports();
455
456 if split_lamports > source_lamport_balance {
457 return Err(ProgramError::InsufficientFunds);
458 }
459
460 match get_stake_state(source_stake_account_info)? {
461 StakeStateV2::Stake(source_meta, mut source_stake, stake_flags) => {
462 source_meta
463 .authorized
464 .check(&signers, StakeAuthorize::Staker)
465 .map_err(to_program_error)?;
466
467 let minimum_delegation = crate::get_minimum_delegation();
468
469 let status = source_stake.delegation.stake_activating_and_deactivating(
470 clock.epoch,
471 stake_history,
472 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
473 );
474
475 let is_active = status.effective > 0;
476
477 let validated_split_info = validate_split_amount(
479 source_lamport_balance,
480 destination_lamport_balance,
481 split_lamports,
482 &source_meta,
483 destination_data_len,
484 minimum_delegation,
485 is_active,
486 )?;
487
488 let (remaining_stake_delta, split_stake_amount) =
494 if validated_split_info.source_remaining_balance == 0 {
495 let remaining_stake_delta =
506 split_lamports.saturating_sub(source_meta.rent_exempt_reserve);
507 (remaining_stake_delta, remaining_stake_delta)
508 } else {
509 if source_stake.delegation.stake.saturating_sub(split_lamports)
513 < minimum_delegation
514 {
515 return Err(StakeError::InsufficientDelegation.into());
516 }
517
518 (
519 split_lamports,
520 split_lamports.saturating_sub(
521 validated_split_info
522 .destination_rent_exempt_reserve
523 .saturating_sub(destination_lamport_balance),
524 ),
525 )
526 };
527
528 if split_stake_amount < minimum_delegation {
529 return Err(StakeError::InsufficientDelegation.into());
530 }
531
532 let destination_stake =
533 source_stake.split(remaining_stake_delta, split_stake_amount)?;
534
535 let mut destination_meta = source_meta;
536 destination_meta.rent_exempt_reserve =
537 validated_split_info.destination_rent_exempt_reserve;
538
539 set_stake_state(
540 source_stake_account_info,
541 &StakeStateV2::Stake(source_meta, source_stake, stake_flags),
542 )?;
543
544 set_stake_state(
545 destination_stake_account_info,
546 &StakeStateV2::Stake(destination_meta, destination_stake, stake_flags),
547 )?;
548 }
549 StakeStateV2::Initialized(source_meta) => {
550 source_meta
551 .authorized
552 .check(&signers, StakeAuthorize::Staker)
553 .map_err(to_program_error)?;
554
555 let validated_split_info = validate_split_amount(
557 source_lamport_balance,
558 destination_lamport_balance,
559 split_lamports,
560 &source_meta,
561 destination_data_len,
562 0, false, )?;
565
566 let mut destination_meta = source_meta;
567 destination_meta.rent_exempt_reserve =
568 validated_split_info.destination_rent_exempt_reserve;
569
570 set_stake_state(
571 destination_stake_account_info,
572 &StakeStateV2::Initialized(destination_meta),
573 )?;
574 }
575 StakeStateV2::Uninitialized => {
576 if !source_stake_account_info.is_signer {
577 return Err(ProgramError::MissingRequiredSignature);
578 }
579 }
580 _ => return Err(ProgramError::InvalidAccountData),
581 }
582
583 if split_lamports == source_lamport_balance {
585 set_stake_state(source_stake_account_info, &StakeStateV2::Uninitialized)?;
586 }
587
588 relocate_lamports(
589 source_stake_account_info,
590 destination_stake_account_info,
591 split_lamports,
592 )?;
593
594 Ok(())
595 }
596
597 fn process_withdraw(accounts: &[AccountInfo], withdraw_lamports: u64) -> ProgramResult {
598 let account_info_iter = &mut accounts.iter();
599
600 let source_stake_account_info = next_account_info(account_info_iter)?;
602 let destination_info = next_account_info(account_info_iter)?;
603 let clock_info = next_account_info(account_info_iter)?;
604 let _stake_history_info = next_account_info(account_info_iter)?;
605 let withdraw_authority_info = next_account_info(account_info_iter)?;
606
607 let option_lockup_authority_info = next_account_info(account_info_iter).ok();
609
610 let clock = &Clock::from_account_info(clock_info)?;
611 let stake_history = &StakeHistorySysvar(clock.epoch);
612
613 let (signers, custodian) =
616 collect_signers_checked(Some(withdraw_authority_info), option_lockup_authority_info)?;
617
618 let (lockup, reserve, is_staked) = match get_stake_state(source_stake_account_info)? {
619 StakeStateV2::Stake(meta, stake, _stake_flag) => {
620 meta.authorized
621 .check(&signers, StakeAuthorize::Withdrawer)
622 .map_err(to_program_error)?;
623 let staked = if clock.epoch >= stake.delegation.deactivation_epoch {
625 stake.delegation.stake(
626 clock.epoch,
627 stake_history,
628 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
629 )
630 } else {
631 stake.delegation.stake
635 };
636
637 let staked_and_reserve = checked_add(staked, meta.rent_exempt_reserve)?;
638 (meta.lockup, staked_and_reserve, staked != 0)
639 }
640 StakeStateV2::Initialized(meta) => {
641 meta.authorized
642 .check(&signers, StakeAuthorize::Withdrawer)
643 .map_err(to_program_error)?;
644 (meta.lockup, meta.rent_exempt_reserve, false)
646 }
647 StakeStateV2::Uninitialized => {
648 if !signers.contains(source_stake_account_info.key) {
649 return Err(ProgramError::MissingRequiredSignature);
650 }
651 (Lockup::default(), 0, false) }
653 _ => return Err(ProgramError::InvalidAccountData),
654 };
655
656 if lockup.is_in_force(clock, custodian) {
659 return Err(StakeError::LockupInForce.into());
660 }
661
662 let stake_account_lamports = source_stake_account_info.lamports();
663 if withdraw_lamports == stake_account_lamports {
664 if is_staked {
666 return Err(ProgramError::InsufficientFunds);
667 }
668
669 set_stake_state(source_stake_account_info, &StakeStateV2::Uninitialized)?;
671 } else {
672 let withdraw_lamports_and_reserve = checked_add(withdraw_lamports, reserve)?;
674 if withdraw_lamports_and_reserve > stake_account_lamports {
675 return Err(ProgramError::InsufficientFunds);
676 }
677 }
678
679 relocate_lamports(
680 source_stake_account_info,
681 destination_info,
682 withdraw_lamports,
683 )?;
684
685 Ok(())
686 }
687
688 fn process_deactivate(accounts: &[AccountInfo]) -> ProgramResult {
689 let signers = collect_signers(accounts);
690 let account_info_iter = &mut accounts.iter();
691
692 let stake_account_info = next_account_info(account_info_iter)?;
694 let clock_info = next_account_info(account_info_iter)?;
695
696 let clock = &Clock::from_account_info(clock_info)?;
700
701 match get_stake_state(stake_account_info)? {
702 StakeStateV2::Stake(meta, mut stake, stake_flags) => {
703 meta.authorized
704 .check(&signers, StakeAuthorize::Staker)
705 .map_err(to_program_error)?;
706
707 stake.deactivate(clock.epoch)?;
708
709 set_stake_state(
710 stake_account_info,
711 &StakeStateV2::Stake(meta, stake, stake_flags),
712 )
713 }
714 _ => Err(ProgramError::InvalidAccountData),
715 }?;
716
717 Ok(())
718 }
719
720 fn process_set_lockup(accounts: &[AccountInfo], lockup: LockupArgs) -> ProgramResult {
721 let signers = collect_signers(accounts);
722 let account_info_iter = &mut accounts.iter();
723
724 let stake_account_info = next_account_info(account_info_iter)?;
726
727 let clock = Clock::get()?;
731
732 do_set_lockup(stake_account_info, &signers, &lockup, &clock)?;
734
735 Ok(())
736 }
737
738 fn process_merge(accounts: &[AccountInfo]) -> ProgramResult {
739 let signers = collect_signers(accounts);
740 let account_info_iter = &mut accounts.iter();
741
742 let destination_stake_account_info = next_account_info(account_info_iter)?;
744 let source_stake_account_info = next_account_info(account_info_iter)?;
745 let clock_info = next_account_info(account_info_iter)?;
746 let _stake_history_info = next_account_info(account_info_iter)?;
747
748 let clock = &Clock::from_account_info(clock_info)?;
752 let stake_history = &StakeHistorySysvar(clock.epoch);
753
754 if source_stake_account_info.key == destination_stake_account_info.key {
755 return Err(ProgramError::InvalidArgument);
756 }
757
758 msg!("Checking if destination stake is mergeable");
759 let destination_merge_kind = MergeKind::get_if_mergeable(
760 &get_stake_state(destination_stake_account_info)?,
761 destination_stake_account_info.lamports(),
762 clock,
763 stake_history,
764 )?;
765
766 destination_merge_kind
768 .meta()
769 .authorized
770 .check(&signers, StakeAuthorize::Staker)
771 .map_err(|_| ProgramError::MissingRequiredSignature)?;
772
773 msg!("Checking if source stake is mergeable");
774 let source_merge_kind = MergeKind::get_if_mergeable(
775 &get_stake_state(source_stake_account_info)?,
776 source_stake_account_info.lamports(),
777 clock,
778 stake_history,
779 )?;
780
781 msg!("Merging stake accounts");
782 if let Some(merged_state) = destination_merge_kind.merge(source_merge_kind, clock)? {
783 set_stake_state(destination_stake_account_info, &merged_state)?;
784 }
785
786 set_stake_state(source_stake_account_info, &StakeStateV2::Uninitialized)?;
788
789 relocate_lamports(
791 source_stake_account_info,
792 destination_stake_account_info,
793 source_stake_account_info.lamports(),
794 )?;
795
796 Ok(())
797 }
798
799 fn process_authorize_with_seed(
800 accounts: &[AccountInfo],
801 authorize_args: AuthorizeWithSeedArgs,
802 ) -> ProgramResult {
803 let account_info_iter = &mut accounts.iter();
804
805 let stake_account_info = next_account_info(account_info_iter)?;
807 let stake_or_withdraw_authority_base_info = next_account_info(account_info_iter)?;
808 let clock_info = next_account_info(account_info_iter)?;
809
810 let option_lockup_authority_info = next_account_info(account_info_iter).ok();
812
813 let clock = &Clock::from_account_info(clock_info)?;
814
815 let (mut signers, custodian) = collect_signers_checked(None, option_lockup_authority_info)?;
816
817 if stake_or_withdraw_authority_base_info.is_signer {
818 signers.insert(Pubkey::create_with_seed(
819 stake_or_withdraw_authority_base_info.key,
820 &authorize_args.authority_seed,
821 &authorize_args.authority_owner,
822 )?);
823 }
824
825 do_authorize(
827 stake_account_info,
828 &signers,
829 &authorize_args.new_authorized_pubkey,
830 authorize_args.stake_authorize,
831 custodian,
832 clock,
833 )?;
834
835 Ok(())
836 }
837
838 fn process_initialize_checked(accounts: &[AccountInfo]) -> ProgramResult {
839 let account_info_iter = &mut accounts.iter();
840
841 let stake_account_info = next_account_info(account_info_iter)?;
843 let rent_info = next_account_info(account_info_iter)?;
844 let stake_authority_info = next_account_info(account_info_iter)?;
845 let withdraw_authority_info = next_account_info(account_info_iter)?;
846
847 let rent = &Rent::from_account_info(rent_info)?;
848
849 if !withdraw_authority_info.is_signer {
850 return Err(ProgramError::MissingRequiredSignature);
851 }
852
853 let authorized = Authorized {
854 staker: *stake_authority_info.key,
855 withdrawer: *withdraw_authority_info.key,
856 };
857
858 do_initialize(stake_account_info, authorized, Lockup::default(), rent)?;
860
861 Ok(())
862 }
863
864 fn process_authorize_checked(
865 accounts: &[AccountInfo],
866 authority_type: StakeAuthorize,
867 ) -> ProgramResult {
868 let signers = collect_signers(accounts);
869 let account_info_iter = &mut accounts.iter();
870
871 let stake_account_info = next_account_info(account_info_iter)?;
873 let clock_info = next_account_info(account_info_iter)?;
874 let _old_stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
875 let new_stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
876
877 let option_lockup_authority_info = next_account_info(account_info_iter).ok();
879
880 let clock = &Clock::from_account_info(clock_info)?;
881
882 if !new_stake_or_withdraw_authority_info.is_signer {
883 return Err(ProgramError::MissingRequiredSignature);
884 }
885
886 let custodian = option_lockup_authority_info
887 .filter(|a| a.is_signer)
888 .map(|a| a.key);
889
890 do_authorize(
892 stake_account_info,
893 &signers,
894 new_stake_or_withdraw_authority_info.key,
895 authority_type,
896 custodian,
897 clock,
898 )?;
899
900 Ok(())
901 }
902
903 fn process_authorize_checked_with_seed(
904 accounts: &[AccountInfo],
905 authorize_args: AuthorizeCheckedWithSeedArgs,
906 ) -> ProgramResult {
907 let account_info_iter = &mut accounts.iter();
908
909 let stake_account_info = next_account_info(account_info_iter)?;
911 let old_stake_or_withdraw_authority_base_info = next_account_info(account_info_iter)?;
912 let clock_info = next_account_info(account_info_iter)?;
913 let new_stake_or_withdraw_authority_info = next_account_info(account_info_iter)?;
914
915 let option_lockup_authority_info = next_account_info(account_info_iter).ok();
917
918 let clock = &Clock::from_account_info(clock_info)?;
919
920 let (mut signers, custodian) = collect_signers_checked(
921 Some(new_stake_or_withdraw_authority_info),
922 option_lockup_authority_info,
923 )?;
924
925 if old_stake_or_withdraw_authority_base_info.is_signer {
926 signers.insert(Pubkey::create_with_seed(
927 old_stake_or_withdraw_authority_base_info.key,
928 &authorize_args.authority_seed,
929 &authorize_args.authority_owner,
930 )?);
931 }
932
933 do_authorize(
935 stake_account_info,
936 &signers,
937 new_stake_or_withdraw_authority_info.key,
938 authorize_args.stake_authorize,
939 custodian,
940 clock,
941 )?;
942
943 Ok(())
944 }
945
946 fn process_set_lockup_checked(
947 accounts: &[AccountInfo],
948 lockup_checked: LockupCheckedArgs,
949 ) -> ProgramResult {
950 let signers = collect_signers(accounts);
951 let account_info_iter = &mut accounts.iter();
952
953 let stake_account_info = next_account_info(account_info_iter)?;
955
956 let _old_withdraw_or_lockup_authority_info = next_account_info(account_info_iter);
958 let option_new_lockup_authority_info = next_account_info(account_info_iter).ok();
959
960 let clock = Clock::get()?;
961
962 let custodian = match option_new_lockup_authority_info {
963 Some(new_lockup_authority_info) if new_lockup_authority_info.is_signer => {
964 Some(new_lockup_authority_info.key)
965 }
966 Some(_) => return Err(ProgramError::MissingRequiredSignature),
967 None => None,
968 };
969
970 let lockup = LockupArgs {
971 unix_timestamp: lockup_checked.unix_timestamp,
972 epoch: lockup_checked.epoch,
973 custodian: custodian.copied(),
974 };
975
976 do_set_lockup(stake_account_info, &signers, &lockup, &clock)?;
978
979 Ok(())
980 }
981
982 fn process_deactivate_delinquent(accounts: &[AccountInfo]) -> ProgramResult {
983 let account_info_iter = &mut accounts.iter();
984
985 let stake_account_info = next_account_info(account_info_iter)?;
987 let delinquent_vote_account_info = next_account_info(account_info_iter)?;
988 let reference_vote_account_info = next_account_info(account_info_iter)?;
989
990 let clock = Clock::get()?;
991
992 let delinquent_vote_state = get_vote_state(delinquent_vote_account_info)?;
993 let reference_vote_state = get_vote_state(reference_vote_account_info)?;
994
995 if !acceptable_reference_epoch_credits(&reference_vote_state.epoch_credits, clock.epoch) {
996 return Err(StakeError::InsufficientReferenceVotes.into());
997 }
998
999 if let StakeStateV2::Stake(meta, mut stake, stake_flags) =
1000 get_stake_state(stake_account_info)?
1001 {
1002 if stake.delegation.voter_pubkey != *delinquent_vote_account_info.key {
1003 return Err(StakeError::VoteAddressMismatch.into());
1004 }
1005
1006 if eligible_for_deactivate_delinquent(&delinquent_vote_state.epoch_credits, clock.epoch)
1010 {
1011 stake.deactivate(clock.epoch)?;
1012
1013 set_stake_state(
1014 stake_account_info,
1015 &StakeStateV2::Stake(meta, stake, stake_flags),
1016 )
1017 } else {
1018 Err(StakeError::MinimumDelinquentEpochsForDeactivationNotMet.into())
1019 }
1020 } else {
1021 Err(ProgramError::InvalidAccountData)
1022 }?;
1023
1024 Ok(())
1025 }
1026
1027 fn process_move_stake(accounts: &[AccountInfo], lamports: u64) -> ProgramResult {
1028 let account_info_iter = &mut accounts.iter();
1029
1030 let source_stake_account_info = next_account_info(account_info_iter)?;
1032 let destination_stake_account_info = next_account_info(account_info_iter)?;
1033 let stake_authority_info = next_account_info(account_info_iter)?;
1034
1035 let (source_merge_kind, destination_merge_kind) = move_stake_or_lamports_shared_checks(
1036 source_stake_account_info,
1037 lamports,
1038 destination_stake_account_info,
1039 stake_authority_info,
1040 )?;
1041
1042 if source_stake_account_info.data_len() != StakeStateV2::size_of()
1046 || destination_stake_account_info.data_len() != StakeStateV2::size_of()
1047 {
1048 return Err(ProgramError::InvalidAccountData);
1049 }
1050
1051 let MergeKind::FullyActive(source_meta, mut source_stake) = source_merge_kind else {
1053 return Err(ProgramError::InvalidAccountData);
1054 };
1055
1056 let minimum_delegation = crate::get_minimum_delegation();
1057 let source_effective_stake = source_stake.delegation.stake;
1058
1059 let source_final_stake = source_effective_stake
1062 .checked_sub(lamports)
1063 .ok_or(ProgramError::InvalidArgument)?;
1064
1065 if source_final_stake != 0 && source_final_stake < minimum_delegation {
1068 return Err(ProgramError::InvalidArgument);
1069 }
1070
1071 let destination_meta = match destination_merge_kind {
1073 MergeKind::FullyActive(destination_meta, mut destination_stake) => {
1074 if source_stake.delegation.voter_pubkey != destination_stake.delegation.voter_pubkey
1076 {
1077 return Err(StakeError::VoteAddressMismatch.into());
1078 }
1079
1080 let destination_effective_stake = destination_stake.delegation.stake;
1081 let destination_final_stake = destination_effective_stake
1082 .checked_add(lamports)
1083 .ok_or(ProgramError::ArithmeticOverflow)?;
1084
1085 if destination_final_stake < minimum_delegation {
1088 return Err(ProgramError::InvalidArgument);
1089 }
1090
1091 merge_delegation_stake_and_credits_observed(
1092 &mut destination_stake,
1093 lamports,
1094 source_stake.credits_observed,
1095 )?;
1096
1097 set_stake_state(
1101 destination_stake_account_info,
1102 &StakeStateV2::Stake(destination_meta, destination_stake, StakeFlags::empty()),
1103 )?;
1104
1105 destination_meta
1106 }
1107 MergeKind::Inactive(destination_meta, _, _) => {
1108 if lamports < minimum_delegation {
1110 return Err(ProgramError::InvalidArgument);
1111 }
1112
1113 let mut destination_stake = source_stake;
1114 destination_stake.delegation.stake = lamports;
1115
1116 set_stake_state(
1120 destination_stake_account_info,
1121 &StakeStateV2::Stake(destination_meta, destination_stake, StakeFlags::empty()),
1122 )?;
1123
1124 destination_meta
1125 }
1126 _ => return Err(ProgramError::InvalidAccountData),
1127 };
1128
1129 if source_final_stake == 0 {
1130 set_stake_state(
1131 source_stake_account_info,
1132 &StakeStateV2::Initialized(source_meta),
1133 )?;
1134 } else {
1135 source_stake.delegation.stake = source_final_stake;
1136
1137 set_stake_state(
1141 source_stake_account_info,
1142 &StakeStateV2::Stake(source_meta, source_stake, StakeFlags::empty()),
1143 )?;
1144 }
1145
1146 relocate_lamports(
1147 source_stake_account_info,
1148 destination_stake_account_info,
1149 lamports,
1150 )?;
1151
1152 if source_stake_account_info.lamports() < source_meta.rent_exempt_reserve
1155 || destination_stake_account_info.lamports() < destination_meta.rent_exempt_reserve
1156 {
1157 msg!("Delegation calculations violated lamport balance assumptions");
1158 return Err(ProgramError::InvalidArgument);
1159 }
1160
1161 Ok(())
1162 }
1163
1164 fn process_move_lamports(accounts: &[AccountInfo], lamports: u64) -> ProgramResult {
1165 let account_info_iter = &mut accounts.iter();
1166
1167 let source_stake_account_info = next_account_info(account_info_iter)?;
1169 let destination_stake_account_info = next_account_info(account_info_iter)?;
1170 let stake_authority_info = next_account_info(account_info_iter)?;
1171
1172 let (source_merge_kind, _) = move_stake_or_lamports_shared_checks(
1173 source_stake_account_info,
1174 lamports,
1175 destination_stake_account_info,
1176 stake_authority_info,
1177 )?;
1178
1179 let source_free_lamports = match source_merge_kind {
1180 MergeKind::FullyActive(source_meta, source_stake) => source_stake_account_info
1181 .lamports()
1182 .saturating_sub(source_stake.delegation.stake)
1183 .saturating_sub(source_meta.rent_exempt_reserve),
1184 MergeKind::Inactive(source_meta, source_lamports, _) => {
1185 source_lamports.saturating_sub(source_meta.rent_exempt_reserve)
1186 }
1187 _ => return Err(ProgramError::InvalidAccountData),
1188 };
1189
1190 if lamports > source_free_lamports {
1191 return Err(ProgramError::InvalidArgument);
1192 }
1193
1194 relocate_lamports(
1195 source_stake_account_info,
1196 destination_stake_account_info,
1197 lamports,
1198 )?;
1199
1200 Ok(())
1201 }
1202
1203 pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
1205 if *program_id != id() {
1207 return Err(ProgramError::IncorrectProgramId);
1208 }
1209
1210 let epoch_rewards_active = EpochRewards::get()
1211 .map(|epoch_rewards| epoch_rewards.active)
1212 .unwrap_or(false);
1213
1214 let instruction =
1215 bincode::deserialize(data).map_err(|_| ProgramError::InvalidInstructionData)?;
1216
1217 if epoch_rewards_active && !matches!(instruction, StakeInstruction::GetMinimumDelegation) {
1218 return Err(StakeError::EpochRewardsActive.into());
1219 }
1220
1221 match instruction {
1222 StakeInstruction::Initialize(authorize, lockup) => {
1223 msg!("Instruction: Initialize");
1224 Self::process_initialize(accounts, authorize, lockup)
1225 }
1226 StakeInstruction::Authorize(new_authority, authority_type) => {
1227 msg!("Instruction: Authorize");
1228 Self::process_authorize(accounts, new_authority, authority_type)
1229 }
1230 StakeInstruction::DelegateStake => {
1231 msg!("Instruction: DelegateStake");
1232 Self::process_delegate(accounts)
1233 }
1234 StakeInstruction::Split(lamports) => {
1235 msg!("Instruction: Split");
1236 Self::process_split(accounts, lamports)
1237 }
1238 StakeInstruction::Withdraw(lamports) => {
1239 msg!("Instruction: Withdraw");
1240 Self::process_withdraw(accounts, lamports)
1241 }
1242 StakeInstruction::Deactivate => {
1243 msg!("Instruction: Deactivate");
1244 Self::process_deactivate(accounts)
1245 }
1246 StakeInstruction::SetLockup(lockup) => {
1247 msg!("Instruction: SetLockup");
1248 Self::process_set_lockup(accounts, lockup)
1249 }
1250 StakeInstruction::Merge => {
1251 msg!("Instruction: Merge");
1252 Self::process_merge(accounts)
1253 }
1254 StakeInstruction::AuthorizeWithSeed(args) => {
1255 msg!("Instruction: AuthorizeWithSeed");
1256 Self::process_authorize_with_seed(accounts, args)
1257 }
1258 StakeInstruction::InitializeChecked => {
1259 msg!("Instruction: InitializeChecked");
1260 Self::process_initialize_checked(accounts)
1261 }
1262 StakeInstruction::AuthorizeChecked(authority_type) => {
1263 msg!("Instruction: AuthorizeChecked");
1264 Self::process_authorize_checked(accounts, authority_type)
1265 }
1266 StakeInstruction::AuthorizeCheckedWithSeed(args) => {
1267 msg!("Instruction: AuthorizeCheckedWithSeed");
1268 Self::process_authorize_checked_with_seed(accounts, args)
1269 }
1270 StakeInstruction::SetLockupChecked(lockup_checked) => {
1271 msg!("Instruction: SetLockupChecked");
1272 Self::process_set_lockup_checked(accounts, lockup_checked)
1273 }
1274 StakeInstruction::GetMinimumDelegation => {
1275 msg!("Instruction: GetMinimumDelegation");
1276 let minimum_delegation = crate::get_minimum_delegation();
1277 set_return_data(&minimum_delegation.to_le_bytes());
1278 Ok(())
1279 }
1280 StakeInstruction::DeactivateDelinquent => {
1281 msg!("Instruction: DeactivateDelinquent");
1282 Self::process_deactivate_delinquent(accounts)
1283 }
1284 #[allow(deprecated)]
1285 StakeInstruction::Redelegate => Err(ProgramError::InvalidInstructionData),
1286 StakeInstruction::MoveStake(lamports) => {
1289 msg!("Instruction: MoveStake");
1290 Self::process_move_stake(accounts, lamports)
1291 }
1292 StakeInstruction::MoveLamports(lamports) => {
1293 msg!("Instruction: MoveLamports");
1294 Self::process_move_lamports(accounts, lamports)
1295 }
1296 }
1297 }
1298}