1use {
2 crate::stake::{
3 config,
4 program::id,
5 state::{Authorized, Lockup, StakeAuthorize, StakeState},
6 },
7 crate::{
8 clock::{Epoch, UnixTimestamp},
9 decode_error::DecodeError,
10 instruction::{AccountMeta, Instruction},
11 pubkey::Pubkey,
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, 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
51impl<E> DecodeError<E> for StakeError {
52 fn type_of() -> &'static str {
53 "StakeError"
54 }
55}
56
57#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
58pub enum StakeInstruction {
59 Initialize(Authorized, Lockup),
69
70 Authorize(Pubkey, StakeAuthorize),
79
80 DelegateStake,
94
95 Split(u64),
102
103 Withdraw(u64),
116
117 Deactivate,
124
125 SetLockup(LockupArgs),
134
135 Merge,
160
161 AuthorizeWithSeed(AuthorizeWithSeedArgs),
170
171 InitializeChecked,
183
184 AuthorizeChecked(StakeAuthorize),
197
198 AuthorizeCheckedWithSeed(AuthorizeCheckedWithSeedArgs),
211
212 SetLockupChecked(LockupCheckedArgs),
225}
226
227#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
228pub struct LockupArgs {
229 pub unix_timestamp: Option<UnixTimestamp>,
230 pub epoch: Option<Epoch>,
231 pub custodian: Option<Pubkey>,
232}
233
234#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
235pub struct LockupCheckedArgs {
236 pub unix_timestamp: Option<UnixTimestamp>,
237 pub epoch: Option<Epoch>,
238}
239
240#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
241pub struct AuthorizeWithSeedArgs {
242 pub new_authorized_pubkey: Pubkey,
243 pub stake_authorize: StakeAuthorize,
244 pub authority_seed: String,
245 pub authority_owner: Pubkey,
246}
247
248#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
249pub struct AuthorizeCheckedWithSeedArgs {
250 pub stake_authorize: StakeAuthorize,
251 pub authority_seed: String,
252 pub authority_owner: Pubkey,
253}
254
255pub fn initialize(stake_pubkey: &Pubkey, authorized: &Authorized, lockup: &Lockup) -> Instruction {
256 Instruction::new_with_bincode(
257 id(),
258 &StakeInstruction::Initialize(*authorized, *lockup),
259 vec![
260 AccountMeta::new(*stake_pubkey, false),
261 AccountMeta::new_readonly(sysvar::rent::id(), false),
262 ],
263 )
264}
265
266pub fn initialize_checked(stake_pubkey: &Pubkey, authorized: &Authorized) -> Instruction {
267 Instruction::new_with_bincode(
268 id(),
269 &StakeInstruction::InitializeChecked,
270 vec![
271 AccountMeta::new(*stake_pubkey, false),
272 AccountMeta::new_readonly(sysvar::rent::id(), false),
273 AccountMeta::new_readonly(authorized.staker, false),
274 AccountMeta::new_readonly(authorized.withdrawer, true),
275 ],
276 )
277}
278
279pub fn create_account_with_seed(
280 from_pubkey: &Pubkey,
281 stake_pubkey: &Pubkey,
282 base: &Pubkey,
283 seed: &str,
284 authorized: &Authorized,
285 lockup: &Lockup,
286 carats: u64,
287) -> Vec<Instruction> {
288 vec![
289 system_instruction::create_account_with_seed(
290 from_pubkey,
291 stake_pubkey,
292 base,
293 seed,
294 carats,
295 std::mem::size_of::<StakeState>() as u64,
296 &id(),
297 ),
298 initialize(stake_pubkey, authorized, lockup),
299 ]
300}
301
302pub fn create_account(
303 from_pubkey: &Pubkey,
304 stake_pubkey: &Pubkey,
305 authorized: &Authorized,
306 lockup: &Lockup,
307 carats: u64,
308) -> Vec<Instruction> {
309 vec![
310 system_instruction::create_account(
311 from_pubkey,
312 stake_pubkey,
313 carats,
314 std::mem::size_of::<StakeState>() as u64,
315 &id(),
316 ),
317 initialize(stake_pubkey, authorized, lockup),
318 ]
319}
320
321pub fn create_account_with_seed_checked(
322 from_pubkey: &Pubkey,
323 stake_pubkey: &Pubkey,
324 base: &Pubkey,
325 seed: &str,
326 authorized: &Authorized,
327 carats: u64,
328) -> Vec<Instruction> {
329 vec![
330 system_instruction::create_account_with_seed(
331 from_pubkey,
332 stake_pubkey,
333 base,
334 seed,
335 carats,
336 std::mem::size_of::<StakeState>() as u64,
337 &id(),
338 ),
339 initialize_checked(stake_pubkey, authorized),
340 ]
341}
342
343pub fn create_account_checked(
344 from_pubkey: &Pubkey,
345 stake_pubkey: &Pubkey,
346 authorized: &Authorized,
347 carats: u64,
348) -> Vec<Instruction> {
349 vec![
350 system_instruction::create_account(
351 from_pubkey,
352 stake_pubkey,
353 carats,
354 std::mem::size_of::<StakeState>() as u64,
355 &id(),
356 ),
357 initialize_checked(stake_pubkey, authorized),
358 ]
359}
360
361fn _split(
362 stake_pubkey: &Pubkey,
363 authorized_pubkey: &Pubkey,
364 carats: u64,
365 split_stake_pubkey: &Pubkey,
366) -> Instruction {
367 let account_metas = vec![
368 AccountMeta::new(*stake_pubkey, false),
369 AccountMeta::new(*split_stake_pubkey, false),
370 AccountMeta::new_readonly(*authorized_pubkey, true),
371 ];
372
373 Instruction::new_with_bincode(id(), &StakeInstruction::Split(carats), account_metas)
374}
375
376pub fn split(
377 stake_pubkey: &Pubkey,
378 authorized_pubkey: &Pubkey,
379 carats: u64,
380 split_stake_pubkey: &Pubkey,
381) -> Vec<Instruction> {
382 vec![
383 system_instruction::allocate(split_stake_pubkey, std::mem::size_of::<StakeState>() as u64),
384 system_instruction::assign(split_stake_pubkey, &id()),
385 _split(
386 stake_pubkey,
387 authorized_pubkey,
388 carats,
389 split_stake_pubkey,
390 ),
391 ]
392}
393
394pub fn split_with_seed(
395 stake_pubkey: &Pubkey,
396 authorized_pubkey: &Pubkey,
397 carats: u64,
398 split_stake_pubkey: &Pubkey, base: &Pubkey, seed: &str, ) -> Vec<Instruction> {
402 vec![
403 system_instruction::allocate_with_seed(
404 split_stake_pubkey,
405 base,
406 seed,
407 std::mem::size_of::<StakeState>() as u64,
408 &id(),
409 ),
410 _split(
411 stake_pubkey,
412 authorized_pubkey,
413 carats,
414 split_stake_pubkey,
415 ),
416 ]
417}
418
419pub fn merge(
420 destination_stake_pubkey: &Pubkey,
421 source_stake_pubkey: &Pubkey,
422 authorized_pubkey: &Pubkey,
423) -> Vec<Instruction> {
424 let account_metas = vec![
425 AccountMeta::new(*destination_stake_pubkey, false),
426 AccountMeta::new(*source_stake_pubkey, false),
427 AccountMeta::new_readonly(sysvar::clock::id(), false),
428 AccountMeta::new_readonly(sysvar::stake_history::id(), false),
429 AccountMeta::new_readonly(*authorized_pubkey, true),
430 ];
431
432 vec![Instruction::new_with_bincode(
433 id(),
434 &StakeInstruction::Merge,
435 account_metas,
436 )]
437}
438
439pub fn create_account_and_delegate_stake(
440 from_pubkey: &Pubkey,
441 stake_pubkey: &Pubkey,
442 vote_pubkey: &Pubkey,
443 authorized: &Authorized,
444 lockup: &Lockup,
445 carats: u64,
446) -> Vec<Instruction> {
447 let mut instructions = create_account(from_pubkey, stake_pubkey, authorized, lockup, carats);
448 instructions.push(delegate_stake(
449 stake_pubkey,
450 &authorized.staker,
451 vote_pubkey,
452 ));
453 instructions
454}
455
456pub fn create_account_with_seed_and_delegate_stake(
457 from_pubkey: &Pubkey,
458 stake_pubkey: &Pubkey,
459 base: &Pubkey,
460 seed: &str,
461 vote_pubkey: &Pubkey,
462 authorized: &Authorized,
463 lockup: &Lockup,
464 carats: u64,
465) -> Vec<Instruction> {
466 let mut instructions = create_account_with_seed(
467 from_pubkey,
468 stake_pubkey,
469 base,
470 seed,
471 authorized,
472 lockup,
473 carats,
474 );
475 instructions.push(delegate_stake(
476 stake_pubkey,
477 &authorized.staker,
478 vote_pubkey,
479 ));
480 instructions
481}
482
483pub fn authorize(
484 stake_pubkey: &Pubkey,
485 authorized_pubkey: &Pubkey,
486 new_authorized_pubkey: &Pubkey,
487 stake_authorize: StakeAuthorize,
488 custodian_pubkey: Option<&Pubkey>,
489) -> Instruction {
490 let mut account_metas = vec![
491 AccountMeta::new(*stake_pubkey, false),
492 AccountMeta::new_readonly(sysvar::clock::id(), false),
493 AccountMeta::new_readonly(*authorized_pubkey, true),
494 ];
495
496 if let Some(custodian_pubkey) = custodian_pubkey {
497 account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
498 }
499
500 Instruction::new_with_bincode(
501 id(),
502 &StakeInstruction::Authorize(*new_authorized_pubkey, stake_authorize),
503 account_metas,
504 )
505}
506
507pub fn authorize_checked(
508 stake_pubkey: &Pubkey,
509 authorized_pubkey: &Pubkey,
510 new_authorized_pubkey: &Pubkey,
511 stake_authorize: StakeAuthorize,
512 custodian_pubkey: Option<&Pubkey>,
513) -> Instruction {
514 let mut account_metas = vec![
515 AccountMeta::new(*stake_pubkey, false),
516 AccountMeta::new_readonly(sysvar::clock::id(), false),
517 AccountMeta::new_readonly(*authorized_pubkey, true),
518 AccountMeta::new_readonly(*new_authorized_pubkey, true),
519 ];
520
521 if let Some(custodian_pubkey) = custodian_pubkey {
522 account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
523 }
524
525 Instruction::new_with_bincode(
526 id(),
527 &StakeInstruction::AuthorizeChecked(stake_authorize),
528 account_metas,
529 )
530}
531
532pub fn authorize_with_seed(
533 stake_pubkey: &Pubkey,
534 authority_base: &Pubkey,
535 authority_seed: String,
536 authority_owner: &Pubkey,
537 new_authorized_pubkey: &Pubkey,
538 stake_authorize: StakeAuthorize,
539 custodian_pubkey: Option<&Pubkey>,
540) -> Instruction {
541 let mut account_metas = vec![
542 AccountMeta::new(*stake_pubkey, false),
543 AccountMeta::new_readonly(*authority_base, true),
544 AccountMeta::new_readonly(sysvar::clock::id(), false),
545 ];
546
547 if let Some(custodian_pubkey) = custodian_pubkey {
548 account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
549 }
550
551 let args = AuthorizeWithSeedArgs {
552 new_authorized_pubkey: *new_authorized_pubkey,
553 stake_authorize,
554 authority_seed,
555 authority_owner: *authority_owner,
556 };
557
558 Instruction::new_with_bincode(
559 id(),
560 &StakeInstruction::AuthorizeWithSeed(args),
561 account_metas,
562 )
563}
564
565pub fn authorize_checked_with_seed(
566 stake_pubkey: &Pubkey,
567 authority_base: &Pubkey,
568 authority_seed: String,
569 authority_owner: &Pubkey,
570 new_authorized_pubkey: &Pubkey,
571 stake_authorize: StakeAuthorize,
572 custodian_pubkey: Option<&Pubkey>,
573) -> Instruction {
574 let mut account_metas = vec![
575 AccountMeta::new(*stake_pubkey, false),
576 AccountMeta::new_readonly(*authority_base, true),
577 AccountMeta::new_readonly(sysvar::clock::id(), false),
578 AccountMeta::new_readonly(*new_authorized_pubkey, true),
579 ];
580
581 if let Some(custodian_pubkey) = custodian_pubkey {
582 account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
583 }
584
585 let args = AuthorizeCheckedWithSeedArgs {
586 stake_authorize,
587 authority_seed,
588 authority_owner: *authority_owner,
589 };
590
591 Instruction::new_with_bincode(
592 id(),
593 &StakeInstruction::AuthorizeCheckedWithSeed(args),
594 account_metas,
595 )
596}
597
598pub fn delegate_stake(
599 stake_pubkey: &Pubkey,
600 authorized_pubkey: &Pubkey,
601 vote_pubkey: &Pubkey,
602) -> Instruction {
603 let account_metas = vec![
604 AccountMeta::new(*stake_pubkey, false),
605 AccountMeta::new_readonly(*vote_pubkey, false),
606 AccountMeta::new_readonly(sysvar::clock::id(), false),
607 AccountMeta::new_readonly(sysvar::stake_history::id(), false),
608 AccountMeta::new_readonly(config::id(), false),
609 AccountMeta::new_readonly(*authorized_pubkey, true),
610 ];
611 Instruction::new_with_bincode(id(), &StakeInstruction::DelegateStake, account_metas)
612}
613
614pub fn withdraw(
615 stake_pubkey: &Pubkey,
616 withdrawer_pubkey: &Pubkey,
617 to_pubkey: &Pubkey,
618 carats: u64,
619 custodian_pubkey: Option<&Pubkey>,
620) -> Instruction {
621 let mut account_metas = vec![
622 AccountMeta::new(*stake_pubkey, false),
623 AccountMeta::new(*to_pubkey, false),
624 AccountMeta::new_readonly(sysvar::clock::id(), false),
625 AccountMeta::new_readonly(sysvar::stake_history::id(), false),
626 AccountMeta::new_readonly(*withdrawer_pubkey, true),
627 ];
628
629 if let Some(custodian_pubkey) = custodian_pubkey {
630 account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
631 }
632
633 Instruction::new_with_bincode(id(), &StakeInstruction::Withdraw(carats), account_metas)
634}
635
636pub fn deactivate_stake(stake_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction {
637 let account_metas = vec![
638 AccountMeta::new(*stake_pubkey, false),
639 AccountMeta::new_readonly(sysvar::clock::id(), false),
640 AccountMeta::new_readonly(*authorized_pubkey, true),
641 ];
642 Instruction::new_with_bincode(id(), &StakeInstruction::Deactivate, account_metas)
643}
644
645pub fn set_lockup(
646 stake_pubkey: &Pubkey,
647 lockup: &LockupArgs,
648 custodian_pubkey: &Pubkey,
649) -> Instruction {
650 let account_metas = vec![
651 AccountMeta::new(*stake_pubkey, false),
652 AccountMeta::new_readonly(*custodian_pubkey, true),
653 ];
654 Instruction::new_with_bincode(id(), &StakeInstruction::SetLockup(*lockup), account_metas)
655}
656
657pub fn set_lockup_checked(
658 stake_pubkey: &Pubkey,
659 lockup: &LockupArgs,
660 custodian_pubkey: &Pubkey,
661) -> Instruction {
662 let mut account_metas = vec![
663 AccountMeta::new(*stake_pubkey, false),
664 AccountMeta::new_readonly(*custodian_pubkey, true),
665 ];
666
667 let lockup_checked = LockupCheckedArgs {
668 unix_timestamp: lockup.unix_timestamp,
669 epoch: lockup.epoch,
670 };
671 if let Some(new_custodian) = lockup.custodian {
672 account_metas.push(AccountMeta::new_readonly(new_custodian, true));
673 }
674 Instruction::new_with_bincode(
675 id(),
676 &StakeInstruction::SetLockupChecked(lockup_checked),
677 account_metas,
678 )
679}
680
681#[cfg(test)]
682mod tests {
683 use super::*;
684 use crate::instruction::InstructionError;
685
686 #[test]
687 fn test_custom_error_decode() {
688 use num_traits::FromPrimitive;
689 fn pretty_err<T>(err: InstructionError) -> String
690 where
691 T: 'static + std::error::Error + DecodeError<T> + FromPrimitive,
692 {
693 if let InstructionError::Custom(code) = err {
694 let specific_error: T = T::decode_custom_error_to_enum(code).unwrap();
695 format!(
696 "{:?}: {}::{:?} - {}",
697 err,
698 T::type_of(),
699 specific_error,
700 specific_error,
701 )
702 } else {
703 "".to_string()
704 }
705 }
706 assert_eq!(
707 "Custom(0): StakeError::NoCreditsToRedeem - not enough credits to redeem",
708 pretty_err::<StakeError>(StakeError::NoCreditsToRedeem.into())
709 )
710 }
711}