marinade_sdk/
validator_system.rs

1//use std::convert::TryInto;
2
3use borsh::{BorshSerialize, BorshDeserialize};
4use solana_program::{pubkey::Pubkey, program_error::ProgramError, msg, account_info::AccountInfo, entrypoint::ProgramResult};
5use crate::{calc::proportional, checks::check_address, error::CommonError, list::List, ID};
6
7
8#[derive(Clone, Copy, Debug, Default, PartialEq, BorshSerialize, BorshDeserialize)]
9pub struct ValidatorRecord {
10    /// Validator vote pubkey
11    pub validator_account: Pubkey,
12
13    /// Validator total balance in lamports
14    pub active_balance: u64, // must be 0 for removing
15    pub score: u32,
16    pub last_stake_delta_epoch: u64,
17    pub duplication_flag_bump_seed: u8,
18}
19
20impl ValidatorRecord {
21    pub const DISCRIMINATOR: &'static [u8; 8] = b"validatr";
22    pub const DUPLICATE_FLAG_SEED: &'static [u8] = b"unique_validator";
23
24    pub fn find_duplication_flag(state: &Pubkey, validator_account: &Pubkey) -> (Pubkey, u8) {
25        Pubkey::find_program_address(
26            &[
27                &state.to_bytes()[..32],
28                Self::DUPLICATE_FLAG_SEED,
29                &validator_account.to_bytes()[..32],
30            ],
31            &ID,
32        )
33    }
34
35    pub fn with_duplication_flag_seeds<R, F: FnOnce(&[&[u8]]) -> R>(
36        &self,
37        state: &Pubkey,
38        f: F,
39    ) -> R {
40        f(&[
41            &state.to_bytes()[..32],
42            Self::DUPLICATE_FLAG_SEED,
43            &self.validator_account.to_bytes()[..32],
44            &[self.duplication_flag_bump_seed],
45        ])
46    }
47
48    pub fn duplication_flag_address(&self, state: &Pubkey) -> Pubkey {
49        self.with_duplication_flag_seeds(state, |seeds| Pubkey::create_program_address(seeds, &ID))
50            .unwrap()
51    }
52
53    pub fn new(
54        validator_account: Pubkey,
55        score: u32,
56        state: &Pubkey,
57        duplication_flag_address: &Pubkey,
58    ) -> Result<Self, ProgramError> {
59        let (actual_duplication_flag, duplication_flag_bump_seed) =
60            Self::find_duplication_flag(state, &validator_account);
61        if duplication_flag_address != &actual_duplication_flag {
62            msg!(
63                "Duplication flag {} does not match {}",
64                duplication_flag_address,
65                actual_duplication_flag
66            );
67            return Err(ProgramError::InvalidArgument);
68        }
69        Ok(Self {
70            validator_account,
71            active_balance: 0,
72            score,
73            last_stake_delta_epoch: std::u64::MAX, // never
74            duplication_flag_bump_seed,
75        })
76    }
77}
78
79#[derive(Clone, BorshSerialize, BorshDeserialize, Debug)]
80pub struct ValidatorSystem {
81    pub validator_list: List,
82    pub manager_authority: Pubkey,
83    pub total_validator_score: u32,
84    /// sum of all active lamports staked
85    pub total_active_balance: u64,
86    /// allow & auto-add validator when a user deposits a stake-account of a non-listed validator
87    pub auto_add_validator_enabled: u8,
88}
89
90impl ValidatorSystem {
91    pub fn bytes_for_list(count: u32, additional_record_space: u32) -> u32 {
92        List::bytes_for(
93            ValidatorRecord::default().try_to_vec().unwrap().len() as u32 + additional_record_space,
94            count,
95        )
96    }
97
98    pub fn validator_list_address(&self) -> &Pubkey {
99        &self.validator_list.account
100    }
101
102    pub fn validator_count(&self) -> u32 {
103        self.validator_list.len()
104    }
105
106    pub fn validator_list_capacity(&self, validator_list_len: usize) -> Result<u32, ProgramError> {
107        self.validator_list.capacity(validator_list_len)
108    }
109
110    pub fn validator_record_size(&self) -> u32 {
111        self.validator_list.item_size()
112    }
113
114    pub fn get(
115        &self,
116        validator_list_data: &[u8],
117        index: u32,
118    ) -> Result<ValidatorRecord, ProgramError> {
119        self.validator_list
120            .get(validator_list_data, index, "validator_list")
121    }
122
123    pub fn validator_stake_target(
124        &self,
125        validator: &ValidatorRecord,
126        total_stake_target: u64,
127    ) -> Result<u64, CommonError> {
128        if self.total_validator_score == 0 {
129            return Ok(0);
130        }
131        proportional(
132            total_stake_target,
133            validator.score as u64,
134            self.total_validator_score as u64,
135        )
136    }
137
138    pub fn check_validator_list<'info>(
139        &self,
140        validator_list: &AccountInfo<'info>,
141    ) -> ProgramResult {
142        check_address(
143            validator_list.key,
144            self.validator_list_address(),
145            "validator_list",
146        )?;
147        if &validator_list.data.borrow().as_ref()[0..8] != ValidatorRecord::DISCRIMINATOR {
148            msg!("Wrong validator list account discriminator");
149            return Err(ProgramError::InvalidAccountData);
150        }
151        Ok(())
152    }
153
154    pub fn check_validator_manager_authority(&self, manager_authority: &Pubkey) -> ProgramResult {
155        check_address(
156            manager_authority,
157            &self.manager_authority,
158            "validator_manager_authority",
159        )
160    }
161}