1#[allow(deprecated)]
2use crate::stake::config;
3use {
4 crate::{
5 clock::{Epoch, UnixTimestamp},
6 decode_error::DecodeError,
7 instruction::{AccountMeta, Instruction},
8 pubkey::Pubkey,
9 stake::{
10 program::id,
11 state::{Authorized, Lockup, StakeAuthorize, StakeStateV2},
12 },
13 system_instruction, sysvar,
14 },
15 log::*,
16 num_derive::{FromPrimitive, ToPrimitive},
17 serde_derive::{Deserialize, Serialize},
18 thiserror::Error,
19};
20
21#[derive(Error, Debug, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
23pub enum StakeError {
24 #[error("not enough credits to redeem")]
25 NoCreditsToRedeem,
26
27 #[error("lockup has not yet expired")]
28 LockupInForce,
29
30 #[error("stake already deactivated")]
31 AlreadyDeactivated,
32
33 #[error("one re-delegation permitted per epoch")]
34 TooSoonToRedelegate,
35
36 #[error("split amount is more than is staked")]
37 InsufficientStake,
38
39 #[error("stake account with transient stake cannot be merged")]
40 MergeTransientStake,
41
42 #[error("stake account merge failed due to different authority, lockups or state")]
43 MergeMismatch,
44
45 #[error("custodian address not present")]
46 CustodianMissing,
47
48 #[error("custodian signature not present")]
49 CustodianSignatureMissing,
50
51 #[error("insufficient voting activity in the reference vote account")]
52 InsufficientReferenceVotes,
53
54 #[error("stake account is not delegated to the provided vote account")]
55 VoteAddressMismatch,
56
57 #[error(
58 "stake account has not been delinquent for the minimum epochs required for deactivation"
59 )]
60 MinimumDelinquentEpochsForDeactivationNotMet,
61
62 #[error("delegation amount is less than the minimum")]
63 InsufficientDelegation,
64
65 #[error("stake account with transient or inactive stake cannot be redelegated")]
66 RedelegateTransientOrInactiveStake,
67
68 #[error("stake redelegation to the same vote account is not permitted")]
69 RedelegateToSameVoteAccount,
70
71 #[error("redelegated stake must be fully activated before deactivation")]
72 RedelegatedStakeMustFullyActivateBeforeDeactivationIsPermitted,
73}
74
75impl<E> DecodeError<E> for StakeError {
76 fn type_of() -> &'static str {
77 "StakeError"
78 }
79}
80
81#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
82pub enum StakeInstruction {
83 Initialize(Authorized, Lockup),
93
94 Authorize(Pubkey, StakeAuthorize),
103
104 DelegateStake,
118
119 Split(u64),
126
127 Withdraw(u64),
140
141 Deactivate,
148
149 SetLockup(LockupArgs),
158
159 Merge,
184
185 AuthorizeWithSeed(AuthorizeWithSeedArgs),
194
195 InitializeChecked,
207
208 AuthorizeChecked(StakeAuthorize),
221
222 AuthorizeCheckedWithSeed(AuthorizeCheckedWithSeedArgs),
235
236 SetLockupChecked(LockupCheckedArgs),
249
250 GetMinimumDelegation,
261
262 DeactivateDelinquent,
274
275 Redelegate,
296}
297
298#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
299pub struct LockupArgs {
300 pub unix_timestamp: Option<UnixTimestamp>,
301 pub epoch: Option<Epoch>,
302 pub custodian: Option<Pubkey>,
303}
304
305#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
306pub struct LockupCheckedArgs {
307 pub unix_timestamp: Option<UnixTimestamp>,
308 pub epoch: Option<Epoch>,
309}
310
311#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
312pub struct AuthorizeWithSeedArgs {
313 pub new_authorized_pubkey: Pubkey,
314 pub stake_authorize: StakeAuthorize,
315 pub authority_seed: String,
316 pub authority_owner: Pubkey,
317}
318
319#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
320pub struct AuthorizeCheckedWithSeedArgs {
321 pub stake_authorize: StakeAuthorize,
322 pub authority_seed: String,
323 pub authority_owner: Pubkey,
324}
325
326pub fn initialize(stake_pubkey: &Pubkey, authorized: &Authorized, lockup: &Lockup) -> Instruction {
327 Instruction::new_with_bincode(
328 id(),
329 &StakeInstruction::Initialize(*authorized, *lockup),
330 vec![
331 AccountMeta::new(*stake_pubkey, false),
332 AccountMeta::new_readonly(sysvar::rent::id(), false),
333 ],
334 )
335}
336
337pub fn initialize_checked(stake_pubkey: &Pubkey, authorized: &Authorized) -> Instruction {
338 Instruction::new_with_bincode(
339 id(),
340 &StakeInstruction::InitializeChecked,
341 vec![
342 AccountMeta::new(*stake_pubkey, false),
343 AccountMeta::new_readonly(sysvar::rent::id(), false),
344 AccountMeta::new_readonly(authorized.staker, false),
345 AccountMeta::new_readonly(authorized.withdrawer, true),
346 ],
347 )
348}
349
350pub fn create_account_with_seed(
351 from_pubkey: &Pubkey,
352 stake_pubkey: &Pubkey,
353 base: &Pubkey,
354 seed: &str,
355 authorized: &Authorized,
356 lockup: &Lockup,
357 lamports: u64,
358) -> Vec<Instruction> {
359 vec![
360 system_instruction::create_account_with_seed(
361 from_pubkey,
362 stake_pubkey,
363 base,
364 seed,
365 lamports,
366 StakeStateV2::size_of() as u64,
367 &id(),
368 ),
369 initialize(stake_pubkey, authorized, lockup),
370 ]
371}
372
373pub fn create_account(
374 from_pubkey: &Pubkey,
375 stake_pubkey: &Pubkey,
376 authorized: &Authorized,
377 lockup: &Lockup,
378 lamports: u64,
379) -> Vec<Instruction> {
380 vec![
381 system_instruction::create_account(
382 from_pubkey,
383 stake_pubkey,
384 lamports,
385 StakeStateV2::size_of() as u64,
386 &id(),
387 ),
388 initialize(stake_pubkey, authorized, lockup),
389 ]
390}
391
392pub fn create_account_with_seed_checked(
393 from_pubkey: &Pubkey,
394 stake_pubkey: &Pubkey,
395 base: &Pubkey,
396 seed: &str,
397 authorized: &Authorized,
398 lamports: u64,
399) -> Vec<Instruction> {
400 vec![
401 system_instruction::create_account_with_seed(
402 from_pubkey,
403 stake_pubkey,
404 base,
405 seed,
406 lamports,
407 StakeStateV2::size_of() as u64,
408 &id(),
409 ),
410 initialize_checked(stake_pubkey, authorized),
411 ]
412}
413
414pub fn create_account_checked(
415 from_pubkey: &Pubkey,
416 stake_pubkey: &Pubkey,
417 authorized: &Authorized,
418 lamports: u64,
419) -> Vec<Instruction> {
420 vec![
421 system_instruction::create_account(
422 from_pubkey,
423 stake_pubkey,
424 lamports,
425 StakeStateV2::size_of() as u64,
426 &id(),
427 ),
428 initialize_checked(stake_pubkey, authorized),
429 ]
430}
431
432fn _split(
433 stake_pubkey: &Pubkey,
434 authorized_pubkey: &Pubkey,
435 lamports: u64,
436 split_stake_pubkey: &Pubkey,
437) -> Instruction {
438 let account_metas = vec![
439 AccountMeta::new(*stake_pubkey, false),
440 AccountMeta::new(*split_stake_pubkey, false),
441 AccountMeta::new_readonly(*authorized_pubkey, true),
442 ];
443
444 Instruction::new_with_bincode(id(), &StakeInstruction::Split(lamports), account_metas)
445}
446
447pub fn split(
448 stake_pubkey: &Pubkey,
449 authorized_pubkey: &Pubkey,
450 lamports: u64,
451 split_stake_pubkey: &Pubkey,
452) -> Vec<Instruction> {
453 vec![
454 system_instruction::allocate(split_stake_pubkey, StakeStateV2::size_of() as u64),
455 system_instruction::assign(split_stake_pubkey, &id()),
456 _split(
457 stake_pubkey,
458 authorized_pubkey,
459 lamports,
460 split_stake_pubkey,
461 ),
462 ]
463}
464
465pub fn split_with_seed(
466 stake_pubkey: &Pubkey,
467 authorized_pubkey: &Pubkey,
468 lamports: u64,
469 split_stake_pubkey: &Pubkey, base: &Pubkey, seed: &str, ) -> Vec<Instruction> {
473 vec![
474 system_instruction::allocate_with_seed(
475 split_stake_pubkey,
476 base,
477 seed,
478 StakeStateV2::size_of() as u64,
479 &id(),
480 ),
481 _split(
482 stake_pubkey,
483 authorized_pubkey,
484 lamports,
485 split_stake_pubkey,
486 ),
487 ]
488}
489
490pub fn merge(
491 destination_stake_pubkey: &Pubkey,
492 source_stake_pubkey: &Pubkey,
493 authorized_pubkey: &Pubkey,
494) -> Vec<Instruction> {
495 let account_metas = vec![
496 AccountMeta::new(*destination_stake_pubkey, false),
497 AccountMeta::new(*source_stake_pubkey, false),
498 AccountMeta::new_readonly(sysvar::clock::id(), false),
499 AccountMeta::new_readonly(sysvar::stake_history::id(), false),
500 AccountMeta::new_readonly(*authorized_pubkey, true),
501 ];
502
503 vec![Instruction::new_with_bincode(
504 id(),
505 &StakeInstruction::Merge,
506 account_metas,
507 )]
508}
509
510pub fn create_account_and_delegate_stake(
511 from_pubkey: &Pubkey,
512 stake_pubkey: &Pubkey,
513 vote_pubkey: &Pubkey,
514 authorized: &Authorized,
515 lockup: &Lockup,
516 lamports: u64,
517) -> Vec<Instruction> {
518 let mut instructions = create_account(from_pubkey, stake_pubkey, authorized, lockup, lamports);
519 instructions.push(delegate_stake(
520 stake_pubkey,
521 &authorized.staker,
522 vote_pubkey,
523 ));
524 instructions
525}
526
527pub fn create_account_with_seed_and_delegate_stake(
528 from_pubkey: &Pubkey,
529 stake_pubkey: &Pubkey,
530 base: &Pubkey,
531 seed: &str,
532 vote_pubkey: &Pubkey,
533 authorized: &Authorized,
534 lockup: &Lockup,
535 lamports: u64,
536) -> Vec<Instruction> {
537 let mut instructions = create_account_with_seed(
538 from_pubkey,
539 stake_pubkey,
540 base,
541 seed,
542 authorized,
543 lockup,
544 lamports,
545 );
546 instructions.push(delegate_stake(
547 stake_pubkey,
548 &authorized.staker,
549 vote_pubkey,
550 ));
551 instructions
552}
553
554pub fn authorize(
555 stake_pubkey: &Pubkey,
556 authorized_pubkey: &Pubkey,
557 new_authorized_pubkey: &Pubkey,
558 stake_authorize: StakeAuthorize,
559 custodian_pubkey: Option<&Pubkey>,
560) -> Instruction {
561 let mut account_metas = vec![
562 AccountMeta::new(*stake_pubkey, false),
563 AccountMeta::new_readonly(sysvar::clock::id(), false),
564 AccountMeta::new_readonly(*authorized_pubkey, true),
565 ];
566
567 if let Some(custodian_pubkey) = custodian_pubkey {
568 account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
569 }
570
571 Instruction::new_with_bincode(
572 id(),
573 &StakeInstruction::Authorize(*new_authorized_pubkey, stake_authorize),
574 account_metas,
575 )
576}
577
578pub fn authorize_checked(
579 stake_pubkey: &Pubkey,
580 authorized_pubkey: &Pubkey,
581 new_authorized_pubkey: &Pubkey,
582 stake_authorize: StakeAuthorize,
583 custodian_pubkey: Option<&Pubkey>,
584) -> Instruction {
585 let mut account_metas = vec![
586 AccountMeta::new(*stake_pubkey, false),
587 AccountMeta::new_readonly(sysvar::clock::id(), false),
588 AccountMeta::new_readonly(*authorized_pubkey, true),
589 AccountMeta::new_readonly(*new_authorized_pubkey, true),
590 ];
591
592 if let Some(custodian_pubkey) = custodian_pubkey {
593 account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
594 }
595
596 Instruction::new_with_bincode(
597 id(),
598 &StakeInstruction::AuthorizeChecked(stake_authorize),
599 account_metas,
600 )
601}
602
603pub fn authorize_with_seed(
604 stake_pubkey: &Pubkey,
605 authority_base: &Pubkey,
606 authority_seed: String,
607 authority_owner: &Pubkey,
608 new_authorized_pubkey: &Pubkey,
609 stake_authorize: StakeAuthorize,
610 custodian_pubkey: Option<&Pubkey>,
611) -> Instruction {
612 let mut account_metas = vec![
613 AccountMeta::new(*stake_pubkey, false),
614 AccountMeta::new_readonly(*authority_base, true),
615 AccountMeta::new_readonly(sysvar::clock::id(), false),
616 ];
617
618 if let Some(custodian_pubkey) = custodian_pubkey {
619 account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
620 }
621
622 let args = AuthorizeWithSeedArgs {
623 new_authorized_pubkey: *new_authorized_pubkey,
624 stake_authorize,
625 authority_seed,
626 authority_owner: *authority_owner,
627 };
628
629 Instruction::new_with_bincode(
630 id(),
631 &StakeInstruction::AuthorizeWithSeed(args),
632 account_metas,
633 )
634}
635
636pub fn authorize_checked_with_seed(
637 stake_pubkey: &Pubkey,
638 authority_base: &Pubkey,
639 authority_seed: String,
640 authority_owner: &Pubkey,
641 new_authorized_pubkey: &Pubkey,
642 stake_authorize: StakeAuthorize,
643 custodian_pubkey: Option<&Pubkey>,
644) -> Instruction {
645 let mut account_metas = vec![
646 AccountMeta::new(*stake_pubkey, false),
647 AccountMeta::new_readonly(*authority_base, true),
648 AccountMeta::new_readonly(sysvar::clock::id(), false),
649 AccountMeta::new_readonly(*new_authorized_pubkey, true),
650 ];
651
652 if let Some(custodian_pubkey) = custodian_pubkey {
653 account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
654 }
655
656 let args = AuthorizeCheckedWithSeedArgs {
657 stake_authorize,
658 authority_seed,
659 authority_owner: *authority_owner,
660 };
661
662 Instruction::new_with_bincode(
663 id(),
664 &StakeInstruction::AuthorizeCheckedWithSeed(args),
665 account_metas,
666 )
667}
668
669pub fn delegate_stake(
670 stake_pubkey: &Pubkey,
671 authorized_pubkey: &Pubkey,
672 vote_pubkey: &Pubkey,
673) -> Instruction {
674 let account_metas = vec![
675 AccountMeta::new(*stake_pubkey, false),
676 AccountMeta::new_readonly(*vote_pubkey, false),
677 AccountMeta::new_readonly(sysvar::clock::id(), false),
678 AccountMeta::new_readonly(sysvar::stake_history::id(), false),
679 #[allow(deprecated)]
680 AccountMeta::new_readonly(config::id(), false),
681 AccountMeta::new_readonly(*authorized_pubkey, true),
682 ];
683 Instruction::new_with_bincode(id(), &StakeInstruction::DelegateStake, account_metas)
684}
685
686pub fn withdraw(
687 stake_pubkey: &Pubkey,
688 withdrawer_pubkey: &Pubkey,
689 to_pubkey: &Pubkey,
690 lamports: u64,
691 custodian_pubkey: Option<&Pubkey>,
692) -> Instruction {
693 let mut account_metas = vec![
694 AccountMeta::new(*stake_pubkey, false),
695 AccountMeta::new(*to_pubkey, false),
696 AccountMeta::new_readonly(sysvar::clock::id(), false),
697 AccountMeta::new_readonly(sysvar::stake_history::id(), false),
698 AccountMeta::new_readonly(*withdrawer_pubkey, true),
699 ];
700
701 if let Some(custodian_pubkey) = custodian_pubkey {
702 account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
703 }
704
705 Instruction::new_with_bincode(id(), &StakeInstruction::Withdraw(lamports), account_metas)
706}
707
708pub fn deactivate_stake(stake_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction {
709 let account_metas = vec![
710 AccountMeta::new(*stake_pubkey, false),
711 AccountMeta::new_readonly(sysvar::clock::id(), false),
712 AccountMeta::new_readonly(*authorized_pubkey, true),
713 ];
714 Instruction::new_with_bincode(id(), &StakeInstruction::Deactivate, account_metas)
715}
716
717pub fn set_lockup(
718 stake_pubkey: &Pubkey,
719 lockup: &LockupArgs,
720 custodian_pubkey: &Pubkey,
721) -> Instruction {
722 let account_metas = vec![
723 AccountMeta::new(*stake_pubkey, false),
724 AccountMeta::new_readonly(*custodian_pubkey, true),
725 ];
726 Instruction::new_with_bincode(id(), &StakeInstruction::SetLockup(*lockup), account_metas)
727}
728
729pub fn set_lockup_checked(
730 stake_pubkey: &Pubkey,
731 lockup: &LockupArgs,
732 custodian_pubkey: &Pubkey,
733) -> Instruction {
734 let mut account_metas = vec![
735 AccountMeta::new(*stake_pubkey, false),
736 AccountMeta::new_readonly(*custodian_pubkey, true),
737 ];
738
739 let lockup_checked = LockupCheckedArgs {
740 unix_timestamp: lockup.unix_timestamp,
741 epoch: lockup.epoch,
742 };
743 if let Some(new_custodian) = lockup.custodian {
744 account_metas.push(AccountMeta::new_readonly(new_custodian, true));
745 }
746 Instruction::new_with_bincode(
747 id(),
748 &StakeInstruction::SetLockupChecked(lockup_checked),
749 account_metas,
750 )
751}
752
753pub fn get_minimum_delegation() -> Instruction {
754 Instruction::new_with_bincode(
755 id(),
756 &StakeInstruction::GetMinimumDelegation,
757 Vec::default(),
758 )
759}
760
761pub fn deactivate_delinquent_stake(
762 stake_account: &Pubkey,
763 delinquent_vote_account: &Pubkey,
764 reference_vote_account: &Pubkey,
765) -> Instruction {
766 let account_metas = vec![
767 AccountMeta::new(*stake_account, false),
768 AccountMeta::new_readonly(*delinquent_vote_account, false),
769 AccountMeta::new_readonly(*reference_vote_account, false),
770 ];
771 Instruction::new_with_bincode(id(), &StakeInstruction::DeactivateDelinquent, account_metas)
772}
773
774fn _redelegate(
775 stake_pubkey: &Pubkey,
776 authorized_pubkey: &Pubkey,
777 vote_pubkey: &Pubkey,
778 uninitialized_stake_pubkey: &Pubkey,
779) -> Instruction {
780 let account_metas = vec![
781 AccountMeta::new(*stake_pubkey, false),
782 AccountMeta::new(*uninitialized_stake_pubkey, false),
783 AccountMeta::new_readonly(*vote_pubkey, false),
784 #[allow(deprecated)]
785 AccountMeta::new_readonly(config::id(), false),
786 AccountMeta::new_readonly(*authorized_pubkey, true),
787 ];
788 Instruction::new_with_bincode(id(), &StakeInstruction::Redelegate, account_metas)
789}
790
791pub fn redelegate(
792 stake_pubkey: &Pubkey,
793 authorized_pubkey: &Pubkey,
794 vote_pubkey: &Pubkey,
795 uninitialized_stake_pubkey: &Pubkey,
796) -> Vec<Instruction> {
797 vec![
798 system_instruction::allocate(uninitialized_stake_pubkey, StakeStateV2::size_of() as u64),
799 system_instruction::assign(uninitialized_stake_pubkey, &id()),
800 _redelegate(
801 stake_pubkey,
802 authorized_pubkey,
803 vote_pubkey,
804 uninitialized_stake_pubkey,
805 ),
806 ]
807}
808
809pub fn redelegate_with_seed(
810 stake_pubkey: &Pubkey,
811 authorized_pubkey: &Pubkey,
812 vote_pubkey: &Pubkey,
813 uninitialized_stake_pubkey: &Pubkey, base: &Pubkey, seed: &str, ) -> Vec<Instruction> {
817 vec![
818 system_instruction::allocate_with_seed(
819 uninitialized_stake_pubkey,
820 base,
821 seed,
822 StakeStateV2::size_of() as u64,
823 &id(),
824 ),
825 _redelegate(
826 stake_pubkey,
827 authorized_pubkey,
828 vote_pubkey,
829 uninitialized_stake_pubkey,
830 ),
831 ]
832}
833
834#[cfg(test)]
835mod tests {
836 use {super::*, crate::instruction::InstructionError};
837
838 #[test]
839 fn test_custom_error_decode() {
840 use num_traits::FromPrimitive;
841 fn pretty_err<T>(err: InstructionError) -> String
842 where
843 T: 'static + std::error::Error + DecodeError<T> + FromPrimitive,
844 {
845 if let InstructionError::Custom(code) = err {
846 let specific_error: T = T::decode_custom_error_to_enum(code).unwrap();
847 format!(
848 "{:?}: {}::{:?} - {}",
849 err,
850 T::type_of(),
851 specific_error,
852 specific_error,
853 )
854 } else {
855 "".to_string()
856 }
857 }
858 assert_eq!(
859 "Custom(0): StakeError::NoCreditsToRedeem - not enough credits to redeem",
860 pretty_err::<StakeError>(StakeError::NoCreditsToRedeem.into())
861 )
862 }
863}