Skip to main content

jito_restaking_core/
config.rs

1//! Global configuration account for the restaking program
2
3use bytemuck::{Pod, Zeroable};
4use jito_bytemuck::{types::PodU64, AccountDeserialize, Discriminator};
5use jito_restaking_sdk::error::RestakingError;
6use shank::ShankAccount;
7use solana_program::{
8    account_info::AccountInfo, clock::DEFAULT_SLOTS_PER_EPOCH, msg, program_error::ProgramError,
9    pubkey::Pubkey,
10};
11
12const RESERVED_SPACE_LEN: usize = 263;
13
14/// The global configuration account for the restaking program. Manages
15/// program-wide settings and state.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Pod, Zeroable, AccountDeserialize, ShankAccount)]
17#[repr(C)]
18pub struct Config {
19    /// The configuration admin
20    pub admin: Pubkey,
21
22    /// The vault program
23    pub vault_program: Pubkey,
24
25    /// The number of NCN managed by the program
26    ncn_count: PodU64,
27
28    /// The number of operators managed by the program
29    operator_count: PodU64,
30
31    /// The length of an epoch in slots
32    epoch_length: PodU64,
33
34    /// The bump seed for the PDA
35    pub bump: u8,
36
37    /// Reserved space
38    reserved: [u8; 263],
39}
40
41impl Config {
42    pub fn new(admin: Pubkey, vault_program: Pubkey, bump: u8) -> Self {
43        Self {
44            admin,
45            vault_program,
46            epoch_length: PodU64::from(DEFAULT_SLOTS_PER_EPOCH),
47            ncn_count: PodU64::from(0),
48            operator_count: PodU64::from(0),
49            bump,
50            reserved: [0; RESERVED_SPACE_LEN],
51        }
52    }
53
54    pub fn epoch_length(&self) -> u64 {
55        self.epoch_length.into()
56    }
57
58    pub fn ncn_count(&self) -> u64 {
59        self.ncn_count.into()
60    }
61
62    pub fn operator_count(&self) -> u64 {
63        self.operator_count.into()
64    }
65
66    pub fn increment_ncn_count(&mut self) -> Result<(), RestakingError> {
67        let ncn_count = self
68            .ncn_count()
69            .checked_add(1)
70            .ok_or(RestakingError::NcnOverflow)?;
71        self.ncn_count = PodU64::from(ncn_count);
72        Ok(())
73    }
74
75    pub fn increment_operator_count(&mut self) -> Result<(), RestakingError> {
76        let operator_count = self
77            .operator_count()
78            .checked_add(1)
79            .ok_or(RestakingError::OperatorOverflow)?;
80        self.operator_count = PodU64::from(operator_count);
81        Ok(())
82    }
83
84    /// Returns the seeds for the PDA
85    pub fn seeds() -> Vec<Vec<u8>> {
86        vec![b"config".to_vec()]
87    }
88
89    /// Find the program address for the global configuration account
90    ///
91    /// # Arguments
92    /// * `program_id` - The program ID
93    /// # Returns
94    /// * `Pubkey` - The program address
95    /// * `u8` - The bump seed
96    /// * `Vec<Vec<u8>>` - The seeds used to generate the PDA
97    pub fn find_program_address(program_id: &Pubkey) -> (Pubkey, u8, Vec<Vec<u8>>) {
98        let seeds = Self::seeds();
99        let seeds_iter: Vec<_> = seeds.iter().map(|s| s.as_slice()).collect();
100        let (pda, bump) = Pubkey::find_program_address(&seeds_iter, program_id);
101        (pda, bump, seeds)
102    }
103
104    /// Attempts to load the account as [`Config`], returning an error if it's not valid.
105    ///
106    /// # Arguments
107    /// * `program_id` - The program ID
108    /// * `account` - The account to load the configuration from
109    /// * `expect_writable` - Whether the account should be writable
110    ///
111    /// # Returns
112    /// * `Result<(), ProgramError>` - The result of the operation
113    pub fn load(
114        program_id: &Pubkey,
115        account: &AccountInfo,
116        expect_writable: bool,
117    ) -> Result<(), ProgramError> {
118        if account.owner.ne(program_id) {
119            msg!("Config account has an invalid owner");
120            return Err(ProgramError::InvalidAccountOwner);
121        }
122        if account.data_is_empty() {
123            msg!("Config account data is empty");
124            return Err(ProgramError::InvalidAccountData);
125        }
126        if expect_writable && !account.is_writable {
127            msg!("Config account is not writable");
128            return Err(ProgramError::InvalidAccountData);
129        }
130        if account.data.borrow()[0].ne(&Self::DISCRIMINATOR) {
131            msg!("Config account discriminator is invalid");
132            return Err(ProgramError::InvalidAccountData);
133        }
134        if account.key.ne(&Self::find_program_address(program_id).0) {
135            msg!("Config account is not at the correct PDA");
136            return Err(ProgramError::InvalidAccountData);
137        }
138        Ok(())
139    }
140
141    pub fn set_admin(&mut self, new_admin: Pubkey) {
142        self.admin = new_admin;
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn test_config_no_padding() {
152        let config_size = std::mem::size_of::<Config>();
153        let sum_of_fields = std::mem::size_of::<Pubkey>() + // admin
154            std::mem::size_of::<Pubkey>() + // vault_program
155            std::mem::size_of::<PodU64>() + // ncn_count
156            std::mem::size_of::<PodU64>() + // operator_count
157            std::mem::size_of::<PodU64>() + // epoch_length
158            std::mem::size_of::<u8>() + // bump
159            RESERVED_SPACE_LEN; // reserved
160        assert_eq!(config_size, sum_of_fields);
161    }
162}