diff --git a/clients/js/jito_tip_router/accounts/ncnConfig.ts b/clients/js/jito_tip_router/accounts/ncnConfig.ts index 7a2d126b..2d385eb9 100644 --- a/clients/js/jito_tip_router/accounts/ncnConfig.ts +++ b/clients/js/jito_tip_router/accounts/ncnConfig.ts @@ -45,6 +45,7 @@ export type NcnConfig = { discriminator: bigint; ncn: Address; tieBreakerAdmin: Address; + feeAdmin: Address; fees: Fees; bump: number; reserved: Array; @@ -54,6 +55,7 @@ export type NcnConfigArgs = { discriminator: number | bigint; ncn: Address; tieBreakerAdmin: Address; + feeAdmin: Address; fees: FeesArgs; bump: number; reserved: Array; @@ -64,6 +66,7 @@ export function getNcnConfigEncoder(): Encoder { ['discriminator', getU64Encoder()], ['ncn', getAddressEncoder()], ['tieBreakerAdmin', getAddressEncoder()], + ['feeAdmin', getAddressEncoder()], ['fees', getFeesEncoder()], ['bump', getU8Encoder()], ['reserved', getArrayEncoder(getU8Encoder(), { size: 127 })], @@ -75,6 +78,7 @@ export function getNcnConfigDecoder(): Decoder { ['discriminator', getU64Decoder()], ['ncn', getAddressDecoder()], ['tieBreakerAdmin', getAddressDecoder()], + ['feeAdmin', getAddressDecoder()], ['fees', getFeesDecoder()], ['bump', getU8Decoder()], ['reserved', getArrayDecoder(getU8Decoder(), { size: 127 })], @@ -139,5 +143,5 @@ export async function fetchAllMaybeNcnConfig( } export function getNcnConfigSize(): number { - return 320; + return 352; } diff --git a/clients/js/jito_tip_router/errors/jitoTipRouter.ts b/clients/js/jito_tip_router/errors/jitoTipRouter.ts index c8d115a8..e121839e 100644 --- a/clients/js/jito_tip_router/errors/jitoTipRouter.ts +++ b/clients/js/jito_tip_router/errors/jitoTipRouter.ts @@ -32,12 +32,15 @@ export const JITO_TIP_ROUTER_ERROR__FEE_CAP_EXCEEDED = 0x2300; // 8960 export const JITO_TIP_ROUTER_ERROR__INCORRECT_NCN_ADMIN = 0x2400; // 9216 /** IncorrectNcn: Incorrect NCN */ export const JITO_TIP_ROUTER_ERROR__INCORRECT_NCN = 0x2401; // 9217 +/** IncorrectFeeAdmin: Incorrect fee admin */ +export const JITO_TIP_ROUTER_ERROR__INCORRECT_FEE_ADMIN = 0x2402; // 9218 export type JitoTipRouterError = | typeof JITO_TIP_ROUTER_ERROR__ARITHMETIC_OVERFLOW | typeof JITO_TIP_ROUTER_ERROR__CANNOT_CREATE_FUTURE_WEIGHT_TABLES | typeof JITO_TIP_ROUTER_ERROR__DENOMINATOR_IS_ZERO | typeof JITO_TIP_ROUTER_ERROR__FEE_CAP_EXCEEDED + | typeof JITO_TIP_ROUTER_ERROR__INCORRECT_FEE_ADMIN | typeof JITO_TIP_ROUTER_ERROR__INCORRECT_NCN | typeof JITO_TIP_ROUTER_ERROR__INCORRECT_NCN_ADMIN | typeof JITO_TIP_ROUTER_ERROR__INCORRECT_WEIGHT_TABLE_ADMIN @@ -51,6 +54,7 @@ if (process.env.NODE_ENV !== 'production') { [JITO_TIP_ROUTER_ERROR__CANNOT_CREATE_FUTURE_WEIGHT_TABLES]: `Cannnot create future weight tables`, [JITO_TIP_ROUTER_ERROR__DENOMINATOR_IS_ZERO]: `Zero in the denominator`, [JITO_TIP_ROUTER_ERROR__FEE_CAP_EXCEEDED]: `Fee cap exceeded`, + [JITO_TIP_ROUTER_ERROR__INCORRECT_FEE_ADMIN]: `Incorrect fee admin`, [JITO_TIP_ROUTER_ERROR__INCORRECT_NCN]: `Incorrect NCN`, [JITO_TIP_ROUTER_ERROR__INCORRECT_NCN_ADMIN]: `Incorrect NCN Admin`, [JITO_TIP_ROUTER_ERROR__INCORRECT_WEIGHT_TABLE_ADMIN]: `Incorrect weight table admin`, diff --git a/clients/js/jito_tip_router/instructions/initializeConfig.ts b/clients/js/jito_tip_router/instructions/initializeConfig.ts index 1d6ee374..e36327c1 100644 --- a/clients/js/jito_tip_router/instructions/initializeConfig.ts +++ b/clients/js/jito_tip_router/instructions/initializeConfig.ts @@ -39,9 +39,9 @@ export type InitializeConfigInstruction< TProgram extends string = typeof JITO_TIP_ROUTER_PROGRAM_ADDRESS, TAccountConfig extends string | IAccountMeta = string, TAccountNcn extends string | IAccountMeta = string, + TAccountNcnAdmin extends string | IAccountMeta = string, TAccountFeeWallet extends string | IAccountMeta = string, TAccountTieBreakerAdmin extends string | IAccountMeta = string, - TAccountPayer extends string | IAccountMeta = string, TAccountRestakingProgramId extends string | IAccountMeta = string, TAccountSystemProgram extends | string @@ -55,15 +55,15 @@ export type InitializeConfigInstruction< ? WritableAccount : TAccountConfig, TAccountNcn extends string ? ReadonlyAccount : TAccountNcn, + TAccountNcnAdmin extends string + ? WritableAccount + : TAccountNcnAdmin, TAccountFeeWallet extends string ? ReadonlyAccount : TAccountFeeWallet, TAccountTieBreakerAdmin extends string ? ReadonlyAccount : TAccountTieBreakerAdmin, - TAccountPayer extends string - ? WritableAccount - : TAccountPayer, TAccountRestakingProgramId extends string ? ReadonlyAccount : TAccountRestakingProgramId, @@ -121,17 +121,17 @@ export function getInitializeConfigInstructionDataCodec(): Codec< export type InitializeConfigInput< TAccountConfig extends string = string, TAccountNcn extends string = string, + TAccountNcnAdmin extends string = string, TAccountFeeWallet extends string = string, TAccountTieBreakerAdmin extends string = string, - TAccountPayer extends string = string, TAccountRestakingProgramId extends string = string, TAccountSystemProgram extends string = string, > = { config: Address; ncn: Address; + ncnAdmin: Address; feeWallet: Address; tieBreakerAdmin: Address; - payer: Address; restakingProgramId: Address; systemProgram?: Address; daoFeeBps: InitializeConfigInstructionDataArgs['daoFeeBps']; @@ -142,9 +142,9 @@ export type InitializeConfigInput< export function getInitializeConfigInstruction< TAccountConfig extends string, TAccountNcn extends string, + TAccountNcnAdmin extends string, TAccountFeeWallet extends string, TAccountTieBreakerAdmin extends string, - TAccountPayer extends string, TAccountRestakingProgramId extends string, TAccountSystemProgram extends string, TProgramAddress extends Address = typeof JITO_TIP_ROUTER_PROGRAM_ADDRESS, @@ -152,9 +152,9 @@ export function getInitializeConfigInstruction< input: InitializeConfigInput< TAccountConfig, TAccountNcn, + TAccountNcnAdmin, TAccountFeeWallet, TAccountTieBreakerAdmin, - TAccountPayer, TAccountRestakingProgramId, TAccountSystemProgram >, @@ -163,9 +163,9 @@ export function getInitializeConfigInstruction< TProgramAddress, TAccountConfig, TAccountNcn, + TAccountNcnAdmin, TAccountFeeWallet, TAccountTieBreakerAdmin, - TAccountPayer, TAccountRestakingProgramId, TAccountSystemProgram > { @@ -177,12 +177,12 @@ export function getInitializeConfigInstruction< const originalAccounts = { config: { value: input.config ?? null, isWritable: true }, ncn: { value: input.ncn ?? null, isWritable: false }, + ncnAdmin: { value: input.ncnAdmin ?? null, isWritable: true }, feeWallet: { value: input.feeWallet ?? null, isWritable: false }, tieBreakerAdmin: { value: input.tieBreakerAdmin ?? null, isWritable: false, }, - payer: { value: input.payer ?? null, isWritable: true }, restakingProgramId: { value: input.restakingProgramId ?? null, isWritable: false, @@ -208,9 +208,9 @@ export function getInitializeConfigInstruction< accounts: [ getAccountMeta(accounts.config), getAccountMeta(accounts.ncn), + getAccountMeta(accounts.ncnAdmin), getAccountMeta(accounts.feeWallet), getAccountMeta(accounts.tieBreakerAdmin), - getAccountMeta(accounts.payer), getAccountMeta(accounts.restakingProgramId), getAccountMeta(accounts.systemProgram), ], @@ -222,9 +222,9 @@ export function getInitializeConfigInstruction< TProgramAddress, TAccountConfig, TAccountNcn, + TAccountNcnAdmin, TAccountFeeWallet, TAccountTieBreakerAdmin, - TAccountPayer, TAccountRestakingProgramId, TAccountSystemProgram >; @@ -240,9 +240,9 @@ export type ParsedInitializeConfigInstruction< accounts: { config: TAccountMetas[0]; ncn: TAccountMetas[1]; - feeWallet: TAccountMetas[2]; - tieBreakerAdmin: TAccountMetas[3]; - payer: TAccountMetas[4]; + ncnAdmin: TAccountMetas[2]; + feeWallet: TAccountMetas[3]; + tieBreakerAdmin: TAccountMetas[4]; restakingProgramId: TAccountMetas[5]; systemProgram: TAccountMetas[6]; }; @@ -272,9 +272,9 @@ export function parseInitializeConfigInstruction< accounts: { config: getNextAccount(), ncn: getNextAccount(), + ncnAdmin: getNextAccount(), feeWallet: getNextAccount(), tieBreakerAdmin: getNextAccount(), - payer: getNextAccount(), restakingProgramId: getNextAccount(), systemProgram: getNextAccount(), }, diff --git a/clients/rust/jito_tip_router/src/generated/accounts/ncn_config.rs b/clients/rust/jito_tip_router/src/generated/accounts/ncn_config.rs index 8986eaad..88972274 100644 --- a/clients/rust/jito_tip_router/src/generated/accounts/ncn_config.rs +++ b/clients/rust/jito_tip_router/src/generated/accounts/ncn_config.rs @@ -23,6 +23,11 @@ pub struct NcnConfig { serde(with = "serde_with::As::") )] pub tie_breaker_admin: Pubkey, + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub fee_admin: Pubkey, pub fees: Fees, pub bump: u8, #[cfg_attr(feature = "serde", serde(with = "serde_with::As::"))] @@ -30,7 +35,7 @@ pub struct NcnConfig { } impl NcnConfig { - pub const LEN: usize = 320; + pub const LEN: usize = 352; #[inline(always)] pub fn from_bytes(data: &[u8]) -> Result { diff --git a/clients/rust/jito_tip_router/src/generated/errors/jito_tip_router.rs b/clients/rust/jito_tip_router/src/generated/errors/jito_tip_router.rs index 5067aa4e..cfd022b5 100644 --- a/clients/rust/jito_tip_router/src/generated/errors/jito_tip_router.rs +++ b/clients/rust/jito_tip_router/src/generated/errors/jito_tip_router.rs @@ -36,6 +36,9 @@ pub enum JitoTipRouterError { /// 9217 - Incorrect NCN #[error("Incorrect NCN")] IncorrectNcn = 0x2401, + /// 9218 - Incorrect fee admin + #[error("Incorrect fee admin")] + IncorrectFeeAdmin = 0x2402, } impl solana_program::program_error::PrintProgramError for JitoTipRouterError { diff --git a/clients/rust/jito_tip_router/src/generated/instructions/initialize_config.rs b/clients/rust/jito_tip_router/src/generated/instructions/initialize_config.rs index 956575f5..d9cbd3ee 100644 --- a/clients/rust/jito_tip_router/src/generated/instructions/initialize_config.rs +++ b/clients/rust/jito_tip_router/src/generated/instructions/initialize_config.rs @@ -12,12 +12,12 @@ pub struct InitializeConfig { pub ncn: solana_program::pubkey::Pubkey, + pub ncn_admin: solana_program::pubkey::Pubkey, + pub fee_wallet: solana_program::pubkey::Pubkey, pub tie_breaker_admin: solana_program::pubkey::Pubkey, - pub payer: solana_program::pubkey::Pubkey, - pub restaking_program_id: solana_program::pubkey::Pubkey, pub system_program: solana_program::pubkey::Pubkey, @@ -44,6 +44,10 @@ impl InitializeConfig { accounts.push(solana_program::instruction::AccountMeta::new_readonly( self.ncn, false, )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.ncn_admin, + false, + )); accounts.push(solana_program::instruction::AccountMeta::new_readonly( self.fee_wallet, false, @@ -52,9 +56,6 @@ impl InitializeConfig { self.tie_breaker_admin, false, )); - accounts.push(solana_program::instruction::AccountMeta::new( - self.payer, false, - )); accounts.push(solana_program::instruction::AccountMeta::new_readonly( self.restaking_program_id, false, @@ -107,18 +108,18 @@ pub struct InitializeConfigInstructionArgs { /// /// 0. `[writable]` config /// 1. `[]` ncn -/// 2. `[]` fee_wallet -/// 3. `[]` tie_breaker_admin -/// 4. `[writable]` payer +/// 2. `[writable]` ncn_admin +/// 3. `[]` fee_wallet +/// 4. `[]` tie_breaker_admin /// 5. `[]` restaking_program_id /// 6. `[optional]` system_program (default to `11111111111111111111111111111111`) #[derive(Clone, Debug, Default)] pub struct InitializeConfigBuilder { config: Option, ncn: Option, + ncn_admin: Option, fee_wallet: Option, tie_breaker_admin: Option, - payer: Option, restaking_program_id: Option, system_program: Option, dao_fee_bps: Option, @@ -142,6 +143,11 @@ impl InitializeConfigBuilder { self } #[inline(always)] + pub fn ncn_admin(&mut self, ncn_admin: solana_program::pubkey::Pubkey) -> &mut Self { + self.ncn_admin = Some(ncn_admin); + self + } + #[inline(always)] pub fn fee_wallet(&mut self, fee_wallet: solana_program::pubkey::Pubkey) -> &mut Self { self.fee_wallet = Some(fee_wallet); self @@ -155,11 +161,6 @@ impl InitializeConfigBuilder { self } #[inline(always)] - pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { - self.payer = Some(payer); - self - } - #[inline(always)] pub fn restaking_program_id( &mut self, restaking_program_id: solana_program::pubkey::Pubkey, @@ -211,11 +212,11 @@ impl InitializeConfigBuilder { let accounts = InitializeConfig { config: self.config.expect("config is not set"), ncn: self.ncn.expect("ncn is not set"), + ncn_admin: self.ncn_admin.expect("ncn_admin is not set"), fee_wallet: self.fee_wallet.expect("fee_wallet is not set"), tie_breaker_admin: self .tie_breaker_admin .expect("tie_breaker_admin is not set"), - payer: self.payer.expect("payer is not set"), restaking_program_id: self .restaking_program_id .expect("restaking_program_id is not set"), @@ -242,12 +243,12 @@ pub struct InitializeConfigCpiAccounts<'a, 'b> { pub ncn: &'b solana_program::account_info::AccountInfo<'a>, + pub ncn_admin: &'b solana_program::account_info::AccountInfo<'a>, + pub fee_wallet: &'b solana_program::account_info::AccountInfo<'a>, pub tie_breaker_admin: &'b solana_program::account_info::AccountInfo<'a>, - pub payer: &'b solana_program::account_info::AccountInfo<'a>, - pub restaking_program_id: &'b solana_program::account_info::AccountInfo<'a>, pub system_program: &'b solana_program::account_info::AccountInfo<'a>, @@ -262,12 +263,12 @@ pub struct InitializeConfigCpi<'a, 'b> { pub ncn: &'b solana_program::account_info::AccountInfo<'a>, + pub ncn_admin: &'b solana_program::account_info::AccountInfo<'a>, + pub fee_wallet: &'b solana_program::account_info::AccountInfo<'a>, pub tie_breaker_admin: &'b solana_program::account_info::AccountInfo<'a>, - pub payer: &'b solana_program::account_info::AccountInfo<'a>, - pub restaking_program_id: &'b solana_program::account_info::AccountInfo<'a>, pub system_program: &'b solana_program::account_info::AccountInfo<'a>, @@ -285,9 +286,9 @@ impl<'a, 'b> InitializeConfigCpi<'a, 'b> { __program: program, config: accounts.config, ncn: accounts.ncn, + ncn_admin: accounts.ncn_admin, fee_wallet: accounts.fee_wallet, tie_breaker_admin: accounts.tie_breaker_admin, - payer: accounts.payer, restaking_program_id: accounts.restaking_program_id, system_program: accounts.system_program, __args: args, @@ -335,6 +336,10 @@ impl<'a, 'b> InitializeConfigCpi<'a, 'b> { *self.ncn.key, false, )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.ncn_admin.key, + false, + )); accounts.push(solana_program::instruction::AccountMeta::new_readonly( *self.fee_wallet.key, false, @@ -343,10 +348,6 @@ impl<'a, 'b> InitializeConfigCpi<'a, 'b> { *self.tie_breaker_admin.key, false, )); - accounts.push(solana_program::instruction::AccountMeta::new( - *self.payer.key, - false, - )); accounts.push(solana_program::instruction::AccountMeta::new_readonly( *self.restaking_program_id.key, false, @@ -375,9 +376,9 @@ impl<'a, 'b> InitializeConfigCpi<'a, 'b> { account_infos.push(self.__program.clone()); account_infos.push(self.config.clone()); account_infos.push(self.ncn.clone()); + account_infos.push(self.ncn_admin.clone()); account_infos.push(self.fee_wallet.clone()); account_infos.push(self.tie_breaker_admin.clone()); - account_infos.push(self.payer.clone()); account_infos.push(self.restaking_program_id.clone()); account_infos.push(self.system_program.clone()); remaining_accounts @@ -398,9 +399,9 @@ impl<'a, 'b> InitializeConfigCpi<'a, 'b> { /// /// 0. `[writable]` config /// 1. `[]` ncn -/// 2. `[]` fee_wallet -/// 3. `[]` tie_breaker_admin -/// 4. `[writable]` payer +/// 2. `[writable]` ncn_admin +/// 3. `[]` fee_wallet +/// 4. `[]` tie_breaker_admin /// 5. `[]` restaking_program_id /// 6. `[]` system_program #[derive(Clone, Debug)] @@ -414,9 +415,9 @@ impl<'a, 'b> InitializeConfigCpiBuilder<'a, 'b> { __program: program, config: None, ncn: None, + ncn_admin: None, fee_wallet: None, tie_breaker_admin: None, - payer: None, restaking_program_id: None, system_program: None, dao_fee_bps: None, @@ -440,6 +441,14 @@ impl<'a, 'b> InitializeConfigCpiBuilder<'a, 'b> { self } #[inline(always)] + pub fn ncn_admin( + &mut self, + ncn_admin: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.ncn_admin = Some(ncn_admin); + self + } + #[inline(always)] pub fn fee_wallet( &mut self, fee_wallet: &'b solana_program::account_info::AccountInfo<'a>, @@ -456,11 +465,6 @@ impl<'a, 'b> InitializeConfigCpiBuilder<'a, 'b> { self } #[inline(always)] - pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { - self.instruction.payer = Some(payer); - self - } - #[inline(always)] pub fn restaking_program_id( &mut self, restaking_program_id: &'b solana_program::account_info::AccountInfo<'a>, @@ -556,6 +560,8 @@ impl<'a, 'b> InitializeConfigCpiBuilder<'a, 'b> { ncn: self.instruction.ncn.expect("ncn is not set"), + ncn_admin: self.instruction.ncn_admin.expect("ncn_admin is not set"), + fee_wallet: self.instruction.fee_wallet.expect("fee_wallet is not set"), tie_breaker_admin: self @@ -563,8 +569,6 @@ impl<'a, 'b> InitializeConfigCpiBuilder<'a, 'b> { .tie_breaker_admin .expect("tie_breaker_admin is not set"), - payer: self.instruction.payer.expect("payer is not set"), - restaking_program_id: self .instruction .restaking_program_id @@ -588,9 +592,9 @@ struct InitializeConfigCpiBuilderInstruction<'a, 'b> { __program: &'b solana_program::account_info::AccountInfo<'a>, config: Option<&'b solana_program::account_info::AccountInfo<'a>>, ncn: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ncn_admin: Option<&'b solana_program::account_info::AccountInfo<'a>>, fee_wallet: Option<&'b solana_program::account_info::AccountInfo<'a>>, tie_breaker_admin: Option<&'b solana_program::account_info::AccountInfo<'a>>, - payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, restaking_program_id: Option<&'b solana_program::account_info::AccountInfo<'a>>, system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, dao_fee_bps: Option, diff --git a/core/src/error.rs b/core/src/error.rs index 43a02872..48e597cf 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -22,6 +22,8 @@ pub enum TipRouterError { IncorrectNcnAdmin = 0x2400, #[error("Incorrect NCN")] IncorrectNcn = 0x2401, + #[error("Incorrect fee admin")] + IncorrectFeeAdmin = 0x2402, } impl DecodeError for TipRouterError { diff --git a/core/src/fees.rs b/core/src/fees.rs new file mode 100644 index 00000000..30424d41 --- /dev/null +++ b/core/src/fees.rs @@ -0,0 +1,179 @@ +use bytemuck::{Pod, Zeroable}; +use shank::ShankType; +use solana_program::pubkey::Pubkey; + +use crate::{error::TipRouterError, MAX_FEE_BPS}; + +/// Fee account. Allows for fee updates to take place in a future epoch without requiring an update. +/// This is important so all operators calculate the same Merkle root regardless of when fee changes take place. +#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod)] +#[repr(C)] +pub struct Fees { + fee_1: Fee, + fee_2: Fee, +} + +impl Fees { + pub const fn new( + wallet: Pubkey, + dao_fee_share_bps: u64, + ncn_fee_share_bps: u64, + block_engine_fee_bps: u64, + current_epoch: u64, + ) -> Self { + let fee = Fee::new( + wallet, + dao_fee_share_bps, + ncn_fee_share_bps, + block_engine_fee_bps, + current_epoch, + ); + Self { + fee_1: fee, + fee_2: fee, + } + } + + const fn current_fee(&self, current_epoch: u64) -> &Fee { + // If either fee is not yet active, return the other one + if self.fee_1.activation_epoch > current_epoch { + return &self.fee_2; + } + if self.fee_2.activation_epoch > current_epoch { + return &self.fee_1; + } + + // Otherwise return the one with higher activation epoch + if self.fee_1.activation_epoch >= self.fee_2.activation_epoch { + &self.fee_1 + } else { + &self.fee_2 + } + } + + pub const fn block_engine_fee(&self, current_epoch: u64) -> u64 { + self.current_fee(current_epoch).block_engine_fee_bps + } + + /// Calculate fee as a portion of remaining BPS after block engine fee + /// new_fee = dao_fee_bps / ((10000 - block_engine_fee_bps) / 10000) + /// = dao_fee_bps * 10000 / (10000 - block_engine_fee_bps) + pub fn dao_fee(&self, current_epoch: u64) -> Result { + let fee = self.current_fee(current_epoch); + let remaining_bps = MAX_FEE_BPS + .checked_sub(fee.block_engine_fee_bps) + .ok_or(TipRouterError::ArithmeticOverflow)?; + fee.dao_share_bps + .checked_mul(MAX_FEE_BPS) + .and_then(|x| x.checked_div(remaining_bps)) + .ok_or(TipRouterError::ArithmeticOverflow) + } + + /// Calculate fee as a portion of remaining BPS after block engine fee + /// new_fee = ncn_fee_bps / ((10000 - block_engine_fee_bps) / 10000) + /// = ncn_fee_bps * 10000 / (10000 - block_engine_fee_bps) + pub fn ncn_fee(&self, current_epoch: u64) -> Result { + let fee = self.current_fee(current_epoch); + let remaining_bps = MAX_FEE_BPS + .checked_sub(fee.block_engine_fee_bps) + .ok_or(TipRouterError::ArithmeticOverflow)?; + fee.ncn_share_bps + .checked_mul(MAX_FEE_BPS) + .and_then(|x| x.checked_div(remaining_bps)) + .ok_or(TipRouterError::ArithmeticOverflow) + } + + pub const fn fee_wallet(&self, current_epoch: u64) -> Pubkey { + self.current_fee(current_epoch).wallet + } + + fn get_updatable_fee_mut(&mut self, current_epoch: u64) -> Result<&mut Fee, TipRouterError> { + let next_epoch = current_epoch + .checked_add(1) + .ok_or(TipRouterError::ArithmeticOverflow)?; + + // If either fee is scheduled for next epoch, return that one + if self.fee_1.activation_epoch == next_epoch { + return Ok(&mut self.fee_1); + } + if self.fee_2.activation_epoch == next_epoch { + return Ok(&mut self.fee_2); + } + + // Otherwise return the one with lower activation epoch + if self.fee_1.activation_epoch <= self.fee_2.activation_epoch { + Ok(&mut self.fee_1) + } else { + Ok(&mut self.fee_2) + } + } + + pub fn set_new_fees( + &mut self, + new_dao_fee_bps: Option, + new_ncn_fee_bps: Option, + new_block_engine_fee_bps: Option, + new_wallet: Option, + current_epoch: u64, + ) -> Result<(), TipRouterError> { + let current_fees = *self.current_fee(current_epoch); + let new_fees = self.get_updatable_fee_mut(current_epoch)?; + *new_fees = current_fees; + + if let Some(new_dao_fee_bps) = new_dao_fee_bps { + if new_dao_fee_bps > MAX_FEE_BPS { + return Err(TipRouterError::FeeCapExceeded); + } + new_fees.dao_share_bps = new_dao_fee_bps; + } + if let Some(new_ncn_fee_bps) = new_ncn_fee_bps { + if new_ncn_fee_bps > MAX_FEE_BPS { + return Err(TipRouterError::FeeCapExceeded); + } + new_fees.ncn_share_bps = new_ncn_fee_bps; + } + if let Some(new_block_engine_fee_bps) = new_block_engine_fee_bps { + if new_block_engine_fee_bps > MAX_FEE_BPS { + return Err(TipRouterError::FeeCapExceeded); + } + new_fees.block_engine_fee_bps = new_block_engine_fee_bps; + } + if let Some(new_wallet) = new_wallet { + new_fees.wallet = new_wallet; + } + new_fees.activation_epoch = current_epoch + .checked_add(1) + .ok_or(TipRouterError::ArithmeticOverflow)?; + Ok(()) + } +} + +#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod)] +#[repr(C)] +pub struct Fee { + wallet: Pubkey, + dao_share_bps: u64, + ncn_share_bps: u64, + block_engine_fee_bps: u64, + activation_epoch: u64, +} + +impl Fee { + pub const fn new( + wallet: Pubkey, + dao_share_bps: u64, + ncn_share_bps: u64, + block_engine_fee_bps: u64, + epoch: u64, + ) -> Self { + Self { + wallet, + dao_share_bps, + ncn_share_bps, + block_engine_fee_bps, + activation_epoch: epoch, + } + } +} + +// TODO Some tests for fees diff --git a/core/src/instruction.rs b/core/src/instruction.rs index d7d87c3a..313ddbd2 100644 --- a/core/src/instruction.rs +++ b/core/src/instruction.rs @@ -10,9 +10,9 @@ pub enum WeightTableInstruction { /// Initialize the global configuration for this NCN #[account(0, writable, name = "config")] #[account(1, name = "ncn")] - #[account(2, name = "fee_wallet")] - #[account(3, name = "tie_breaker_admin")] - #[account(4, writable, name = "payer")] + #[account(2, writable, name = "ncn_admin")] + #[account(3, name = "fee_wallet")] + #[account(4, name = "tie_breaker_admin")] #[account(5, name = "restaking_program_id")] #[account(6, name = "system_program")] InitializeConfig { diff --git a/core/src/lib.rs b/core/src/lib.rs index 23da2928..18715e5d 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -2,6 +2,7 @@ pub mod discriminators; pub mod error; pub mod instruction; // pub mod depreciated_weight; +pub mod fees; pub mod ncn_config; pub mod weight_table; diff --git a/core/src/ncn_config.rs b/core/src/ncn_config.rs index 25eb4f97..c77cc75d 100644 --- a/core/src/ncn_config.rs +++ b/core/src/ncn_config.rs @@ -14,9 +14,8 @@ use jito_bytemuck::{AccountDeserialize, Discriminator}; use shank::{ShankAccount, ShankType}; use solana_program::{account_info::AccountInfo, msg, program_error::ProgramError, pubkey::Pubkey}; -use crate::{discriminators::Discriminators, error::TipRouterError, MAX_FEE_BPS}; +use crate::{discriminators::Discriminators, fees::Fees}; -// PDA'd ["CONFIG"] #[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod, AccountDeserialize, ShankAccount)] #[repr(C)] pub struct NcnConfig { @@ -25,7 +24,8 @@ pub struct NcnConfig { pub tie_breaker_admin: Pubkey, - // Separate fee admin here or just admin? + pub fee_admin: Pubkey, + pub fees: Fees, /// Bump seed for the PDA @@ -40,25 +40,17 @@ impl Discriminator for NcnConfig { } impl NcnConfig { - pub fn new( + pub const fn new( ncn: Pubkey, tie_breaker_admin: Pubkey, - fee_wallet: Pubkey, - dao_fee_bps: u64, - ncn_fee_bps: u64, - block_engine_fee_bps: u64, - current_epoch: u64, + fee_admin: Pubkey, + fees: Fees, ) -> Self { Self { ncn, tie_breaker_admin, - fees: Fees::new( - fee_wallet, - dao_fee_bps, - ncn_fee_bps, - block_engine_fee_bps, - current_epoch, - ), + fee_admin, + fees, bump: 0, reserved: [0; 127], } @@ -68,17 +60,20 @@ impl NcnConfig { vec![b"config".to_vec()] } - pub fn find_program_address(program_id: &Pubkey) -> (Pubkey, u8, Vec>) { - 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 find_program_address(program_id: &Pubkey, ncn: &Pubkey) -> (Pubkey, u8, Vec>) { + let seeds = vec![b"config".to_vec(), ncn.to_bytes().to_vec()]; + let (address, bump) = Pubkey::find_program_address( + &seeds.iter().map(|s| s.as_slice()).collect::>(), + program_id, + ); + (address, bump, seeds) } /// Loads the NCN [`Config`] account /// /// # Arguments /// * `program_id` - The program ID + /// * `ncn` - The NCN pubkey /// * `account` - The account to load /// * `expect_writable` - Whether the account should be writable /// @@ -86,6 +81,7 @@ impl NcnConfig { /// * `Result<(), ProgramError>` - The result of the operation pub fn load( program_id: &Pubkey, + ncn: &Pubkey, account: &AccountInfo, expect_writable: bool, ) -> Result<(), ProgramError> { @@ -105,181 +101,13 @@ impl NcnConfig { msg!("Config account discriminator is invalid"); return Err(ProgramError::InvalidAccountData); } - if account.key.ne(&Self::find_program_address(program_id).0) { + if account + .key + .ne(&Self::find_program_address(program_id, ncn).0) + { msg!("Config account is not at the correct PDA"); return Err(ProgramError::InvalidAccountData); } Ok(()) } } - -/// Fee account. Allows for fee updates to take place in a future epoch without requiring an update. -/// This is important so all operators calculate the same Merkle root regardless of when fee changes take place. -#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod)] -#[repr(C)] -pub struct Fees { - fee_1: Fee, - fee_2: Fee, -} - -impl Fees { - pub const fn new( - wallet: Pubkey, - dao_fee_share_bps: u64, - ncn_fee_share_bps: u64, - block_engine_fee_bps: u64, - current_epoch: u64, - ) -> Self { - let fee = Fee::new( - wallet, - dao_fee_share_bps, - ncn_fee_share_bps, - block_engine_fee_bps, - current_epoch, - ); - Self { - fee_1: fee, - fee_2: fee, - } - } - - const fn current_fee(&self, current_epoch: u64) -> &Fee { - // If either fee is not yet active, return the other one - if self.fee_1.activation_epoch > current_epoch { - return &self.fee_2; - } - if self.fee_2.activation_epoch > current_epoch { - return &self.fee_1; - } - - // Otherwise return the one with higher activation epoch - if self.fee_1.activation_epoch >= self.fee_2.activation_epoch { - &self.fee_1 - } else { - &self.fee_2 - } - } - - pub const fn block_engine_fee(&self, current_epoch: u64) -> u64 { - self.current_fee(current_epoch).block_engine_fee_bps - } - - /// Calculate fee as a portion of remaining BPS after block engine fee - /// new_fee = dao_fee_bps / ((10000 - block_engine_fee_bps) / 10000) - /// = dao_fee_bps * 10000 / (10000 - block_engine_fee_bps) - pub fn dao_fee(&self, current_epoch: u64) -> Result { - let fee = self.current_fee(current_epoch); - let remaining_bps = MAX_FEE_BPS - .checked_sub(fee.block_engine_fee_bps) - .ok_or(TipRouterError::ArithmeticOverflow)?; - fee.dao_share_bps - .checked_mul(MAX_FEE_BPS) - .and_then(|x| x.checked_div(remaining_bps)) - .ok_or(TipRouterError::ArithmeticOverflow) - } - - /// Calculate fee as a portion of remaining BPS after block engine fee - /// new_fee = ncn_fee_bps / ((10000 - block_engine_fee_bps) / 10000) - /// = ncn_fee_bps * 10000 / (10000 - block_engine_fee_bps) - pub fn ncn_fee(&self, current_epoch: u64) -> Result { - let fee = self.current_fee(current_epoch); - let remaining_bps = MAX_FEE_BPS - .checked_sub(fee.block_engine_fee_bps) - .ok_or(TipRouterError::ArithmeticOverflow)?; - fee.ncn_share_bps - .checked_mul(MAX_FEE_BPS) - .and_then(|x| x.checked_div(remaining_bps)) - .ok_or(TipRouterError::ArithmeticOverflow) - } - - pub const fn fee_wallet(&self, current_epoch: u64) -> Pubkey { - self.current_fee(current_epoch).wallet - } - - fn get_updatable_fee_mut(&mut self, current_epoch: u64) -> Result<&mut Fee, TipRouterError> { - let next_epoch = current_epoch - .checked_add(1) - .ok_or(TipRouterError::ArithmeticOverflow)?; - - // If either fee is scheduled for next epoch, return that one - if self.fee_1.activation_epoch == next_epoch { - return Ok(&mut self.fee_1); - } - if self.fee_2.activation_epoch == next_epoch { - return Ok(&mut self.fee_2); - } - - // Otherwise return the one with lower activation epoch - if self.fee_1.activation_epoch <= self.fee_2.activation_epoch { - Ok(&mut self.fee_1) - } else { - Ok(&mut self.fee_2) - } - } - - pub fn set_new_fees( - &mut self, - new_dao_fee_bps: Option, - new_ncn_fee_bps: Option, - new_block_engine_fee_bps: Option, - new_wallet: Option, - current_epoch: u64, - ) -> Result<(), TipRouterError> { - let fee = self.get_updatable_fee_mut(current_epoch)?; - if let Some(new_dao_fee_bps) = new_dao_fee_bps { - if new_dao_fee_bps > MAX_FEE_BPS { - return Err(TipRouterError::FeeCapExceeded); - } - fee.dao_share_bps = new_dao_fee_bps; - } - if let Some(new_ncn_fee_bps) = new_ncn_fee_bps { - if new_ncn_fee_bps > MAX_FEE_BPS { - return Err(TipRouterError::FeeCapExceeded); - } - fee.ncn_share_bps = new_ncn_fee_bps; - } - if let Some(new_block_engine_fee_bps) = new_block_engine_fee_bps { - if new_block_engine_fee_bps > MAX_FEE_BPS { - return Err(TipRouterError::FeeCapExceeded); - } - fee.block_engine_fee_bps = new_block_engine_fee_bps; - } - if let Some(new_wallet) = new_wallet { - fee.wallet = new_wallet; - } - fee.activation_epoch = current_epoch - .checked_add(1) - .ok_or(TipRouterError::ArithmeticOverflow)?; - Ok(()) - } -} - -#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod)] -#[repr(C)] -pub struct Fee { - wallet: Pubkey, - dao_share_bps: u64, - ncn_share_bps: u64, - block_engine_fee_bps: u64, - activation_epoch: u64, -} - -impl Fee { - pub const fn new( - wallet: Pubkey, - dao_share_bps: u64, - ncn_share_bps: u64, - block_engine_fee_bps: u64, - epoch: u64, - ) -> Self { - Self { - wallet, - dao_share_bps, - ncn_share_bps, - block_engine_fee_bps, - activation_epoch: epoch, - } - } -} - -// TODO Some tests for fees diff --git a/idl/jito_tip_router.json b/idl/jito_tip_router.json index 8af1d580..2c3d914d 100644 --- a/idl/jito_tip_router.json +++ b/idl/jito_tip_router.json @@ -16,18 +16,18 @@ "isSigner": false }, { - "name": "feeWallet", - "isMut": false, + "name": "ncnAdmin", + "isMut": true, "isSigner": false }, { - "name": "tieBreakerAdmin", + "name": "feeWallet", "isMut": false, "isSigner": false }, { - "name": "payer", - "isMut": true, + "name": "tieBreakerAdmin", + "isMut": false, "isSigner": false }, { @@ -255,6 +255,10 @@ "name": "tieBreakerAdmin", "type": "publicKey" }, + { + "name": "feeAdmin", + "type": "publicKey" + }, { "name": "fees", "type": { @@ -445,6 +449,11 @@ "code": 9217, "name": "IncorrectNcn", "msg": "Incorrect NCN" + }, + { + "code": 9218, + "name": "IncorrectFeeAdmin", + "msg": "Incorrect fee admin" } ], "metadata": { diff --git a/integration_tests/tests/fixtures/tip_router_client.rs b/integration_tests/tests/fixtures/tip_router_client.rs index 72ea3378..7ec3a6d4 100644 --- a/integration_tests/tests/fixtures/tip_router_client.rs +++ b/integration_tests/tests/fixtures/tip_router_client.rs @@ -54,36 +54,43 @@ impl TipRouterClient { Ok(()) } - pub async fn get_config(&mut self) -> TestResult { - let config_pda = NcnConfig::find_program_address(&jito_tip_router_program::id()).0; + pub async fn get_config(&mut self, ncn_pubkey: Pubkey) -> TestResult { + let config_pda = + NcnConfig::find_program_address(&jito_tip_router_program::id(), &ncn_pubkey).0; let config = self.banks_client.get_account(config_pda).await?.unwrap(); Ok(*NcnConfig::try_from_slice_unchecked(config.data.as_slice()).unwrap()) } - pub async fn do_initialize_config(&mut self, ncn: Pubkey, ncn_admin: Pubkey) -> TestResult<()> { + pub async fn do_initialize_config( + &mut self, + ncn: Pubkey, + ncn_admin: &Keypair, + ) -> TestResult<()> { self._airdrop(&self.payer.pubkey(), 1.0).await?; - self.initialize_config(ncn, ncn_admin, ncn_admin, 0, 0, 0) + let ncn_admin_pubkey = ncn_admin.pubkey(); + self.initialize_config(ncn, ncn_admin, ncn_admin_pubkey, ncn_admin_pubkey, 0, 0, 0) .await } pub async fn initialize_config( &mut self, ncn: Pubkey, + ncn_admin: &Keypair, fee_wallet: Pubkey, tie_breaker_admin: Pubkey, dao_fee_bps: u64, ncn_fee_bps: u64, block_engine_fee_bps: u64, ) -> TestResult<()> { - let config_pda = NcnConfig::find_program_address(&jito_tip_router_program::id()).0; + let config_pda = NcnConfig::find_program_address(&jito_tip_router_program::id(), &ncn).0; let ix = InitializeConfigBuilder::new() .config(config_pda) .ncn(ncn) + .ncn_admin(ncn_admin.pubkey()) .fee_wallet(fee_wallet) .tie_breaker_admin(tie_breaker_admin) - .payer(self.payer.pubkey()) .restaking_program_id(jito_restaking_program::id()) .dao_fee_bps(dao_fee_bps) .ncn_fee_bps(ncn_fee_bps) @@ -93,8 +100,8 @@ impl TipRouterClient { let blockhash = self.banks_client.get_latest_blockhash().await?; self.process_transaction(&Transaction::new_signed_with_payer( &[ix], - Some(&self.payer.pubkey()), - &[&self.payer], + Some(&ncn_admin.pubkey()), + &[&ncn_admin], blockhash, )) .await @@ -108,7 +115,8 @@ impl TipRouterClient { fee_wallet: Pubkey, ncn_root: &NcnRoot, ) -> TestResult<()> { - let config_pda = NcnConfig::find_program_address(&jito_tip_router_program::id()).0; + let config_pda = + NcnConfig::find_program_address(&jito_tip_router_program::id(), &ncn_root.ncn_pubkey).0; self._airdrop(&ncn_root.ncn_admin.pubkey(), 1.0).await?; self.set_config_fees( config_pda, diff --git a/integration_tests/tests/tip_router/initialize_ncn_config.rs b/integration_tests/tests/tip_router/initialize_ncn_config.rs index 19d5f2cb..80be9598 100644 --- a/integration_tests/tests/tip_router/initialize_ncn_config.rs +++ b/integration_tests/tests/tip_router/initialize_ncn_config.rs @@ -5,8 +5,8 @@ mod tests { use solana_sdk::signature::{Keypair, Signer}; use crate::fixtures::{ - assert_ix_error, test_builder::TestBuilder, tip_router_client::assert_tip_router_error, - TestResult, + assert_ix_error, restaking_client::NcnRoot, test_builder::TestBuilder, + tip_router_client::assert_tip_router_error, TestResult, }; #[tokio::test] @@ -15,7 +15,7 @@ mod tests { let mut tip_router_client = fixture.tip_router_client(); let ncn_root = fixture.setup_ncn().await?; tip_router_client - .do_initialize_config(ncn_root.ncn_pubkey, ncn_root.ncn_admin.pubkey()) + .do_initialize_config(ncn_root.ncn_pubkey, &ncn_root.ncn_admin) .await?; Ok(()) } @@ -26,11 +26,11 @@ mod tests { let mut tip_router_client = fixture.tip_router_client(); let ncn_root = fixture.setup_ncn().await?; tip_router_client - .do_initialize_config(ncn_root.ncn_pubkey, ncn_root.ncn_admin.pubkey()) + .do_initialize_config(ncn_root.ncn_pubkey, &ncn_root.ncn_admin) .await?; fixture.warp_slot_incremental(1).await?; let transaction_error = tip_router_client - .do_initialize_config(ncn_root.ncn_pubkey, ncn_root.ncn_admin.pubkey()) + .do_initialize_config(ncn_root.ncn_pubkey, &ncn_root.ncn_admin) .await; assert_ix_error(transaction_error, InstructionError::InvalidAccountOwner); Ok(()) @@ -42,8 +42,15 @@ mod tests { let mut tip_router_client = fixture.tip_router_client(); let fake_ncn = Keypair::new(); let fake_admin = Keypair::new(); + let fake_ncn_root = NcnRoot { + ncn_pubkey: fake_ncn.pubkey(), + ncn_admin: fake_admin, + }; + tip_router_client + ._airdrop(&fake_ncn_root.ncn_admin.pubkey(), 1.0) + .await?; let transaction_error = tip_router_client - .do_initialize_config(fake_ncn.pubkey(), fake_admin.pubkey()) + .do_initialize_config(fake_ncn_root.ncn_pubkey, &fake_ncn_root.ncn_admin) .await; assert_ix_error(transaction_error, InstructionError::InvalidAccountOwner); Ok(()) @@ -55,11 +62,13 @@ mod tests { let mut tip_router_client = fixture.tip_router_client(); let ncn_root = fixture.setup_ncn().await?; + let ncn_admin_pubkey = ncn_root.ncn_admin.pubkey(); let transaction_error = tip_router_client .initialize_config( ncn_root.ncn_pubkey, - ncn_root.ncn_admin.pubkey(), - ncn_root.ncn_admin.pubkey(), + &ncn_root.ncn_admin, + ncn_admin_pubkey, + ncn_admin_pubkey, 10_001, 0, 0, diff --git a/integration_tests/tests/tip_router/set_config_fees.rs b/integration_tests/tests/tip_router/set_config_fees.rs index b07b369c..dd793932 100644 --- a/integration_tests/tests/tip_router/set_config_fees.rs +++ b/integration_tests/tests/tip_router/set_config_fees.rs @@ -16,9 +16,9 @@ mod tests { let mut tip_router_client = fixture.tip_router_client(); let ncn_root = fixture.setup_ncn().await?; - // Initialize config first + // Initialize config first - note that ncn_admin is now required as signer tip_router_client - .do_initialize_config(ncn_root.ncn_pubkey, ncn_root.ncn_admin.pubkey()) + .do_initialize_config(ncn_root.ncn_pubkey, &ncn_root.ncn_admin) .await?; // Change fees and fee wallet @@ -44,7 +44,7 @@ mod tests { // Initialize config first tip_router_client - .do_initialize_config(ncn_root.ncn_pubkey, ncn_root.ncn_admin.pubkey()) + .do_initialize_config(ncn_root.ncn_pubkey, &ncn_root.ncn_admin) .await?; // Try to set fees above max @@ -63,7 +63,7 @@ mod tests { let mut ncn_root = fixture.setup_ncn().await?; tip_router_client - .do_initialize_config(ncn_root.ncn_pubkey, ncn_root.ncn_admin.pubkey()) + .do_initialize_config(ncn_root.ncn_pubkey, &ncn_root.ncn_admin) .await?; let wrong_admin = Keypair::new(); @@ -72,7 +72,7 @@ mod tests { .do_set_config_fees(100, 200, 300, ncn_root.ncn_admin.pubkey(), &ncn_root) .await; - assert_tip_router_error(transaction_error, TipRouterError::IncorrectNcnAdmin); + assert_tip_router_error(transaction_error, TipRouterError::IncorrectFeeAdmin); Ok(()) } @@ -84,7 +84,7 @@ mod tests { // Initialize config first tip_router_client - .do_initialize_config(ncn_root.ncn_pubkey, ncn_root.ncn_admin.pubkey()) + .do_initialize_config(ncn_root.ncn_pubkey, &ncn_root.ncn_admin) .await?; // Set new fees @@ -98,7 +98,7 @@ mod tests { .warp_slot_incremental(2 * DEFAULT_SLOTS_PER_EPOCH) .await?; - let config = tip_router_client.get_config().await?; + let config = tip_router_client.get_config(ncn_root.ncn_pubkey).await?; let clock = fixture.clock().await; assert_eq!(config.fees.dao_fee(clock.epoch as u64).unwrap(), 100); assert_eq!(config.fees.ncn_fee(clock.epoch as u64).unwrap(), 200); diff --git a/program/src/initialize_ncn_config.rs b/program/src/initialize_ncn_config.rs index 5ead65a1..a1fe566f 100644 --- a/program/src/initialize_ncn_config.rs +++ b/program/src/initialize_ncn_config.rs @@ -4,7 +4,7 @@ use jito_jsm_core::{ loader::{load_signer, load_system_account, load_system_program}, }; use jito_restaking_core::ncn::Ncn; -use jito_tip_router_core::{error::TipRouterError, ncn_config::NcnConfig}; +use jito_tip_router_core::{error::TipRouterError, fees::Fees, ncn_config::NcnConfig, MAX_FEE_BPS}; use solana_program::{ account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, rent::Rent, sysvar::Sysvar, @@ -17,37 +17,44 @@ pub fn process_initialize_ncn_config( ncn_fee_bps: u64, block_engine_fee_bps: u64, ) -> ProgramResult { - let [config, ncn_account, fee_wallet, tie_breaker_admin, payer, restaking_program_id, system_program] = + let [config, ncn_account, fee_wallet, ncn_admin, tie_breaker_admin, restaking_program_id, system_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; load_system_account(config, true)?; - load_signer(payer, false)?; load_system_program(system_program)?; + load_signer(ncn_admin, false)?; Ncn::load(restaking_program_id.key, ncn_account, false)?; - let (config_pda, config_bump, mut config_seeds) = NcnConfig::find_program_address(program_id); + let ncn_data = ncn_account.data.borrow(); + let ncn = Ncn::try_from_slice_unchecked(&ncn_data)?; + if ncn.admin != *ncn_admin.key { + return Err(TipRouterError::IncorrectNcnAdmin.into()); + } + + let (config_pda, config_bump, mut config_seeds) = + NcnConfig::find_program_address(program_id, ncn_account.key); config_seeds.push(vec![config_bump]); if config_pda != *config.key { return Err(ProgramError::InvalidSeeds); } - if dao_fee_bps > 10_000 { + if dao_fee_bps > MAX_FEE_BPS { return Err(TipRouterError::FeeCapExceeded.into()); } - if ncn_fee_bps > 10_000 { + if ncn_fee_bps > MAX_FEE_BPS { return Err(TipRouterError::FeeCapExceeded.into()); } - if block_engine_fee_bps > 10_000 { + if block_engine_fee_bps > MAX_FEE_BPS { return Err(TipRouterError::FeeCapExceeded.into()); } create_account( - payer, + ncn_admin, config, system_program, program_id, @@ -58,7 +65,7 @@ pub fn process_initialize_ncn_config( &config_seeds, )?; - let epoch = Clock::get()?.epoch as u64; + let epoch = Clock::get()?.epoch; let mut config_data = config.try_borrow_mut_data()?; config_data[0] = NcnConfig::DISCRIMINATOR; @@ -66,11 +73,14 @@ pub fn process_initialize_ncn_config( *config = NcnConfig::new( *ncn_account.key, *tie_breaker_admin.key, - *fee_wallet.key, - dao_fee_bps, - ncn_fee_bps, - block_engine_fee_bps, - epoch, + *ncn_admin.key, + Fees::new( + *fee_wallet.key, + dao_fee_bps, + ncn_fee_bps, + block_engine_fee_bps, + epoch, + ), ); config.bump = config_bump; diff --git a/program/src/set_config_fees.rs b/program/src/set_config_fees.rs index dced1018..ad7b3be6 100644 --- a/program/src/set_config_fees.rs +++ b/program/src/set_config_fees.rs @@ -15,13 +15,13 @@ pub fn process_set_config_fees( new_block_engine_fee_bps: Option, new_fee_wallet: Option, ) -> ProgramResult { - let [config, ncn_account, ncn_admin, restaking_program_id] = accounts else { + let [config, ncn_account, fee_admin, restaking_program_id] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; - load_signer(ncn_admin, true)?; + load_signer(fee_admin, true)?; - NcnConfig::load(program_id, config, true)?; + NcnConfig::load(program_id, ncn_account.key, config, true)?; Ncn::load(restaking_program_id.key, ncn_account, false)?; let mut config_data = config.try_borrow_mut_data()?; @@ -35,10 +35,8 @@ pub fn process_set_config_fees( return Err(TipRouterError::IncorrectNcn.into()); } - let ncn_data = ncn_account.data.borrow(); - let ncn = Ncn::try_from_slice_unchecked(&ncn_data)?; - if ncn.admin != *ncn_admin.key { - return Err(TipRouterError::IncorrectNcnAdmin.into()); + if config.fee_admin != *fee_admin.key { + return Err(TipRouterError::IncorrectFeeAdmin.into()); } let epoch = Clock::get()?.epoch; diff --git a/program/src/set_new_admin.rs b/program/src/set_new_admin.rs new file mode 100644 index 00000000..e69de29b