use bytemuck::{Pod, Zeroable};
use jito_bytemuck::{types::PodU64, AccountDeserialize, Discriminator};
use jito_restaking_sdk::error::RestakingError;
use shank::ShankAccount;
use solana_program::{
account_info::AccountInfo, clock::DEFAULT_SLOTS_PER_EPOCH, msg, program_error::ProgramError,
pubkey::Pubkey,
};
const RESERVED_SPACE_LEN: usize = 263;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Pod, Zeroable, AccountDeserialize, ShankAccount)]
#[repr(C)]
pub struct Config {
pub admin: Pubkey,
pub vault_program: Pubkey,
ncn_count: PodU64,
operator_count: PodU64,
epoch_length: PodU64,
pub bump: u8,
reserved: [u8; 263],
}
impl Config {
pub fn new(admin: Pubkey, vault_program: Pubkey, bump: u8) -> Self {
Self {
admin,
vault_program,
epoch_length: PodU64::from(DEFAULT_SLOTS_PER_EPOCH),
ncn_count: PodU64::from(0),
operator_count: PodU64::from(0),
bump,
reserved: [0; RESERVED_SPACE_LEN],
}
}
pub fn epoch_length(&self) -> u64 {
self.epoch_length.into()
}
pub fn ncn_count(&self) -> u64 {
self.ncn_count.into()
}
pub fn operator_count(&self) -> u64 {
self.operator_count.into()
}
pub fn increment_ncn_count(&mut self) -> Result<(), RestakingError> {
let ncn_count = self
.ncn_count()
.checked_add(1)
.ok_or(RestakingError::NcnOverflow)?;
self.ncn_count = PodU64::from(ncn_count);
Ok(())
}
pub fn increment_operator_count(&mut self) -> Result<(), RestakingError> {
let operator_count = self
.operator_count()
.checked_add(1)
.ok_or(RestakingError::OperatorOverflow)?;
self.operator_count = PodU64::from(operator_count);
Ok(())
}
pub fn seeds() -> Vec<Vec<u8>> {
vec![b"config".to_vec()]
}
pub fn find_program_address(program_id: &Pubkey) -> (Pubkey, u8, Vec<Vec<u8>>) {
let seeds = Self::seeds();
let seeds_iter: Vec<_> = seeds.iter().map(|s| s.as_slice()).collect();
let (pda, bump) = Pubkey::find_program_address(&seeds_iter, program_id);
(pda, bump, seeds)
}
pub fn load(
program_id: &Pubkey,
account: &AccountInfo,
expect_writable: bool,
) -> Result<(), ProgramError> {
if account.owner.ne(program_id) {
msg!("Config account has an invalid owner");
return Err(ProgramError::InvalidAccountOwner);
}
if account.data_is_empty() {
msg!("Config account data is empty");
return Err(ProgramError::InvalidAccountData);
}
if expect_writable && !account.is_writable {
msg!("Config account is not writable");
return Err(ProgramError::InvalidAccountData);
}
if account.data.borrow()[0].ne(&Self::DISCRIMINATOR) {
msg!("Config account discriminator is invalid");
return Err(ProgramError::InvalidAccountData);
}
if account.key.ne(&Self::find_program_address(program_id).0) {
msg!("Config account is not at the correct PDA");
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
pub fn set_admin(&mut self, new_admin: Pubkey) {
self.admin = new_admin;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_no_padding() {
let config_size = std::mem::size_of::<Config>();
let sum_of_fields = std::mem::size_of::<Pubkey>() + std::mem::size_of::<Pubkey>() + std::mem::size_of::<PodU64>() + std::mem::size_of::<PodU64>() + std::mem::size_of::<PodU64>() + std::mem::size_of::<u8>() + RESERVED_SPACE_LEN; assert_eq!(config_size, sum_of_fields);
}
}