use std::fmt::Debug;
use bytemuck::{Pod, Zeroable};
use jito_bytemuck::{
types::{PodU16, PodU64},
AccountDeserialize, Discriminator,
};
use jito_vault_sdk::error::VaultError;
use shank::ShankAccount;
use solana_program::{
account_info::AccountInfo, epoch_schedule::DEFAULT_SLOTS_PER_EPOCH, msg,
program_error::ProgramError, pubkey::Pubkey,
};
use crate::MAX_BPS;
const RESERVED_SPACE_LEN: usize = 229;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Pod, Zeroable, AccountDeserialize, ShankAccount)]
#[repr(C)]
pub struct Config {
pub admin: Pubkey,
pub restaking_program: Pubkey,
epoch_length: PodU64,
num_vaults: PodU64,
deposit_withdrawal_fee_cap_bps: PodU16,
fee_rate_of_change_bps: PodU16,
fee_bump_bps: PodU16,
program_fee_bps: PodU16,
pub program_fee_wallet: Pubkey,
pub fee_admin: Pubkey,
pub bump: u8,
reserved: [u8; 229],
}
impl Config {
pub const DEFAULT_FEES_CAP_BPS: u16 = 2_000; pub const DEFAULT_FEE_RATE_OF_CHANGE_BPS: u16 = 2_500; pub const DEFAULT_FEE_BUMP_BPS: u16 = 10;
pub fn new(
admin: Pubkey,
restaking_program: Pubkey,
program_fee_wallet: Pubkey,
program_fee_bps: u16,
bump: u8,
) -> Self {
Self {
admin,
restaking_program,
epoch_length: PodU64::from(DEFAULT_SLOTS_PER_EPOCH),
num_vaults: PodU64::from(0),
deposit_withdrawal_fee_cap_bps: PodU16::from(Self::DEFAULT_FEES_CAP_BPS),
fee_rate_of_change_bps: PodU16::from(Self::DEFAULT_FEE_RATE_OF_CHANGE_BPS),
fee_bump_bps: PodU16::from(Self::DEFAULT_FEE_BUMP_BPS),
program_fee_bps: PodU16::from(program_fee_bps),
program_fee_wallet,
fee_admin: admin,
bump,
reserved: [0; RESERVED_SPACE_LEN],
}
}
pub fn get_epoch_from_slot(&self, slot: u64) -> Result<u64, VaultError> {
slot.checked_div(self.epoch_length())
.ok_or(VaultError::InvalidEpochLength)
}
pub fn epoch_length(&self) -> u64 {
self.epoch_length.into()
}
pub fn num_vaults(&self) -> u64 {
self.num_vaults.into()
}
pub fn deposit_withdrawal_fee_cap_bps(&self) -> u16 {
u16::from(self.deposit_withdrawal_fee_cap_bps)
}
pub fn fee_rate_of_change_bps(&self) -> u16 {
u16::from(self.fee_rate_of_change_bps)
}
pub fn program_fee_bps(&self) -> u16 {
self.program_fee_bps.into()
}
pub fn set_program_fee_bps(&mut self, new_fee_bps: u16) -> Result<(), ProgramError> {
if new_fee_bps > MAX_BPS {
msg!("New fee exceeds maximum allowed fee");
return Err(ProgramError::InvalidInstructionData);
}
self.program_fee_bps = PodU16::from(new_fee_bps);
Ok(())
}
pub fn fee_bump_bps(&self) -> u16 {
u16::from(self.fee_bump_bps)
}
pub fn set_deposit_withdrawal_fee_cap_bps(
&mut self,
fee_cap_bps: u16,
) -> Result<(), VaultError> {
if fee_cap_bps > MAX_BPS {
return Err(VaultError::VaultFeeCapExceeded);
}
self.deposit_withdrawal_fee_cap_bps = PodU16::from(fee_cap_bps);
Ok(())
}
pub fn set_fee_rate_of_change_bps(
&mut self,
fee_rate_of_change_bps: u16,
) -> Result<(), VaultError> {
if fee_rate_of_change_bps > MAX_BPS {
return Err(VaultError::VaultFeeCapExceeded);
}
self.fee_rate_of_change_bps = PodU16::from(fee_rate_of_change_bps);
Ok(())
}
pub fn set_fee_bump_bps(&mut self, fee_bump_bps: u16) -> Result<(), VaultError> {
if fee_bump_bps > MAX_BPS {
return Err(VaultError::VaultFeeCapExceeded);
}
self.fee_bump_bps = PodU16::from(fee_bump_bps);
Ok(())
}
pub fn increment_num_vaults(&mut self) -> Result<(), VaultError> {
let mut num_vaults: u64 = self.num_vaults.into();
num_vaults = num_vaults.checked_add(1).ok_or(VaultError::VaultOverflow)?;
self.num_vaults = PodU64::from(num_vaults);
Ok(())
}
pub fn calculate_program_fee(program_fee_bps: u16, vrt_amount: u64) -> Result<u64, VaultError> {
let fee = (vrt_amount as u128)
.checked_mul(program_fee_bps as u128)
.map(|x| x.div_ceil(MAX_BPS as u128))
.and_then(|x| x.try_into().ok())
.ok_or(VaultError::VaultOverflow)?;
Ok(fee)
}
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;
self.fee_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::<PodU16>() + std::mem::size_of::<PodU16>() + std::mem::size_of::<PodU16>() + std::mem::size_of::<PodU16>() + std::mem::size_of::<Pubkey>() + std::mem::size_of::<Pubkey>() + std::mem::size_of::<u8>() + RESERVED_SPACE_LEN; assert_eq!(config_size, sum_of_fields);
}
#[test]
fn test_bps_are_within_bounds() {
assert!(Config::DEFAULT_FEES_CAP_BPS <= MAX_BPS);
assert!(Config::DEFAULT_FEE_RATE_OF_CHANGE_BPS <= MAX_BPS);
assert!(Config::DEFAULT_FEE_BUMP_BPS <= MAX_BPS);
}
#[test]
fn test_set_fee_cap_bps() {
let mut config = Config::new(
Pubkey::new_unique(),
Pubkey::new_unique(),
Pubkey::new_unique(),
0,
0,
);
assert_eq!(config.set_deposit_withdrawal_fee_cap_bps(1), Ok(()));
assert_eq!(config.set_deposit_withdrawal_fee_cap_bps(MAX_BPS), Ok(()));
assert_eq!(
config.set_deposit_withdrawal_fee_cap_bps(MAX_BPS + 1),
Err(VaultError::VaultFeeCapExceeded)
);
}
#[test]
fn test_set_fee_rate_of_change_bps() {
let mut config = Config::new(
Pubkey::new_unique(),
Pubkey::new_unique(),
Pubkey::new_unique(),
0,
0,
);
assert_eq!(config.set_fee_rate_of_change_bps(1), Ok(()));
assert_eq!(config.set_fee_rate_of_change_bps(MAX_BPS), Ok(()));
assert_eq!(
config.set_fee_rate_of_change_bps(MAX_BPS + 1),
Err(VaultError::VaultFeeCapExceeded)
);
}
#[test]
fn test_set_fee_bump_bps() {
let mut config = Config::new(
Pubkey::new_unique(),
Pubkey::new_unique(),
Pubkey::new_unique(),
0,
0,
);
assert_eq!(config.set_fee_bump_bps(1), Ok(()));
assert_eq!(config.set_fee_bump_bps(MAX_BPS), Ok(()));
assert_eq!(
config.set_fee_bump_bps(MAX_BPS + 1),
Err(VaultError::VaultFeeCapExceeded)
);
}
}