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