use bytemuck::{Pod, Zeroable};
use jito_bytemuck::{types::PodU64, AccountDeserialize, Discriminator};
use jito_jsm_core::slot_toggle::SlotToggle;
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 NcnVaultSlasherTicket {
pub ncn: Pubkey,
pub vault: Pubkey,
pub slasher: Pubkey,
max_slashable_per_epoch: PodU64,
index: PodU64,
pub state: SlotToggle,
pub bump: u8,
reserved: [u8; 263],
}
impl NcnVaultSlasherTicket {
pub fn new(
ncn: Pubkey,
vault: Pubkey,
slasher: Pubkey,
max_slashable_per_epoch: u64,
index: u64,
bump: u8,
slot: u64,
) -> Self {
Self {
ncn,
vault,
slasher,
max_slashable_per_epoch: PodU64::from(max_slashable_per_epoch),
index: PodU64::from(index),
state: SlotToggle::new(slot),
bump,
reserved: [0; RESERVED_SPACE_LEN],
}
}
pub fn index(&self) -> u64 {
self.index.into()
}
pub fn max_slashable_per_epoch(&self) -> u64 {
self.max_slashable_per_epoch.into()
}
pub fn seeds(ncn: &Pubkey, vault: &Pubkey, slasher: &Pubkey) -> Vec<Vec<u8>> {
Vec::from_iter([
b"ncn_slasher_ticket".to_vec(),
ncn.as_ref().to_vec(),
vault.as_ref().to_vec(),
slasher.as_ref().to_vec(),
])
}
pub fn find_program_address(
program_id: &Pubkey,
ncn: &Pubkey,
vault: &Pubkey,
slasher: &Pubkey,
) -> (Pubkey, u8, Vec<Vec<u8>>) {
let seeds = Self::seeds(ncn, vault, slasher);
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,
ncn_vault_slasher_ticket: &AccountInfo,
ncn: &AccountInfo,
vault: &AccountInfo,
slasher: &AccountInfo,
expect_writable: bool,
) -> Result<(), ProgramError> {
if ncn_vault_slasher_ticket.owner.ne(program_id) {
msg!("NCN vault slasher ticket account has an invalid owner");
return Err(ProgramError::InvalidAccountOwner);
}
if ncn_vault_slasher_ticket.data_is_empty() {
msg!("NCN vault slasher ticket account data is empty");
return Err(ProgramError::InvalidAccountData);
}
if expect_writable && !ncn_vault_slasher_ticket.is_writable {
msg!("NCN vault slasher ticket account is not writable");
return Err(ProgramError::InvalidAccountData);
}
if ncn_vault_slasher_ticket.data.borrow()[0].ne(&Self::DISCRIMINATOR) {
msg!("NCN vault slasher ticket account discriminator is invalid");
return Err(ProgramError::InvalidAccountData);
}
let expected_pubkey =
Self::find_program_address(program_id, ncn.key, vault.key, slasher.key).0;
if ncn_vault_slasher_ticket.key.ne(&expected_pubkey) {
msg!("NCN vault slasher ticket account is not at the correct PDA");
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use jito_jsm_core::slot_toggle::SlotToggleState;
use super::*;
#[test]
fn test_ncn_vault_slasher_ticket_no_padding() {
let ncn_vault_slasher_ticket_size = std::mem::size_of::<NcnVaultSlasherTicket>();
let sum_of_fields = size_of::<Pubkey>() + size_of::<Pubkey>() + size_of::<Pubkey>() + size_of::<PodU64>() + size_of::<PodU64>() + size_of::<SlotToggle>() + size_of::<u8>() + RESERVED_SPACE_LEN; assert_eq!(ncn_vault_slasher_ticket_size, sum_of_fields);
}
#[test]
fn test_ncn_vault_slasher_ticket_inactive_on_creation() {
let slot = 1;
let ncn_vault_slasher_ticket = NcnVaultSlasherTicket::new(
Pubkey::default(),
Pubkey::default(),
Pubkey::default(),
0,
0,
0,
slot,
);
assert_eq!(
ncn_vault_slasher_ticket.state.state(slot + 1, 100).unwrap(),
SlotToggleState::Inactive
);
}
}