use std::fmt::Debug;
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, 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 Ncn {
pub base: Pubkey,
pub admin: Pubkey,
pub operator_admin: Pubkey,
pub vault_admin: Pubkey,
pub slasher_admin: Pubkey,
pub delegate_admin: Pubkey,
pub metadata_admin: Pubkey,
pub weight_table_admin: Pubkey,
pub ncn_program_admin: Pubkey,
index: PodU64,
operator_count: PodU64,
vault_count: PodU64,
slasher_count: PodU64,
pub bump: u8,
reserved: [u8; 263],
}
impl Ncn {
#[allow(clippy::too_many_arguments)]
pub fn new(base: Pubkey, admin: Pubkey, ncn_index: u64, bump: u8) -> Self {
Self {
base,
admin,
operator_admin: admin,
vault_admin: admin,
slasher_admin: admin,
delegate_admin: admin,
metadata_admin: admin,
weight_table_admin: admin,
ncn_program_admin: admin,
index: PodU64::from(ncn_index),
operator_count: PodU64::from(0),
vault_count: PodU64::from(0),
slasher_count: PodU64::from(0),
bump,
reserved: [0; RESERVED_SPACE_LEN],
}
}
pub fn index(&self) -> u64 {
self.index.into()
}
pub fn operator_count(&self) -> u64 {
self.operator_count.into()
}
pub fn vault_count(&self) -> u64 {
self.vault_count.into()
}
pub fn slasher_count(&self) -> u64 {
self.slasher_count.into()
}
pub fn increment_operator_count(&mut self) -> Result<(), RestakingError> {
let mut operator_count: u64 = self.operator_count.into();
operator_count = operator_count
.checked_add(1)
.ok_or(RestakingError::OperatorOverflow)?;
self.operator_count = PodU64::from(operator_count);
Ok(())
}
pub fn increment_vault_count(&mut self) -> Result<(), RestakingError> {
let mut vault_count: u64 = self.vault_count.into();
vault_count = vault_count
.checked_add(1)
.ok_or(RestakingError::VaultOverflow)?;
self.vault_count = PodU64::from(vault_count);
Ok(())
}
pub fn increment_slasher_count(&mut self) -> Result<(), RestakingError> {
let mut slasher_count: u64 = self.slasher_count.into();
slasher_count = slasher_count
.checked_add(1)
.ok_or(RestakingError::SlasherOverflow)?;
self.slasher_count = PodU64::from(slasher_count);
Ok(())
}
pub fn check_admin(&self, admin: &Pubkey) -> Result<(), RestakingError> {
if self.admin.ne(admin) {
msg!(
"Incorrect admin provided, expected {}, received {}",
self.admin,
admin
);
return Err(RestakingError::NcnAdminInvalid);
}
Ok(())
}
pub fn check_delegate_admin(&self, delegate_admin: &Pubkey) -> Result<(), RestakingError> {
if self.delegate_admin.ne(delegate_admin) {
msg!(
"Incorrect delegate_admin provided, expected {}, received {}",
self.delegate_admin,
delegate_admin
);
return Err(RestakingError::NcnDelegateAdminInvalid);
}
Ok(())
}
pub fn update_secondary_admin(&mut self, old_admin: &Pubkey, new_admin: &Pubkey) {
if self.operator_admin.eq(old_admin) {
self.operator_admin = *new_admin;
msg!("Operator admin set to {:?}", new_admin);
}
if self.vault_admin.eq(old_admin) {
self.vault_admin = *new_admin;
msg!("Vault admin set to {:?}", new_admin);
}
if self.slasher_admin.eq(old_admin) {
self.slasher_admin = *new_admin;
msg!("Slasher admin set to {:?}", new_admin);
}
if self.delegate_admin.eq(old_admin) {
self.delegate_admin = *new_admin;
msg!("Delegate admin set to {:?}", new_admin);
}
if self.metadata_admin.eq(old_admin) {
self.metadata_admin = *new_admin;
msg!("Metadata admin set to {:?}", new_admin);
}
if self.weight_table_admin.eq(old_admin) {
self.weight_table_admin = *new_admin;
msg!("Weight table admin set to {:?}", new_admin);
}
if self.ncn_program_admin.eq(old_admin) {
self.ncn_program_admin = *new_admin;
msg!("Ncn program admin set to {:?}", new_admin);
}
}
pub fn seeds(base: &Pubkey) -> Vec<Vec<u8>> {
Vec::from_iter([b"ncn".to_vec(), base.as_ref().to_vec()])
}
pub fn find_program_address(program_id: &Pubkey, base: &Pubkey) -> (Pubkey, u8, Vec<Vec<u8>>) {
let seeds = Self::seeds(base);
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!("NCN account has an invalid owner");
return Err(ProgramError::InvalidAccountOwner);
}
if account.data_is_empty() {
msg!("NCN account data is empty");
return Err(ProgramError::InvalidAccountData);
}
if expect_writable && !account.is_writable {
msg!("NCN account is not writable");
return Err(ProgramError::InvalidAccountData);
}
if account.data.borrow()[0].ne(&Self::DISCRIMINATOR) {
msg!("NCN account discriminator is invalid");
return Err(ProgramError::InvalidAccountData);
}
let base = Self::try_from_slice_unchecked(&account.data.borrow())?.base;
if account
.key
.ne(&Self::find_program_address(program_id, &base).0)
{
msg!("NCN account is not at the correct PDA");
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use jito_bytemuck::types::PodU64;
use solana_program::pubkey::Pubkey;
use super::{Ncn, RESERVED_SPACE_LEN};
#[test]
fn test_ncn_no_padding() {
let ncn_size = std::mem::size_of::<Ncn>();
let sum_of_fields = std::mem::size_of::<Pubkey>() + std::mem::size_of::<Pubkey>() + std::mem::size_of::<Pubkey>() + std::mem::size_of::<Pubkey>() + std::mem::size_of::<Pubkey>() + std::mem::size_of::<Pubkey>() + std::mem::size_of::<Pubkey>() + 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::<PodU64>() + std::mem::size_of::<u8>() + RESERVED_SPACE_LEN; assert_eq!(ncn_size, sum_of_fields);
}
#[test]
fn test_update_secondary_admin_ok() {
let old_admin = Pubkey::new_unique();
let mut ncn = Ncn::new(Pubkey::new_unique(), old_admin, 0, 0);
assert_eq!(ncn.operator_admin, old_admin);
assert_eq!(ncn.vault_admin, old_admin);
assert_eq!(ncn.slasher_admin, old_admin);
assert_eq!(ncn.delegate_admin, old_admin);
assert_eq!(ncn.metadata_admin, old_admin);
assert_eq!(ncn.weight_table_admin, old_admin);
assert_eq!(ncn.ncn_program_admin, old_admin);
let new_admin = Pubkey::new_unique();
ncn.update_secondary_admin(&old_admin, &new_admin);
assert_eq!(ncn.operator_admin, new_admin);
assert_eq!(ncn.vault_admin, new_admin);
assert_eq!(ncn.slasher_admin, new_admin);
assert_eq!(ncn.delegate_admin, new_admin);
assert_eq!(ncn.metadata_admin, new_admin);
assert_eq!(ncn.weight_table_admin, new_admin);
assert_eq!(ncn.ncn_program_admin, new_admin);
}
}