marinade_sdk/
stake_system.rs

1use borsh::{BorshSerialize, BorshDeserialize};
2use solana_program::{pubkey::Pubkey, msg, account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError};
3use crate::{ID, checks::check_address, list::List, located::Located, state::State};
4
5
6
7#[derive(Clone, Copy, Debug, Default, PartialEq, BorshSerialize, BorshDeserialize)]
8pub struct StakeRecord {
9    pub stake_account: Pubkey,
10    pub last_update_delegated_lamports: u64,
11    pub last_update_epoch: u64,
12    pub is_emergency_unstaking: u8, // 1 for cooling down after emergency unstake, 0 otherwise
13}
14
15impl StakeRecord {
16    pub const DISCRIMINATOR: &'static [u8; 8] = b"staker__";
17}
18
19#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)]
20pub struct StakeSystem {
21    pub stake_list: List,
22    //pub last_update_epoch: u64,
23    //pub updated_during_last_epoch: u32,
24    pub delayed_unstake_cooling_down: u64,
25    pub stake_deposit_bump_seed: u8,
26    pub stake_withdraw_bump_seed: u8,
27
28    /// set by admin, how much slots before the end of the epoch, stake-delta can start
29    pub slots_for_stake_delta: u64,
30    /// Marks the start of stake-delta operations, meaning that if somebody starts a delayed-unstake ticket
31    /// after this var is set with epoch_num the ticket will have epoch_created = current_epoch+1
32    /// (the user must wait one more epoch, because their unstake-delta will be execute in this epoch)
33    pub last_stake_delta_epoch: u64,
34    pub min_stake: u64, // Minimal stake account delegation
35    /// can be set by validator-manager-auth to allow a second run of stake-delta to stake late stakers in the last minute of the epoch
36    /// so we maximize user's rewards
37    pub extra_stake_delta_runs: u32,
38}
39
40impl StakeSystem {
41    pub const STAKE_WITHDRAW_SEED: &'static [u8] = b"withdraw";
42    pub const STAKE_DEPOSIT_SEED: &'static [u8] = b"deposit";
43
44    pub fn bytes_for_list(count: u32, additional_record_space: u32) -> u32 {
45        List::bytes_for(
46            StakeRecord::default().try_to_vec().unwrap().len() as u32 + additional_record_space,
47            count,
48        )
49    }
50
51    pub fn find_stake_withdraw_authority(state: &Pubkey) -> (Pubkey, u8) {
52        Pubkey::find_program_address(&[&state.to_bytes()[..32], Self::STAKE_WITHDRAW_SEED], &ID)
53    }
54
55    pub fn find_stake_deposit_authority(state: &Pubkey) -> (Pubkey, u8) {
56        Pubkey::find_program_address(&[&state.to_bytes()[..32], Self::STAKE_DEPOSIT_SEED], &ID)
57    }
58
59    pub fn stake_list_address(&self) -> &Pubkey {
60        &self.stake_list.account
61    }
62
63    pub fn stake_count(&self) -> u32 {
64        self.stake_list.len()
65    }
66
67    pub fn stake_list_capacity(&self, stake_list_len: usize) -> Result<u32, ProgramError> {
68        self.stake_list.capacity(stake_list_len)
69    }
70
71    pub fn stake_record_size(&self) -> u32 {
72        self.stake_list.item_size()
73    }
74
75    pub fn get(&self, stake_list_data: &[u8], index: u32) -> Result<StakeRecord, ProgramError> {
76        self.stake_list.get(stake_list_data, index, "stake_list")
77    }
78
79    pub fn check_stake_list<'info>(&self, stake_list: &AccountInfo<'info>) -> ProgramResult {
80        check_address(stake_list.key, self.stake_list_address(), "stake_list")?;
81        if &stake_list.data.borrow().as_ref()[0..8] != StakeRecord::DISCRIMINATOR {
82            msg!("Wrong stake list account discriminator");
83            return Err(ProgramError::InvalidAccountData);
84        }
85        Ok(())
86    }
87}
88
89pub trait StakeSystemHelpers {
90    fn stake_withdraw_authority(&self) -> Pubkey;
91    fn with_stake_withdraw_authority_seeds<R, F: FnOnce(&[&[u8]]) -> R>(&self, f: F) -> R;
92    fn check_stake_withdraw_authority(&self, stake_withdraw_authority: &Pubkey) -> ProgramResult;
93
94    fn stake_deposit_authority(&self) -> Pubkey;
95    fn with_stake_deposit_authority_seeds<R, F: FnOnce(&[&[u8]]) -> R>(&self, f: F) -> R;
96    fn check_stake_deposit_authority(&self, stake_deposit_authority: &Pubkey) -> ProgramResult;
97}
98
99impl<T> StakeSystemHelpers for T
100where
101    T: Located<State>,
102{
103    fn stake_withdraw_authority(&self) -> Pubkey {
104        self.with_stake_withdraw_authority_seeds(|seeds| {
105            Pubkey::create_program_address(seeds, &ID).unwrap()
106        })
107    }
108
109    fn with_stake_withdraw_authority_seeds<R, F: FnOnce(&[&[u8]]) -> R>(&self, f: F) -> R {
110        f(&[
111            &self.key().to_bytes()[..32],
112            StakeSystem::STAKE_WITHDRAW_SEED,
113            &[self.as_ref().stake_system.stake_withdraw_bump_seed],
114        ])
115    }
116
117    fn check_stake_withdraw_authority(&self, stake_withdraw_authority: &Pubkey) -> ProgramResult {
118        check_address(
119            stake_withdraw_authority,
120            &self.stake_withdraw_authority(),
121            "stake_withdraw_authority",
122        )
123    }
124
125    fn stake_deposit_authority(&self) -> Pubkey {
126        self.with_stake_deposit_authority_seeds(|seeds| {
127            Pubkey::create_program_address(seeds, &ID).unwrap()
128        })
129    }
130
131    fn with_stake_deposit_authority_seeds<R, F: FnOnce(&[&[u8]]) -> R>(&self, f: F) -> R {
132        f(&[
133            &self.key().to_bytes()[..32],
134            StakeSystem::STAKE_DEPOSIT_SEED,
135            &[self.as_ref().stake_system.stake_deposit_bump_seed],
136        ])
137    }
138
139    fn check_stake_deposit_authority(&self, stake_deposit_authority: &Pubkey) -> ProgramResult {
140        check_address(
141            stake_deposit_authority,
142            &self.stake_deposit_authority(),
143            "stake_deposit_authority",
144        )
145    }
146}