1#![allow(deprecated)]
5
6use {
7 crate::{
8 instruction::{AccountMeta, Instruction},
9 program_error::ProgramError,
10 pubkey::Pubkey,
11 stake::{
12 config,
13 program::id,
14 state::{Authorized, Lockup, StakeAuthorize, StakeStateV2},
15 },
16 system_instruction, sysvar,
17 },
18 log::*,
19 num_derive::{FromPrimitive, ToPrimitive},
20 serde_derive::{Deserialize, Serialize},
21 solana_clock::{Epoch, UnixTimestamp},
22 solana_decode_error::DecodeError,
23 thiserror::Error,
24};
25
26#[derive(Error, Debug, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
28pub enum StakeError {
29 #[error("not enough credits to redeem")]
31 NoCreditsToRedeem,
32
33 #[error("lockup has not yet expired")]
34 LockupInForce,
35
36 #[error("stake already deactivated")]
37 AlreadyDeactivated,
38
39 #[error("one re-delegation permitted per epoch")]
40 TooSoonToRedelegate,
41
42 #[error("split amount is more than is staked")]
43 InsufficientStake,
44
45 #[error("stake account with transient stake cannot be merged")]
47 MergeTransientStake,
48
49 #[error("stake account merge failed due to different authority, lockups or state")]
50 MergeMismatch,
51
52 #[error("custodian address not present")]
53 CustodianMissing,
54
55 #[error("custodian signature not present")]
56 CustodianSignatureMissing,
57
58 #[error("insufficient voting activity in the reference vote account")]
59 InsufficientReferenceVotes,
60
61 #[error("stake account is not delegated to the provided vote account")]
63 VoteAddressMismatch,
64
65 #[error(
66 "stake account has not been delinquent for the minimum epochs required for deactivation"
67 )]
68 MinimumDelinquentEpochsForDeactivationNotMet,
69
70 #[error("delegation amount is less than the minimum")]
71 InsufficientDelegation,
72
73 #[error("stake account with transient or inactive stake cannot be redelegated")]
74 RedelegateTransientOrInactiveStake,
75
76 #[error("stake redelegation to the same vote account is not permitted")]
77 RedelegateToSameVoteAccount,
78
79 #[error("redelegated stake must be fully activated before deactivation")]
81 RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted,
82
83 #[error("stake action is not permitted while the epoch rewards period is active")]
84 EpochRewardsActive,
85}
86
87impl From<StakeError> for ProgramError {
88 fn from(e: StakeError) -> Self {
89 ProgramError::Custom(e as u32)
90 }
91}
92
93impl<E> DecodeError<E> for StakeError {
94 fn type_of() -> &'static str {
95 "StakeError"
96 }
97}
98
99#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
100pub enum StakeInstruction {
101 Initialize(Authorized, Lockup),
111
112 Authorize(Pubkey, StakeAuthorize),
121
122 DelegateStake,
136
137 Split(u64),
144
145 Withdraw(u64),
158
159 Deactivate,
166
167 SetLockup(LockupArgs),
176
177 Merge,
202
203 AuthorizeWithSeed(AuthorizeWithSeedArgs),
212
213 InitializeChecked,
225
226 AuthorizeChecked(StakeAuthorize),
239
240 AuthorizeCheckedWithSeed(AuthorizeCheckedWithSeedArgs),
253
254 SetLockupChecked(LockupCheckedArgs),
267
268 GetMinimumDelegation,
279
280 DeactivateDelinquent,
292
293 #[deprecated(since = "2.1.0", note = "Redelegate will not be enabled")]
314 Redelegate,
315
316 MoveStake(u64),
336
337 MoveLamports(u64),
351}
352
353#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
354pub struct LockupArgs {
355 pub unix_timestamp: Option<UnixTimestamp>,
356 pub epoch: Option<Epoch>,
357 pub custodian: Option<Pubkey>,
358}
359
360#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
361pub struct LockupCheckedArgs {
362 pub unix_timestamp: Option<UnixTimestamp>,
363 pub epoch: Option<Epoch>,
364}
365
366#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
367pub struct AuthorizeWithSeedArgs {
368 pub new_authorized_pubkey: Pubkey,
369 pub stake_authorize: StakeAuthorize,
370 pub authority_seed: String,
371 pub authority_owner: Pubkey,
372}
373
374#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
375pub struct AuthorizeCheckedWithSeedArgs {
376 pub stake_authorize: StakeAuthorize,
377 pub authority_seed: String,
378 pub authority_owner: Pubkey,
379}
380
381pub fn initialize(stake_pubkey: &Pubkey, authorized: &Authorized, lockup: &Lockup) -> Instruction {
382 Instruction::new_with_bincode(
383 id(),
384 &StakeInstruction::Initialize(*authorized, *lockup),
385 vec![
386 AccountMeta::new(*stake_pubkey, false),
387 AccountMeta::new_readonly(sysvar::rent::id(), false),
388 ],
389 )
390}
391
392pub fn initialize_checked(stake_pubkey: &Pubkey, authorized: &Authorized) -> Instruction {
393 Instruction::new_with_bincode(
394 id(),
395 &StakeInstruction::InitializeChecked,
396 vec![
397 AccountMeta::new(*stake_pubkey, false),
398 AccountMeta::new_readonly(sysvar::rent::id(), false),
399 AccountMeta::new_readonly(authorized.staker, false),
400 AccountMeta::new_readonly(authorized.withdrawer, true),
401 ],
402 )
403}
404
405pub fn create_account_with_seed(
406 from_pubkey: &Pubkey,
407 stake_pubkey: &Pubkey,
408 base: &Pubkey,
409 seed: &str,
410 authorized: &Authorized,
411 lockup: &Lockup,
412 lamports: u64,
413) -> Vec<Instruction> {
414 vec![
415 system_instruction::create_account_with_seed(
416 from_pubkey,
417 stake_pubkey,
418 base,
419 seed,
420 lamports,
421 StakeStateV2::size_of() as u64,
422 &id(),
423 ),
424 initialize(stake_pubkey, authorized, lockup),
425 ]
426}
427
428pub fn create_account(
429 from_pubkey: &Pubkey,
430 stake_pubkey: &Pubkey,
431 authorized: &Authorized,
432 lockup: &Lockup,
433 lamports: u64,
434) -> Vec<Instruction> {
435 vec![
436 system_instruction::create_account(
437 from_pubkey,
438 stake_pubkey,
439 lamports,
440 StakeStateV2::size_of() as u64,
441 &id(),
442 ),
443 initialize(stake_pubkey, authorized, lockup),
444 ]
445}
446
447pub fn create_account_with_seed_checked(
448 from_pubkey: &Pubkey,
449 stake_pubkey: &Pubkey,
450 base: &Pubkey,
451 seed: &str,
452 authorized: &Authorized,
453 lamports: u64,
454) -> Vec<Instruction> {
455 vec![
456 system_instruction::create_account_with_seed(
457 from_pubkey,
458 stake_pubkey,
459 base,
460 seed,
461 lamports,
462 StakeStateV2::size_of() as u64,
463 &id(),
464 ),
465 initialize_checked(stake_pubkey, authorized),
466 ]
467}
468
469pub fn create_account_checked(
470 from_pubkey: &Pubkey,
471 stake_pubkey: &Pubkey,
472 authorized: &Authorized,
473 lamports: u64,
474) -> Vec<Instruction> {
475 vec![
476 system_instruction::create_account(
477 from_pubkey,
478 stake_pubkey,
479 lamports,
480 StakeStateV2::size_of() as u64,
481 &id(),
482 ),
483 initialize_checked(stake_pubkey, authorized),
484 ]
485}
486
487fn _split(
488 stake_pubkey: &Pubkey,
489 authorized_pubkey: &Pubkey,
490 lamports: u64,
491 split_stake_pubkey: &Pubkey,
492) -> Instruction {
493 let account_metas = vec![
494 AccountMeta::new(*stake_pubkey, false),
495 AccountMeta::new(*split_stake_pubkey, false),
496 AccountMeta::new_readonly(*authorized_pubkey, true),
497 ];
498
499 Instruction::new_with_bincode(id(), &StakeInstruction::Split(lamports), account_metas)
500}
501
502pub fn split(
503 stake_pubkey: &Pubkey,
504 authorized_pubkey: &Pubkey,
505 lamports: u64,
506 split_stake_pubkey: &Pubkey,
507) -> Vec<Instruction> {
508 vec![
509 system_instruction::allocate(split_stake_pubkey, StakeStateV2::size_of() as u64),
510 system_instruction::assign(split_stake_pubkey, &id()),
511 _split(
512 stake_pubkey,
513 authorized_pubkey,
514 lamports,
515 split_stake_pubkey,
516 ),
517 ]
518}
519
520pub fn split_with_seed(
521 stake_pubkey: &Pubkey,
522 authorized_pubkey: &Pubkey,
523 lamports: u64,
524 split_stake_pubkey: &Pubkey, base: &Pubkey, seed: &str, ) -> Vec<Instruction> {
528 vec![
529 system_instruction::allocate_with_seed(
530 split_stake_pubkey,
531 base,
532 seed,
533 StakeStateV2::size_of() as u64,
534 &id(),
535 ),
536 _split(
537 stake_pubkey,
538 authorized_pubkey,
539 lamports,
540 split_stake_pubkey,
541 ),
542 ]
543}
544
545pub fn merge(
546 destination_stake_pubkey: &Pubkey,
547 source_stake_pubkey: &Pubkey,
548 authorized_pubkey: &Pubkey,
549) -> Vec<Instruction> {
550 let account_metas = vec![
551 AccountMeta::new(*destination_stake_pubkey, false),
552 AccountMeta::new(*source_stake_pubkey, false),
553 AccountMeta::new_readonly(sysvar::clock::id(), false),
554 AccountMeta::new_readonly(sysvar::stake_history::id(), false),
555 AccountMeta::new_readonly(*authorized_pubkey, true),
556 ];
557
558 vec![Instruction::new_with_bincode(
559 id(),
560 &StakeInstruction::Merge,
561 account_metas,
562 )]
563}
564
565pub fn create_account_and_delegate_stake(
566 from_pubkey: &Pubkey,
567 stake_pubkey: &Pubkey,
568 vote_pubkey: &Pubkey,
569 authorized: &Authorized,
570 lockup: &Lockup,
571 lamports: u64,
572) -> Vec<Instruction> {
573 let mut instructions = create_account(from_pubkey, stake_pubkey, authorized, lockup, lamports);
574 instructions.push(delegate_stake(
575 stake_pubkey,
576 &authorized.staker,
577 vote_pubkey,
578 ));
579 instructions
580}
581
582pub fn create_account_with_seed_and_delegate_stake(
583 from_pubkey: &Pubkey,
584 stake_pubkey: &Pubkey,
585 base: &Pubkey,
586 seed: &str,
587 vote_pubkey: &Pubkey,
588 authorized: &Authorized,
589 lockup: &Lockup,
590 lamports: u64,
591) -> Vec<Instruction> {
592 let mut instructions = create_account_with_seed(
593 from_pubkey,
594 stake_pubkey,
595 base,
596 seed,
597 authorized,
598 lockup,
599 lamports,
600 );
601 instructions.push(delegate_stake(
602 stake_pubkey,
603 &authorized.staker,
604 vote_pubkey,
605 ));
606 instructions
607}
608
609pub fn authorize(
610 stake_pubkey: &Pubkey,
611 authorized_pubkey: &Pubkey,
612 new_authorized_pubkey: &Pubkey,
613 stake_authorize: StakeAuthorize,
614 custodian_pubkey: Option<&Pubkey>,
615) -> Instruction {
616 let mut account_metas = vec![
617 AccountMeta::new(*stake_pubkey, false),
618 AccountMeta::new_readonly(sysvar::clock::id(), false),
619 AccountMeta::new_readonly(*authorized_pubkey, true),
620 ];
621
622 if let Some(custodian_pubkey) = custodian_pubkey {
623 account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
624 }
625
626 Instruction::new_with_bincode(
627 id(),
628 &StakeInstruction::Authorize(*new_authorized_pubkey, stake_authorize),
629 account_metas,
630 )
631}
632
633pub fn authorize_checked(
634 stake_pubkey: &Pubkey,
635 authorized_pubkey: &Pubkey,
636 new_authorized_pubkey: &Pubkey,
637 stake_authorize: StakeAuthorize,
638 custodian_pubkey: Option<&Pubkey>,
639) -> Instruction {
640 let mut account_metas = vec![
641 AccountMeta::new(*stake_pubkey, false),
642 AccountMeta::new_readonly(sysvar::clock::id(), false),
643 AccountMeta::new_readonly(*authorized_pubkey, true),
644 AccountMeta::new_readonly(*new_authorized_pubkey, true),
645 ];
646
647 if let Some(custodian_pubkey) = custodian_pubkey {
648 account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
649 }
650
651 Instruction::new_with_bincode(
652 id(),
653 &StakeInstruction::AuthorizeChecked(stake_authorize),
654 account_metas,
655 )
656}
657
658pub fn authorize_with_seed(
659 stake_pubkey: &Pubkey,
660 authority_base: &Pubkey,
661 authority_seed: String,
662 authority_owner: &Pubkey,
663 new_authorized_pubkey: &Pubkey,
664 stake_authorize: StakeAuthorize,
665 custodian_pubkey: Option<&Pubkey>,
666) -> Instruction {
667 let mut account_metas = vec![
668 AccountMeta::new(*stake_pubkey, false),
669 AccountMeta::new_readonly(*authority_base, true),
670 AccountMeta::new_readonly(sysvar::clock::id(), false),
671 ];
672
673 if let Some(custodian_pubkey) = custodian_pubkey {
674 account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
675 }
676
677 let args = AuthorizeWithSeedArgs {
678 new_authorized_pubkey: *new_authorized_pubkey,
679 stake_authorize,
680 authority_seed,
681 authority_owner: *authority_owner,
682 };
683
684 Instruction::new_with_bincode(
685 id(),
686 &StakeInstruction::AuthorizeWithSeed(args),
687 account_metas,
688 )
689}
690
691pub fn authorize_checked_with_seed(
692 stake_pubkey: &Pubkey,
693 authority_base: &Pubkey,
694 authority_seed: String,
695 authority_owner: &Pubkey,
696 new_authorized_pubkey: &Pubkey,
697 stake_authorize: StakeAuthorize,
698 custodian_pubkey: Option<&Pubkey>,
699) -> Instruction {
700 let mut account_metas = vec![
701 AccountMeta::new(*stake_pubkey, false),
702 AccountMeta::new_readonly(*authority_base, true),
703 AccountMeta::new_readonly(sysvar::clock::id(), false),
704 AccountMeta::new_readonly(*new_authorized_pubkey, true),
705 ];
706
707 if let Some(custodian_pubkey) = custodian_pubkey {
708 account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
709 }
710
711 let args = AuthorizeCheckedWithSeedArgs {
712 stake_authorize,
713 authority_seed,
714 authority_owner: *authority_owner,
715 };
716
717 Instruction::new_with_bincode(
718 id(),
719 &StakeInstruction::AuthorizeCheckedWithSeed(args),
720 account_metas,
721 )
722}
723
724pub fn delegate_stake(
725 stake_pubkey: &Pubkey,
726 authorized_pubkey: &Pubkey,
727 vote_pubkey: &Pubkey,
728) -> Instruction {
729 let account_metas = vec![
730 AccountMeta::new(*stake_pubkey, false),
731 AccountMeta::new_readonly(*vote_pubkey, false),
732 AccountMeta::new_readonly(sysvar::clock::id(), false),
733 AccountMeta::new_readonly(sysvar::stake_history::id(), false),
734 AccountMeta::new_readonly(config::id(), false),
736 AccountMeta::new_readonly(*authorized_pubkey, true),
737 ];
738 Instruction::new_with_bincode(id(), &StakeInstruction::DelegateStake, account_metas)
739}
740
741pub fn withdraw(
742 stake_pubkey: &Pubkey,
743 withdrawer_pubkey: &Pubkey,
744 to_pubkey: &Pubkey,
745 lamports: u64,
746 custodian_pubkey: Option<&Pubkey>,
747) -> Instruction {
748 let mut account_metas = vec![
749 AccountMeta::new(*stake_pubkey, false),
750 AccountMeta::new(*to_pubkey, false),
751 AccountMeta::new_readonly(sysvar::clock::id(), false),
752 AccountMeta::new_readonly(sysvar::stake_history::id(), false),
753 AccountMeta::new_readonly(*withdrawer_pubkey, true),
754 ];
755
756 if let Some(custodian_pubkey) = custodian_pubkey {
757 account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
758 }
759
760 Instruction::new_with_bincode(id(), &StakeInstruction::Withdraw(lamports), account_metas)
761}
762
763pub fn deactivate_stake(stake_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction {
764 let account_metas = vec![
765 AccountMeta::new(*stake_pubkey, false),
766 AccountMeta::new_readonly(sysvar::clock::id(), false),
767 AccountMeta::new_readonly(*authorized_pubkey, true),
768 ];
769 Instruction::new_with_bincode(id(), &StakeInstruction::Deactivate, account_metas)
770}
771
772pub fn set_lockup(
773 stake_pubkey: &Pubkey,
774 lockup: &LockupArgs,
775 custodian_pubkey: &Pubkey,
776) -> Instruction {
777 let account_metas = vec![
778 AccountMeta::new(*stake_pubkey, false),
779 AccountMeta::new_readonly(*custodian_pubkey, true),
780 ];
781 Instruction::new_with_bincode(id(), &StakeInstruction::SetLockup(*lockup), account_metas)
782}
783
784pub fn set_lockup_checked(
785 stake_pubkey: &Pubkey,
786 lockup: &LockupArgs,
787 custodian_pubkey: &Pubkey,
788) -> Instruction {
789 let mut account_metas = vec![
790 AccountMeta::new(*stake_pubkey, false),
791 AccountMeta::new_readonly(*custodian_pubkey, true),
792 ];
793
794 let lockup_checked = LockupCheckedArgs {
795 unix_timestamp: lockup.unix_timestamp,
796 epoch: lockup.epoch,
797 };
798 if let Some(new_custodian) = lockup.custodian {
799 account_metas.push(AccountMeta::new_readonly(new_custodian, true));
800 }
801 Instruction::new_with_bincode(
802 id(),
803 &StakeInstruction::SetLockupChecked(lockup_checked),
804 account_metas,
805 )
806}
807
808pub fn get_minimum_delegation() -> Instruction {
809 Instruction::new_with_bincode(
810 id(),
811 &StakeInstruction::GetMinimumDelegation,
812 Vec::default(),
813 )
814}
815
816pub fn deactivate_delinquent_stake(
817 stake_account: &Pubkey,
818 delinquent_vote_account: &Pubkey,
819 reference_vote_account: &Pubkey,
820) -> Instruction {
821 let account_metas = vec![
822 AccountMeta::new(*stake_account, false),
823 AccountMeta::new_readonly(*delinquent_vote_account, false),
824 AccountMeta::new_readonly(*reference_vote_account, false),
825 ];
826 Instruction::new_with_bincode(id(), &StakeInstruction::DeactivateDelinquent, account_metas)
827}
828
829fn _redelegate(
830 stake_pubkey: &Pubkey,
831 authorized_pubkey: &Pubkey,
832 vote_pubkey: &Pubkey,
833 uninitialized_stake_pubkey: &Pubkey,
834) -> Instruction {
835 let account_metas = vec![
836 AccountMeta::new(*stake_pubkey, false),
837 AccountMeta::new(*uninitialized_stake_pubkey, false),
838 AccountMeta::new_readonly(*vote_pubkey, false),
839 AccountMeta::new_readonly(config::id(), false),
841 AccountMeta::new_readonly(*authorized_pubkey, true),
842 ];
843 Instruction::new_with_bincode(id(), &StakeInstruction::Redelegate, account_metas)
844}
845
846#[deprecated(since = "2.1.0", note = "Redelegate will not be enabled")]
847pub fn redelegate(
848 stake_pubkey: &Pubkey,
849 authorized_pubkey: &Pubkey,
850 vote_pubkey: &Pubkey,
851 uninitialized_stake_pubkey: &Pubkey,
852) -> Vec<Instruction> {
853 vec![
854 system_instruction::allocate(uninitialized_stake_pubkey, StakeStateV2::size_of() as u64),
855 system_instruction::assign(uninitialized_stake_pubkey, &id()),
856 _redelegate(
857 stake_pubkey,
858 authorized_pubkey,
859 vote_pubkey,
860 uninitialized_stake_pubkey,
861 ),
862 ]
863}
864
865#[deprecated(since = "2.1.0", note = "Redelegate will not be enabled")]
866pub fn redelegate_with_seed(
867 stake_pubkey: &Pubkey,
868 authorized_pubkey: &Pubkey,
869 vote_pubkey: &Pubkey,
870 uninitialized_stake_pubkey: &Pubkey, base: &Pubkey, seed: &str, ) -> Vec<Instruction> {
874 vec![
875 system_instruction::allocate_with_seed(
876 uninitialized_stake_pubkey,
877 base,
878 seed,
879 StakeStateV2::size_of() as u64,
880 &id(),
881 ),
882 _redelegate(
883 stake_pubkey,
884 authorized_pubkey,
885 vote_pubkey,
886 uninitialized_stake_pubkey,
887 ),
888 ]
889}
890
891pub fn move_stake(
892 source_stake_pubkey: &Pubkey,
893 destination_stake_pubkey: &Pubkey,
894 authorized_pubkey: &Pubkey,
895 lamports: u64,
896) -> Instruction {
897 let account_metas = vec![
898 AccountMeta::new(*source_stake_pubkey, false),
899 AccountMeta::new(*destination_stake_pubkey, false),
900 AccountMeta::new_readonly(*authorized_pubkey, true),
901 ];
902
903 Instruction::new_with_bincode(id(), &StakeInstruction::MoveStake(lamports), account_metas)
904}
905
906pub fn move_lamports(
907 source_stake_pubkey: &Pubkey,
908 destination_stake_pubkey: &Pubkey,
909 authorized_pubkey: &Pubkey,
910 lamports: u64,
911) -> Instruction {
912 let account_metas = vec![
913 AccountMeta::new(*source_stake_pubkey, false),
914 AccountMeta::new(*destination_stake_pubkey, false),
915 AccountMeta::new_readonly(*authorized_pubkey, true),
916 ];
917
918 Instruction::new_with_bincode(
919 id(),
920 &StakeInstruction::MoveLamports(lamports),
921 account_metas,
922 )
923}
924
925#[cfg(test)]
926mod tests {
927 use {super::*, crate::instruction::InstructionError};
928
929 #[test]
930 fn test_custom_error_decode() {
931 use num_traits::FromPrimitive;
932 fn pretty_err<T>(err: InstructionError) -> String
933 where
934 T: 'static + std::error::Error + DecodeError<T> + FromPrimitive,
935 {
936 if let InstructionError::Custom(code) = err {
937 let specific_error: T = T::decode_custom_error_to_enum(code).unwrap();
938 format!(
939 "{:?}: {}::{:?} - {}",
940 err,
941 T::type_of(),
942 specific_error,
943 specific_error,
944 )
945 } else {
946 "".to_string()
947 }
948 }
949 assert_eq!(
950 "Custom(0): StakeError::NoCreditsToRedeem - not enough credits to redeem",
951 pretty_err::<StakeError>(StakeError::NoCreditsToRedeem.into())
952 )
953 }
954}