diff --git a/clients/js/jito_tip_router/accounts/epochRewardRouter.ts b/clients/js/jito_tip_router/accounts/epochRewardRouter.ts new file mode 100644 index 0000000..fb72dec --- /dev/null +++ b/clients/js/jito_tip_router/accounts/epochRewardRouter.ts @@ -0,0 +1,169 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { + assertAccountExists, + assertAccountsExist, + combineCodec, + decodeAccount, + fetchEncodedAccount, + fetchEncodedAccounts, + getAddressDecoder, + getAddressEncoder, + getArrayDecoder, + getArrayEncoder, + getStructDecoder, + getStructEncoder, + getU64Decoder, + getU64Encoder, + getU8Decoder, + getU8Encoder, + type Account, + type Address, + type Codec, + type Decoder, + type EncodedAccount, + type Encoder, + type FetchAccountConfig, + type FetchAccountsConfig, + type MaybeAccount, + type MaybeEncodedAccount, +} from '@solana/web3.js'; +import { + getOperatorRewardDecoder, + getOperatorRewardEncoder, + type OperatorReward, + type OperatorRewardArgs, +} from '../types'; + +export type EpochRewardRouter = { + discriminator: bigint; + ncn: Address; + ncnEpoch: bigint; + bump: number; + slotCreated: bigint; + reserved: Array; + rewardPool: bigint; + operatorRewards: Array; +}; + +export type EpochRewardRouterArgs = { + discriminator: number | bigint; + ncn: Address; + ncnEpoch: number | bigint; + bump: number; + slotCreated: number | bigint; + reserved: Array; + rewardPool: number | bigint; + operatorRewards: Array; +}; + +export function getEpochRewardRouterEncoder(): Encoder { + return getStructEncoder([ + ['discriminator', getU64Encoder()], + ['ncn', getAddressEncoder()], + ['ncnEpoch', getU64Encoder()], + ['bump', getU8Encoder()], + ['slotCreated', getU64Encoder()], + ['reserved', getArrayEncoder(getU8Encoder(), { size: 128 })], + ['rewardPool', getU64Encoder()], + [ + 'operatorRewards', + getArrayEncoder(getOperatorRewardEncoder(), { size: 32 }), + ], + ]); +} + +export function getEpochRewardRouterDecoder(): Decoder { + return getStructDecoder([ + ['discriminator', getU64Decoder()], + ['ncn', getAddressDecoder()], + ['ncnEpoch', getU64Decoder()], + ['bump', getU8Decoder()], + ['slotCreated', getU64Decoder()], + ['reserved', getArrayDecoder(getU8Decoder(), { size: 128 })], + ['rewardPool', getU64Decoder()], + [ + 'operatorRewards', + getArrayDecoder(getOperatorRewardDecoder(), { size: 32 }), + ], + ]); +} + +export function getEpochRewardRouterCodec(): Codec< + EpochRewardRouterArgs, + EpochRewardRouter +> { + return combineCodec( + getEpochRewardRouterEncoder(), + getEpochRewardRouterDecoder() + ); +} + +export function decodeEpochRewardRouter( + encodedAccount: EncodedAccount +): Account; +export function decodeEpochRewardRouter( + encodedAccount: MaybeEncodedAccount +): MaybeAccount; +export function decodeEpochRewardRouter( + encodedAccount: EncodedAccount | MaybeEncodedAccount +): + | Account + | MaybeAccount { + return decodeAccount( + encodedAccount as MaybeEncodedAccount, + getEpochRewardRouterDecoder() + ); +} + +export async function fetchEpochRewardRouter( + rpc: Parameters[0], + address: Address, + config?: FetchAccountConfig +): Promise> { + const maybeAccount = await fetchMaybeEpochRewardRouter(rpc, address, config); + assertAccountExists(maybeAccount); + return maybeAccount; +} + +export async function fetchMaybeEpochRewardRouter< + TAddress extends string = string, +>( + rpc: Parameters[0], + address: Address, + config?: FetchAccountConfig +): Promise> { + const maybeAccount = await fetchEncodedAccount(rpc, address, config); + return decodeEpochRewardRouter(maybeAccount); +} + +export async function fetchAllEpochRewardRouter( + rpc: Parameters[0], + addresses: Array
, + config?: FetchAccountsConfig +): Promise[]> { + const maybeAccounts = await fetchAllMaybeEpochRewardRouter( + rpc, + addresses, + config + ); + assertAccountsExist(maybeAccounts); + return maybeAccounts; +} + +export async function fetchAllMaybeEpochRewardRouter( + rpc: Parameters[0], + addresses: Array
, + config?: FetchAccountsConfig +): Promise[]> { + const maybeAccounts = await fetchEncodedAccounts(rpc, addresses, config); + return maybeAccounts.map((maybeAccount) => + decodeEpochRewardRouter(maybeAccount) + ); +} diff --git a/clients/js/jito_tip_router/accounts/epochSnapshot.ts b/clients/js/jito_tip_router/accounts/epochSnapshot.ts index 18dcef0..990137d 100644 --- a/clients/js/jito_tip_router/accounts/epochSnapshot.ts +++ b/clients/js/jito_tip_router/accounts/epochSnapshot.ts @@ -37,10 +37,10 @@ import { type MaybeEncodedAccount, } from '@solana/web3.js'; import { - getFeesDecoder, - getFeesEncoder, - type Fees, - type FeesArgs, + getFeeConfigDecoder, + getFeeConfigEncoder, + type FeeConfig, + type FeeConfigArgs, } from '../types'; export type EpochSnapshot = { @@ -50,12 +50,13 @@ export type EpochSnapshot = { bump: number; slotCreated: bigint; slotFinalized: bigint; - ncnFees: Fees; + fees: FeeConfig; operatorCount: bigint; vaultCount: bigint; operatorsRegistered: bigint; validOperatorVaultDelegations: bigint; stakeWeight: bigint; + rewardStakeWeight: bigint; reserved: Array; }; @@ -66,12 +67,13 @@ export type EpochSnapshotArgs = { bump: number; slotCreated: number | bigint; slotFinalized: number | bigint; - ncnFees: FeesArgs; + fees: FeeConfigArgs; operatorCount: number | bigint; vaultCount: number | bigint; operatorsRegistered: number | bigint; validOperatorVaultDelegations: number | bigint; stakeWeight: number | bigint; + rewardStakeWeight: number | bigint; reserved: Array; }; @@ -83,12 +85,13 @@ export function getEpochSnapshotEncoder(): Encoder { ['bump', getU8Encoder()], ['slotCreated', getU64Encoder()], ['slotFinalized', getU64Encoder()], - ['ncnFees', getFeesEncoder()], + ['fees', getFeeConfigEncoder()], ['operatorCount', getU64Encoder()], ['vaultCount', getU64Encoder()], ['operatorsRegistered', getU64Encoder()], ['validOperatorVaultDelegations', getU64Encoder()], ['stakeWeight', getU128Encoder()], + ['rewardStakeWeight', getU128Encoder()], ['reserved', getArrayEncoder(getU8Encoder(), { size: 128 })], ]); } @@ -101,12 +104,13 @@ export function getEpochSnapshotDecoder(): Decoder { ['bump', getU8Decoder()], ['slotCreated', getU64Decoder()], ['slotFinalized', getU64Decoder()], - ['ncnFees', getFeesDecoder()], + ['fees', getFeeConfigDecoder()], ['operatorCount', getU64Decoder()], ['vaultCount', getU64Decoder()], ['operatorsRegistered', getU64Decoder()], ['validOperatorVaultDelegations', getU64Decoder()], ['stakeWeight', getU128Decoder()], + ['rewardStakeWeight', getU128Decoder()], ['reserved', getArrayDecoder(getU8Decoder(), { size: 128 })], ]); } diff --git a/clients/js/jito_tip_router/accounts/index.ts b/clients/js/jito_tip_router/accounts/index.ts index 9e306af..a20cd09 100644 --- a/clients/js/jito_tip_router/accounts/index.ts +++ b/clients/js/jito_tip_router/accounts/index.ts @@ -7,8 +7,10 @@ */ export * from './ballotBox'; +export * from './epochRewardRouter'; export * from './epochSnapshot'; export * from './ncnConfig'; +export * from './operatorRewardRouter'; export * from './operatorSnapshot'; export * from './trackedMints'; export * from './weightTable'; diff --git a/clients/js/jito_tip_router/accounts/ncnConfig.ts b/clients/js/jito_tip_router/accounts/ncnConfig.ts index 6299e66..b396a55 100644 --- a/clients/js/jito_tip_router/accounts/ncnConfig.ts +++ b/clients/js/jito_tip_router/accounts/ncnConfig.ts @@ -35,10 +35,10 @@ import { type MaybeEncodedAccount, } from '@solana/web3.js'; import { - getFeesDecoder, - getFeesEncoder, - type Fees, - type FeesArgs, + getFeeConfigDecoder, + getFeeConfigEncoder, + type FeeConfig, + type FeeConfigArgs, } from '../types'; export type NcnConfig = { @@ -46,7 +46,7 @@ export type NcnConfig = { ncn: Address; tieBreakerAdmin: Address; feeAdmin: Address; - fees: Fees; + feeConfig: FeeConfig; bump: number; reserved: Array; }; @@ -56,7 +56,7 @@ export type NcnConfigArgs = { ncn: Address; tieBreakerAdmin: Address; feeAdmin: Address; - fees: FeesArgs; + feeConfig: FeeConfigArgs; bump: number; reserved: Array; }; @@ -67,7 +67,7 @@ export function getNcnConfigEncoder(): Encoder { ['ncn', getAddressEncoder()], ['tieBreakerAdmin', getAddressEncoder()], ['feeAdmin', getAddressEncoder()], - ['fees', getFeesEncoder()], + ['feeConfig', getFeeConfigEncoder()], ['bump', getU8Encoder()], ['reserved', getArrayEncoder(getU8Encoder(), { size: 127 })], ]); @@ -79,7 +79,7 @@ export function getNcnConfigDecoder(): Decoder { ['ncn', getAddressDecoder()], ['tieBreakerAdmin', getAddressDecoder()], ['feeAdmin', getAddressDecoder()], - ['fees', getFeesDecoder()], + ['feeConfig', getFeeConfigDecoder()], ['bump', getU8Decoder()], ['reserved', getArrayDecoder(getU8Decoder(), { size: 127 })], ]); diff --git a/clients/js/jito_tip_router/accounts/operatorRewardRouter.ts b/clients/js/jito_tip_router/accounts/operatorRewardRouter.ts new file mode 100644 index 0000000..6848eb5 --- /dev/null +++ b/clients/js/jito_tip_router/accounts/operatorRewardRouter.ts @@ -0,0 +1,169 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { + assertAccountExists, + assertAccountsExist, + combineCodec, + decodeAccount, + fetchEncodedAccount, + fetchEncodedAccounts, + getAddressDecoder, + getAddressEncoder, + getArrayDecoder, + getArrayEncoder, + getStructDecoder, + getStructEncoder, + getU64Decoder, + getU64Encoder, + getU8Decoder, + getU8Encoder, + type Account, + type Address, + type Codec, + type Decoder, + type EncodedAccount, + type Encoder, + type FetchAccountConfig, + type FetchAccountsConfig, + type MaybeAccount, + type MaybeEncodedAccount, +} from '@solana/web3.js'; +import { + getVaultRewardDecoder, + getVaultRewardEncoder, + type VaultReward, + type VaultRewardArgs, +} from '../types'; + +export type OperatorRewardRouter = { + discriminator: bigint; + ncn: Address; + ncnEpoch: bigint; + bump: number; + slotCreated: bigint; + reserved: Array; + rewardPool: bigint; + vaultRewards: Array; +}; + +export type OperatorRewardRouterArgs = { + discriminator: number | bigint; + ncn: Address; + ncnEpoch: number | bigint; + bump: number; + slotCreated: number | bigint; + reserved: Array; + rewardPool: number | bigint; + vaultRewards: Array; +}; + +export function getOperatorRewardRouterEncoder(): Encoder { + return getStructEncoder([ + ['discriminator', getU64Encoder()], + ['ncn', getAddressEncoder()], + ['ncnEpoch', getU64Encoder()], + ['bump', getU8Encoder()], + ['slotCreated', getU64Encoder()], + ['reserved', getArrayEncoder(getU8Encoder(), { size: 128 })], + ['rewardPool', getU64Encoder()], + ['vaultRewards', getArrayEncoder(getVaultRewardEncoder(), { size: 32 })], + ]); +} + +export function getOperatorRewardRouterDecoder(): Decoder { + return getStructDecoder([ + ['discriminator', getU64Decoder()], + ['ncn', getAddressDecoder()], + ['ncnEpoch', getU64Decoder()], + ['bump', getU8Decoder()], + ['slotCreated', getU64Decoder()], + ['reserved', getArrayDecoder(getU8Decoder(), { size: 128 })], + ['rewardPool', getU64Decoder()], + ['vaultRewards', getArrayDecoder(getVaultRewardDecoder(), { size: 32 })], + ]); +} + +export function getOperatorRewardRouterCodec(): Codec< + OperatorRewardRouterArgs, + OperatorRewardRouter +> { + return combineCodec( + getOperatorRewardRouterEncoder(), + getOperatorRewardRouterDecoder() + ); +} + +export function decodeOperatorRewardRouter( + encodedAccount: EncodedAccount +): Account; +export function decodeOperatorRewardRouter( + encodedAccount: MaybeEncodedAccount +): MaybeAccount; +export function decodeOperatorRewardRouter( + encodedAccount: EncodedAccount | MaybeEncodedAccount +): + | Account + | MaybeAccount { + return decodeAccount( + encodedAccount as MaybeEncodedAccount, + getOperatorRewardRouterDecoder() + ); +} + +export async function fetchOperatorRewardRouter< + TAddress extends string = string, +>( + rpc: Parameters[0], + address: Address, + config?: FetchAccountConfig +): Promise> { + const maybeAccount = await fetchMaybeOperatorRewardRouter( + rpc, + address, + config + ); + assertAccountExists(maybeAccount); + return maybeAccount; +} + +export async function fetchMaybeOperatorRewardRouter< + TAddress extends string = string, +>( + rpc: Parameters[0], + address: Address, + config?: FetchAccountConfig +): Promise> { + const maybeAccount = await fetchEncodedAccount(rpc, address, config); + return decodeOperatorRewardRouter(maybeAccount); +} + +export async function fetchAllOperatorRewardRouter( + rpc: Parameters[0], + addresses: Array
, + config?: FetchAccountsConfig +): Promise[]> { + const maybeAccounts = await fetchAllMaybeOperatorRewardRouter( + rpc, + addresses, + config + ); + assertAccountsExist(maybeAccounts); + return maybeAccounts; +} + +export async function fetchAllMaybeOperatorRewardRouter( + rpc: Parameters[0], + addresses: Array
, + config?: FetchAccountsConfig +): Promise[]> { + const maybeAccounts = await fetchEncodedAccounts(rpc, addresses, config); + return maybeAccounts.map((maybeAccount) => + decodeOperatorRewardRouter(maybeAccount) + ); +} diff --git a/clients/js/jito_tip_router/accounts/operatorSnapshot.ts b/clients/js/jito_tip_router/accounts/operatorSnapshot.ts index 76a521b..f6756da 100644 --- a/clients/js/jito_tip_router/accounts/operatorSnapshot.ts +++ b/clients/js/jito_tip_router/accounts/operatorSnapshot.ts @@ -63,6 +63,7 @@ export type OperatorSnapshot = { vaultOperatorDelegationsRegistered: bigint; validOperatorVaultDelegations: bigint; stakeWeight: bigint; + rewardStakeWeight: bigint; reserved: Array; vaultOperatorStakeWeight: Array; }; @@ -83,6 +84,7 @@ export type OperatorSnapshotArgs = { vaultOperatorDelegationsRegistered: number | bigint; validOperatorVaultDelegations: number | bigint; stakeWeight: number | bigint; + rewardStakeWeight: number | bigint; reserved: Array; vaultOperatorStakeWeight: Array; }; @@ -104,6 +106,7 @@ export function getOperatorSnapshotEncoder(): Encoder { ['vaultOperatorDelegationsRegistered', getU64Encoder()], ['validOperatorVaultDelegations', getU64Encoder()], ['stakeWeight', getU128Encoder()], + ['rewardStakeWeight', getU128Encoder()], ['reserved', getArrayEncoder(getU8Encoder(), { size: 256 })], [ 'vaultOperatorStakeWeight', @@ -129,6 +132,7 @@ export function getOperatorSnapshotDecoder(): Decoder { ['vaultOperatorDelegationsRegistered', getU64Decoder()], ['validOperatorVaultDelegations', getU64Decoder()], ['stakeWeight', getU128Decoder()], + ['rewardStakeWeight', getU128Decoder()], ['reserved', getArrayDecoder(getU8Decoder(), { size: 256 })], [ 'vaultOperatorStakeWeight', diff --git a/clients/js/jito_tip_router/errors/jitoTipRouter.ts b/clients/js/jito_tip_router/errors/jitoTipRouter.ts index 74b4496..42348aa 100644 --- a/clients/js/jito_tip_router/errors/jitoTipRouter.ts +++ b/clients/js/jito_tip_router/errors/jitoTipRouter.ts @@ -84,6 +84,10 @@ export const JITO_TIP_ROUTER_ERROR__BALLOT_TALLY_FULL = 0x221b; // 8731 export const JITO_TIP_ROUTER_ERROR__CONSENSUS_ALREADY_REACHED = 0x221c; // 8732 /** ConsensusNotReached: Consensus not reached */ export const JITO_TIP_ROUTER_ERROR__CONSENSUS_NOT_REACHED = 0x221d; // 8733 +/** NotValidNcnShareGroup: Not a valid NCN share group */ +export const JITO_TIP_ROUTER_ERROR__NOT_VALID_NCN_SHARE_GROUP = 0x221e; // 8734 +/** TrackedMintsVaultIndexDne: Tracked Mints does not contain vault index */ +export const JITO_TIP_ROUTER_ERROR__TRACKED_MINTS_VAULT_INDEX_DNE = 0x221f; // 8735 export type JitoTipRouterError = | typeof JITO_TIP_ROUTER_ERROR__ARITHMETIC_OVERFLOW @@ -108,12 +112,14 @@ export type JitoTipRouterError = | typeof JITO_TIP_ROUTER_ERROR__NEW_PRECISE_NUMBER_ERROR | typeof JITO_TIP_ROUTER_ERROR__NO_MINTS_IN_TABLE | typeof JITO_TIP_ROUTER_ERROR__NO_OPERATORS + | typeof JITO_TIP_ROUTER_ERROR__NOT_VALID_NCN_SHARE_GROUP | typeof JITO_TIP_ROUTER_ERROR__OPERATOR_FINALIZED | typeof JITO_TIP_ROUTER_ERROR__OPERATOR_VOTES_FULL | typeof JITO_TIP_ROUTER_ERROR__TOO_MANY_MINTS_FOR_TABLE | typeof JITO_TIP_ROUTER_ERROR__TOO_MANY_VAULT_OPERATOR_DELEGATIONS | typeof JITO_TIP_ROUTER_ERROR__TRACKED_MINT_LIST_FULL | typeof JITO_TIP_ROUTER_ERROR__TRACKED_MINTS_LOCKED + | typeof JITO_TIP_ROUTER_ERROR__TRACKED_MINTS_VAULT_INDEX_DNE | typeof JITO_TIP_ROUTER_ERROR__VAULT_INDEX_ALREADY_IN_USE | typeof JITO_TIP_ROUTER_ERROR__VAULT_OPERATOR_DELEGATION_FINALIZED | typeof JITO_TIP_ROUTER_ERROR__WEIGHT_MINTS_DO_NOT_MATCH_LENGTH @@ -147,12 +153,14 @@ if (process.env.NODE_ENV !== 'production') { [JITO_TIP_ROUTER_ERROR__NEW_PRECISE_NUMBER_ERROR]: `New precise number error`, [JITO_TIP_ROUTER_ERROR__NO_MINTS_IN_TABLE]: `There are no mints in the table`, [JITO_TIP_ROUTER_ERROR__NO_OPERATORS]: `No operators in ncn`, + [JITO_TIP_ROUTER_ERROR__NOT_VALID_NCN_SHARE_GROUP]: `Not a valid NCN share group`, [JITO_TIP_ROUTER_ERROR__OPERATOR_FINALIZED]: `Operator is already finalized - should not happen`, [JITO_TIP_ROUTER_ERROR__OPERATOR_VOTES_FULL]: `Operator votes full`, [JITO_TIP_ROUTER_ERROR__TOO_MANY_MINTS_FOR_TABLE]: `Too many mints for table`, [JITO_TIP_ROUTER_ERROR__TOO_MANY_VAULT_OPERATOR_DELEGATIONS]: `Too many vault operator delegations`, [JITO_TIP_ROUTER_ERROR__TRACKED_MINT_LIST_FULL]: `Tracked mints are at capacity`, [JITO_TIP_ROUTER_ERROR__TRACKED_MINTS_LOCKED]: `Tracked mints are locked for the epoch`, + [JITO_TIP_ROUTER_ERROR__TRACKED_MINTS_VAULT_INDEX_DNE]: `Tracked Mints does not contain vault index`, [JITO_TIP_ROUTER_ERROR__VAULT_INDEX_ALREADY_IN_USE]: `Vault index already in use by a different mint`, [JITO_TIP_ROUTER_ERROR__VAULT_OPERATOR_DELEGATION_FINALIZED]: `Vault operator delegation is already finalized - should not happen`, [JITO_TIP_ROUTER_ERROR__WEIGHT_MINTS_DO_NOT_MATCH_LENGTH]: `Weight mints do not match - length`, diff --git a/clients/js/jito_tip_router/instructions/setConfigFees.ts b/clients/js/jito_tip_router/instructions/setConfigFees.ts index d5b0dc4..808ad2b 100644 --- a/clients/js/jito_tip_router/instructions/setConfigFees.ts +++ b/clients/js/jito_tip_router/instructions/setConfigFees.ts @@ -76,27 +76,30 @@ export type SetConfigFeesInstruction< export type SetConfigFeesInstructionData = { discriminator: number; + newFeeWallet: Option
; + newBlockEngineFeeBps: Option; newDaoFeeBps: Option; newNcnFeeBps: Option; - newBlockEngineFeeBps: Option; - newFeeWallet: Option
; + newNcnFeeGroup: Option; }; export type SetConfigFeesInstructionDataArgs = { + newFeeWallet: OptionOrNullable
; + newBlockEngineFeeBps: OptionOrNullable; newDaoFeeBps: OptionOrNullable; newNcnFeeBps: OptionOrNullable; - newBlockEngineFeeBps: OptionOrNullable; - newFeeWallet: OptionOrNullable
; + newNcnFeeGroup: OptionOrNullable; }; export function getSetConfigFeesInstructionDataEncoder(): Encoder { return transformEncoder( getStructEncoder([ ['discriminator', getU8Encoder()], + ['newFeeWallet', getOptionEncoder(getAddressEncoder())], + ['newBlockEngineFeeBps', getOptionEncoder(getU64Encoder())], ['newDaoFeeBps', getOptionEncoder(getU64Encoder())], ['newNcnFeeBps', getOptionEncoder(getU64Encoder())], - ['newBlockEngineFeeBps', getOptionEncoder(getU64Encoder())], - ['newFeeWallet', getOptionEncoder(getAddressEncoder())], + ['newNcnFeeGroup', getOptionEncoder(getU8Encoder())], ]), (value) => ({ ...value, discriminator: SET_CONFIG_FEES_DISCRIMINATOR }) ); @@ -105,10 +108,11 @@ export function getSetConfigFeesInstructionDataEncoder(): Encoder { return getStructDecoder([ ['discriminator', getU8Decoder()], + ['newFeeWallet', getOptionDecoder(getAddressDecoder())], + ['newBlockEngineFeeBps', getOptionDecoder(getU64Decoder())], ['newDaoFeeBps', getOptionDecoder(getU64Decoder())], ['newNcnFeeBps', getOptionDecoder(getU64Decoder())], - ['newBlockEngineFeeBps', getOptionDecoder(getU64Decoder())], - ['newFeeWallet', getOptionDecoder(getAddressDecoder())], + ['newNcnFeeGroup', getOptionDecoder(getU8Decoder())], ]); } @@ -134,10 +138,11 @@ export type SetConfigFeesInput< ncn: Address; ncnAdmin: TransactionSigner; restakingProgram: Address; + newFeeWallet: SetConfigFeesInstructionDataArgs['newFeeWallet']; + newBlockEngineFeeBps: SetConfigFeesInstructionDataArgs['newBlockEngineFeeBps']; newDaoFeeBps: SetConfigFeesInstructionDataArgs['newDaoFeeBps']; newNcnFeeBps: SetConfigFeesInstructionDataArgs['newNcnFeeBps']; - newBlockEngineFeeBps: SetConfigFeesInstructionDataArgs['newBlockEngineFeeBps']; - newFeeWallet: SetConfigFeesInstructionDataArgs['newFeeWallet']; + newNcnFeeGroup: SetConfigFeesInstructionDataArgs['newNcnFeeGroup']; }; export function getSetConfigFeesInstruction< diff --git a/clients/js/jito_tip_router/instructions/snapshotVaultOperatorDelegation.ts b/clients/js/jito_tip_router/instructions/snapshotVaultOperatorDelegation.ts index 17fbe18..e077e0c 100644 --- a/clients/js/jito_tip_router/instructions/snapshotVaultOperatorDelegation.ts +++ b/clients/js/jito_tip_router/instructions/snapshotVaultOperatorDelegation.ts @@ -45,6 +45,7 @@ export type SnapshotVaultOperatorDelegationInstruction< TProgram extends string = typeof JITO_TIP_ROUTER_PROGRAM_ADDRESS, TAccountNcnConfig extends string | IAccountMeta = string, TAccountRestakingConfig extends string | IAccountMeta = string, + TAccountTrackedMints extends string | IAccountMeta = string, TAccountNcn extends string | IAccountMeta = string, TAccountOperator extends string | IAccountMeta = string, TAccountVault extends string | IAccountMeta = string, @@ -69,6 +70,9 @@ export type SnapshotVaultOperatorDelegationInstruction< TAccountRestakingConfig extends string ? ReadonlyAccount : TAccountRestakingConfig, + TAccountTrackedMints extends string + ? ReadonlyAccount + : TAccountTrackedMints, TAccountNcn extends string ? ReadonlyAccount : TAccountNcn, TAccountOperator extends string ? ReadonlyAccount @@ -146,6 +150,7 @@ export function getSnapshotVaultOperatorDelegationInstructionDataCodec(): Codec< export type SnapshotVaultOperatorDelegationInput< TAccountNcnConfig extends string = string, TAccountRestakingConfig extends string = string, + TAccountTrackedMints extends string = string, TAccountNcn extends string = string, TAccountOperator extends string = string, TAccountVault extends string = string, @@ -160,6 +165,7 @@ export type SnapshotVaultOperatorDelegationInput< > = { ncnConfig: Address; restakingConfig: Address; + trackedMints: Address; ncn: Address; operator: Address; vault: Address; @@ -177,6 +183,7 @@ export type SnapshotVaultOperatorDelegationInput< export function getSnapshotVaultOperatorDelegationInstruction< TAccountNcnConfig extends string, TAccountRestakingConfig extends string, + TAccountTrackedMints extends string, TAccountNcn extends string, TAccountOperator extends string, TAccountVault extends string, @@ -193,6 +200,7 @@ export function getSnapshotVaultOperatorDelegationInstruction< input: SnapshotVaultOperatorDelegationInput< TAccountNcnConfig, TAccountRestakingConfig, + TAccountTrackedMints, TAccountNcn, TAccountOperator, TAccountVault, @@ -210,6 +218,7 @@ export function getSnapshotVaultOperatorDelegationInstruction< TProgramAddress, TAccountNcnConfig, TAccountRestakingConfig, + TAccountTrackedMints, TAccountNcn, TAccountOperator, TAccountVault, @@ -233,6 +242,7 @@ export function getSnapshotVaultOperatorDelegationInstruction< value: input.restakingConfig ?? null, isWritable: false, }, + trackedMints: { value: input.trackedMints ?? null, isWritable: false }, ncn: { value: input.ncn ?? null, isWritable: false }, operator: { value: input.operator ?? null, isWritable: false }, vault: { value: input.vault ?? null, isWritable: false }, @@ -267,6 +277,7 @@ export function getSnapshotVaultOperatorDelegationInstruction< accounts: [ getAccountMeta(accounts.ncnConfig), getAccountMeta(accounts.restakingConfig), + getAccountMeta(accounts.trackedMints), getAccountMeta(accounts.ncn), getAccountMeta(accounts.operator), getAccountMeta(accounts.vault), @@ -287,6 +298,7 @@ export function getSnapshotVaultOperatorDelegationInstruction< TProgramAddress, TAccountNcnConfig, TAccountRestakingConfig, + TAccountTrackedMints, TAccountNcn, TAccountOperator, TAccountVault, @@ -311,17 +323,18 @@ export type ParsedSnapshotVaultOperatorDelegationInstruction< accounts: { ncnConfig: TAccountMetas[0]; restakingConfig: TAccountMetas[1]; - ncn: TAccountMetas[2]; - operator: TAccountMetas[3]; - vault: TAccountMetas[4]; - vaultNcnTicket: TAccountMetas[5]; - ncnVaultTicket: TAccountMetas[6]; - vaultOperatorDelegation: TAccountMetas[7]; - weightTable: TAccountMetas[8]; - epochSnapshot: TAccountMetas[9]; - operatorSnapshot: TAccountMetas[10]; - vaultProgram: TAccountMetas[11]; - restakingProgram: TAccountMetas[12]; + trackedMints: TAccountMetas[2]; + ncn: TAccountMetas[3]; + operator: TAccountMetas[4]; + vault: TAccountMetas[5]; + vaultNcnTicket: TAccountMetas[6]; + ncnVaultTicket: TAccountMetas[7]; + vaultOperatorDelegation: TAccountMetas[8]; + weightTable: TAccountMetas[9]; + epochSnapshot: TAccountMetas[10]; + operatorSnapshot: TAccountMetas[11]; + vaultProgram: TAccountMetas[12]; + restakingProgram: TAccountMetas[13]; }; data: SnapshotVaultOperatorDelegationInstructionData; }; @@ -334,7 +347,7 @@ export function parseSnapshotVaultOperatorDelegationInstruction< IInstructionWithAccounts & IInstructionWithData ): ParsedSnapshotVaultOperatorDelegationInstruction { - if (instruction.accounts.length < 13) { + if (instruction.accounts.length < 14) { // TODO: Coded error. throw new Error('Not enough accounts'); } @@ -349,6 +362,7 @@ export function parseSnapshotVaultOperatorDelegationInstruction< accounts: { ncnConfig: getNextAccount(), restakingConfig: getNextAccount(), + trackedMints: getNextAccount(), ncn: getNextAccount(), operator: getNextAccount(), vault: getNextAccount(), diff --git a/clients/js/jito_tip_router/programs/jitoTipRouter.ts b/clients/js/jito_tip_router/programs/jitoTipRouter.ts index 3c57061..9fb0c19 100644 --- a/clients/js/jito_tip_router/programs/jitoTipRouter.ts +++ b/clients/js/jito_tip_router/programs/jitoTipRouter.ts @@ -33,6 +33,8 @@ export enum JitoTipRouterAccount { EpochSnapshot, OperatorSnapshot, NcnConfig, + EpochRewardRouter, + OperatorRewardRouter, TrackedMints, WeightTable, } diff --git a/clients/js/jito_tip_router/types/fee.ts b/clients/js/jito_tip_router/types/fee.ts deleted file mode 100644 index f91f0c4..0000000 --- a/clients/js/jito_tip_router/types/fee.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * This code was AUTOGENERATED using the kinobi library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun kinobi to update it. - * - * @see https://github.com/kinobi-so/kinobi - */ - -import { - combineCodec, - getAddressDecoder, - getAddressEncoder, - getStructDecoder, - getStructEncoder, - getU64Decoder, - getU64Encoder, - type Address, - type Codec, - type Decoder, - type Encoder, -} from '@solana/web3.js'; - -export type Fee = { - wallet: Address; - daoShareBps: bigint; - ncnShareBps: bigint; - blockEngineFeeBps: bigint; - activationEpoch: bigint; -}; - -export type FeeArgs = { - wallet: Address; - daoShareBps: number | bigint; - ncnShareBps: number | bigint; - blockEngineFeeBps: number | bigint; - activationEpoch: number | bigint; -}; - -export function getFeeEncoder(): Encoder { - return getStructEncoder([ - ['wallet', getAddressEncoder()], - ['daoShareBps', getU64Encoder()], - ['ncnShareBps', getU64Encoder()], - ['blockEngineFeeBps', getU64Encoder()], - ['activationEpoch', getU64Encoder()], - ]); -} - -export function getFeeDecoder(): Decoder { - return getStructDecoder([ - ['wallet', getAddressDecoder()], - ['daoShareBps', getU64Decoder()], - ['ncnShareBps', getU64Decoder()], - ['blockEngineFeeBps', getU64Decoder()], - ['activationEpoch', getU64Decoder()], - ]); -} - -export function getFeeCodec(): Codec { - return combineCodec(getFeeEncoder(), getFeeDecoder()); -} diff --git a/clients/js/jito_tip_router/types/feeConfig.ts b/clients/js/jito_tip_router/types/feeConfig.ts new file mode 100644 index 0000000..5482cfe --- /dev/null +++ b/clients/js/jito_tip_router/types/feeConfig.ts @@ -0,0 +1,48 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { + combineCodec, + getAddressDecoder, + getAddressEncoder, + getStructDecoder, + getStructEncoder, + type Address, + type Codec, + type Decoder, + type Encoder, +} from '@solana/web3.js'; +import { getFeesDecoder, getFeesEncoder, type Fees, type FeesArgs } from '.'; + +export type FeeConfig = { daoFeeWallet: Address; fee1: Fees; fee2: Fees }; + +export type FeeConfigArgs = { + daoFeeWallet: Address; + fee1: FeesArgs; + fee2: FeesArgs; +}; + +export function getFeeConfigEncoder(): Encoder { + return getStructEncoder([ + ['daoFeeWallet', getAddressEncoder()], + ['fee1', getFeesEncoder()], + ['fee2', getFeesEncoder()], + ]); +} + +export function getFeeConfigDecoder(): Decoder { + return getStructDecoder([ + ['daoFeeWallet', getAddressDecoder()], + ['fee1', getFeesDecoder()], + ['fee2', getFeesDecoder()], + ]); +} + +export function getFeeConfigCodec(): Codec { + return combineCodec(getFeeConfigEncoder(), getFeeConfigDecoder()); +} diff --git a/clients/js/jito_tip_router/types/fees.ts b/clients/js/jito_tip_router/types/fees.ts index 2803f66..ee572f3 100644 --- a/clients/js/jito_tip_router/types/fees.ts +++ b/clients/js/jito_tip_router/types/fees.ts @@ -8,29 +8,65 @@ import { combineCodec, + fixDecoderSize, + fixEncoderSize, + getArrayDecoder, + getArrayEncoder, + getBytesDecoder, + getBytesEncoder, getStructDecoder, getStructEncoder, + getU64Decoder, + getU64Encoder, type Codec, type Decoder, type Encoder, + type ReadonlyUint8Array, } from '@solana/web3.js'; -import { getFeeDecoder, getFeeEncoder, type Fee, type FeeArgs } from '.'; +import { + getNcnFeeDecoder, + getNcnFeeEncoder, + type NcnFee, + type NcnFeeArgs, +} from '.'; -export type Fees = { fee1: Fee; fee2: Fee }; +export type Fees = { + activationEpoch: bigint; + blockEngineFeeBps: bigint; + daoFeeBps: bigint; + ncnDefaultFeeBps: bigint; + ncnFeeGroupsBps: Array; + reserved: ReadonlyUint8Array; +}; -export type FeesArgs = { fee1: FeeArgs; fee2: FeeArgs }; +export type FeesArgs = { + activationEpoch: number | bigint; + blockEngineFeeBps: number | bigint; + daoFeeBps: number | bigint; + ncnDefaultFeeBps: number | bigint; + ncnFeeGroupsBps: Array; + reserved: ReadonlyUint8Array; +}; export function getFeesEncoder(): Encoder { return getStructEncoder([ - ['fee1', getFeeEncoder()], - ['fee2', getFeeEncoder()], + ['activationEpoch', getU64Encoder()], + ['blockEngineFeeBps', getU64Encoder()], + ['daoFeeBps', getU64Encoder()], + ['ncnDefaultFeeBps', getU64Encoder()], + ['ncnFeeGroupsBps', getArrayEncoder(getNcnFeeEncoder(), { size: 8 })], + ['reserved', fixEncoderSize(getBytesEncoder(), 64)], ]); } export function getFeesDecoder(): Decoder { return getStructDecoder([ - ['fee1', getFeeDecoder()], - ['fee2', getFeeDecoder()], + ['activationEpoch', getU64Decoder()], + ['blockEngineFeeBps', getU64Decoder()], + ['daoFeeBps', getU64Decoder()], + ['ncnDefaultFeeBps', getU64Decoder()], + ['ncnFeeGroupsBps', getArrayDecoder(getNcnFeeDecoder(), { size: 8 })], + ['reserved', fixDecoderSize(getBytesDecoder(), 64)], ]); } diff --git a/clients/js/jito_tip_router/types/index.ts b/clients/js/jito_tip_router/types/index.ts index b49207d..05ea810 100644 --- a/clients/js/jito_tip_router/types/index.ts +++ b/clients/js/jito_tip_router/types/index.ts @@ -9,9 +9,13 @@ export * from './ballot'; export * from './ballotTally'; export * from './configAdminRole'; -export * from './fee'; +export * from './feeConfig'; export * from './fees'; export * from './mintEntry'; +export * from './ncnFee'; +export * from './ncnFeeGroup'; +export * from './operatorReward'; export * from './operatorVote'; export * from './vaultOperatorStakeWeight'; +export * from './vaultReward'; export * from './weightEntry'; diff --git a/clients/js/jito_tip_router/types/mintEntry.ts b/clients/js/jito_tip_router/types/mintEntry.ts index aade315..3dfaf49 100644 --- a/clients/js/jito_tip_router/types/mintEntry.ts +++ b/clients/js/jito_tip_router/types/mintEntry.ts @@ -24,16 +24,24 @@ import { type Encoder, type ReadonlyUint8Array, } from '@solana/web3.js'; +import { + getNcnFeeGroupDecoder, + getNcnFeeGroupEncoder, + type NcnFeeGroup, + type NcnFeeGroupArgs, +} from '.'; export type MintEntry = { stMint: Address; vaultIndex: bigint; + ncnFeeGroup: NcnFeeGroup; reserved: ReadonlyUint8Array; }; export type MintEntryArgs = { stMint: Address; vaultIndex: number | bigint; + ncnFeeGroup: NcnFeeGroupArgs; reserved: ReadonlyUint8Array; }; @@ -41,6 +49,7 @@ export function getMintEntryEncoder(): Encoder { return getStructEncoder([ ['stMint', getAddressEncoder()], ['vaultIndex', getU64Encoder()], + ['ncnFeeGroup', getNcnFeeGroupEncoder()], ['reserved', fixEncoderSize(getBytesEncoder(), 32)], ]); } @@ -49,6 +58,7 @@ export function getMintEntryDecoder(): Decoder { return getStructDecoder([ ['stMint', getAddressDecoder()], ['vaultIndex', getU64Decoder()], + ['ncnFeeGroup', getNcnFeeGroupDecoder()], ['reserved', fixDecoderSize(getBytesDecoder(), 32)], ]); } diff --git a/clients/js/jito_tip_router/types/ncnFee.ts b/clients/js/jito_tip_router/types/ncnFee.ts new file mode 100644 index 0000000..e537d1a --- /dev/null +++ b/clients/js/jito_tip_router/types/ncnFee.ts @@ -0,0 +1,34 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { + combineCodec, + getStructDecoder, + getStructEncoder, + getU64Decoder, + getU64Encoder, + type Codec, + type Decoder, + type Encoder, +} from '@solana/web3.js'; + +export type NcnFee = { fee: bigint }; + +export type NcnFeeArgs = { fee: number | bigint }; + +export function getNcnFeeEncoder(): Encoder { + return getStructEncoder([['fee', getU64Encoder()]]); +} + +export function getNcnFeeDecoder(): Decoder { + return getStructDecoder([['fee', getU64Decoder()]]); +} + +export function getNcnFeeCodec(): Codec { + return combineCodec(getNcnFeeEncoder(), getNcnFeeDecoder()); +} diff --git a/clients/js/jito_tip_router/types/ncnFeeGroup.ts b/clients/js/jito_tip_router/types/ncnFeeGroup.ts new file mode 100644 index 0000000..c9e3c57 --- /dev/null +++ b/clients/js/jito_tip_router/types/ncnFeeGroup.ts @@ -0,0 +1,34 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { + combineCodec, + getStructDecoder, + getStructEncoder, + getU8Decoder, + getU8Encoder, + type Codec, + type Decoder, + type Encoder, +} from '@solana/web3.js'; + +export type NcnFeeGroup = { group: number }; + +export type NcnFeeGroupArgs = NcnFeeGroup; + +export function getNcnFeeGroupEncoder(): Encoder { + return getStructEncoder([['group', getU8Encoder()]]); +} + +export function getNcnFeeGroupDecoder(): Decoder { + return getStructDecoder([['group', getU8Decoder()]]); +} + +export function getNcnFeeGroupCodec(): Codec { + return combineCodec(getNcnFeeGroupEncoder(), getNcnFeeGroupDecoder()); +} diff --git a/clients/js/jito_tip_router/types/operatorReward.ts b/clients/js/jito_tip_router/types/operatorReward.ts new file mode 100644 index 0000000..3fefe57 --- /dev/null +++ b/clients/js/jito_tip_router/types/operatorReward.ts @@ -0,0 +1,60 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { + combineCodec, + getAddressDecoder, + getAddressEncoder, + getArrayDecoder, + getArrayEncoder, + getStructDecoder, + getStructEncoder, + getU64Decoder, + getU64Encoder, + getU8Decoder, + getU8Encoder, + type Address, + type Codec, + type Decoder, + type Encoder, +} from '@solana/web3.js'; + +export type OperatorReward = { + operator: Address; + reward: bigint; + reserved: Array; +}; + +export type OperatorRewardArgs = { + operator: Address; + reward: number | bigint; + reserved: Array; +}; + +export function getOperatorRewardEncoder(): Encoder { + return getStructEncoder([ + ['operator', getAddressEncoder()], + ['reward', getU64Encoder()], + ['reserved', getArrayEncoder(getU8Encoder(), { size: 128 })], + ]); +} + +export function getOperatorRewardDecoder(): Decoder { + return getStructDecoder([ + ['operator', getAddressDecoder()], + ['reward', getU64Decoder()], + ['reserved', getArrayDecoder(getU8Decoder(), { size: 128 })], + ]); +} + +export function getOperatorRewardCodec(): Codec< + OperatorRewardArgs, + OperatorReward +> { + return combineCodec(getOperatorRewardEncoder(), getOperatorRewardDecoder()); +} diff --git a/clients/js/jito_tip_router/types/vaultOperatorStakeWeight.ts b/clients/js/jito_tip_router/types/vaultOperatorStakeWeight.ts index bd2932c..1a32b77 100644 --- a/clients/js/jito_tip_router/types/vaultOperatorStakeWeight.ts +++ b/clients/js/jito_tip_router/types/vaultOperatorStakeWeight.ts @@ -30,6 +30,7 @@ import { export type VaultOperatorStakeWeight = { vault: Address; stakeWeight: bigint; + rewardStakeWeight: bigint; vaultIndex: bigint; reserved: ReadonlyUint8Array; }; @@ -37,6 +38,7 @@ export type VaultOperatorStakeWeight = { export type VaultOperatorStakeWeightArgs = { vault: Address; stakeWeight: number | bigint; + rewardStakeWeight: number | bigint; vaultIndex: number | bigint; reserved: ReadonlyUint8Array; }; @@ -45,6 +47,7 @@ export function getVaultOperatorStakeWeightEncoder(): Encoder; +}; + +export type VaultRewardArgs = { + vault: Address; + reward: number | bigint; + reserved: Array; +}; + +export function getVaultRewardEncoder(): Encoder { + return getStructEncoder([ + ['vault', getAddressEncoder()], + ['reward', getU64Encoder()], + ['reserved', getArrayEncoder(getU8Encoder(), { size: 128 })], + ]); +} + +export function getVaultRewardDecoder(): Decoder { + return getStructDecoder([ + ['vault', getAddressDecoder()], + ['reward', getU64Decoder()], + ['reserved', getArrayDecoder(getU8Decoder(), { size: 128 })], + ]); +} + +export function getVaultRewardCodec(): Codec { + return combineCodec(getVaultRewardEncoder(), getVaultRewardDecoder()); +} diff --git a/clients/rust/jito_tip_router/src/generated/accounts/epoch_reward_router.rs b/clients/rust/jito_tip_router/src/generated/accounts/epoch_reward_router.rs new file mode 100644 index 0000000..b5527de --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/accounts/epoch_reward_router.rs @@ -0,0 +1,72 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! + +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +use crate::generated::types::OperatorReward; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct EpochRewardRouter { + pub discriminator: u64, + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub ncn: Pubkey, + pub ncn_epoch: u64, + pub bump: u8, + pub slot_created: u64, + #[cfg_attr(feature = "serde", serde(with = "serde_with::As::"))] + pub reserved: [u8; 128], + pub reward_pool: u64, + pub operator_rewards: [OperatorReward; 32], +} + +impl EpochRewardRouter { + #[inline(always)] + pub fn from_bytes(data: &[u8]) -> Result { + let mut data = data; + Self::deserialize(&mut data) + } +} + +impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for EpochRewardRouter { + type Error = std::io::Error; + + fn try_from( + account_info: &solana_program::account_info::AccountInfo<'a>, + ) -> Result { + let mut data: &[u8] = &(*account_info.data).borrow(); + Self::deserialize(&mut data) + } +} + +#[cfg(feature = "anchor")] +impl anchor_lang::AccountDeserialize for EpochRewardRouter { + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { + Ok(Self::deserialize(buf)?) + } +} + +#[cfg(feature = "anchor")] +impl anchor_lang::AccountSerialize for EpochRewardRouter {} + +#[cfg(feature = "anchor")] +impl anchor_lang::Owner for EpochRewardRouter { + fn owner() -> Pubkey { + crate::JITO_TIP_ROUTER_ID + } +} + +#[cfg(feature = "anchor-idl-build")] +impl anchor_lang::IdlBuild for EpochRewardRouter {} + +#[cfg(feature = "anchor-idl-build")] +impl anchor_lang::Discriminator for EpochRewardRouter { + const DISCRIMINATOR: [u8; 8] = [0; 8]; +} diff --git a/clients/rust/jito_tip_router/src/generated/accounts/epoch_snapshot.rs b/clients/rust/jito_tip_router/src/generated/accounts/epoch_snapshot.rs index 991ef77..b97cc56 100644 --- a/clients/rust/jito_tip_router/src/generated/accounts/epoch_snapshot.rs +++ b/clients/rust/jito_tip_router/src/generated/accounts/epoch_snapshot.rs @@ -7,7 +7,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::pubkey::Pubkey; -use crate::generated::types::Fees; +use crate::generated::types::FeeConfig; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -22,12 +22,13 @@ pub struct EpochSnapshot { pub bump: u8, pub slot_created: u64, pub slot_finalized: u64, - pub ncn_fees: Fees, + pub fees: FeeConfig, pub operator_count: u64, pub vault_count: u64, pub operators_registered: u64, pub valid_operator_vault_delegations: u64, pub stake_weight: u128, + pub reward_stake_weight: u128, #[cfg_attr(feature = "serde", serde(with = "serde_with::As::"))] pub reserved: [u8; 128], } diff --git a/clients/rust/jito_tip_router/src/generated/accounts/mod.rs b/clients/rust/jito_tip_router/src/generated/accounts/mod.rs index aa0f253..70f97c4 100644 --- a/clients/rust/jito_tip_router/src/generated/accounts/mod.rs +++ b/clients/rust/jito_tip_router/src/generated/accounts/mod.rs @@ -5,13 +5,15 @@ //! pub(crate) mod r#ballot_box; +pub(crate) mod r#epoch_reward_router; pub(crate) mod r#epoch_snapshot; pub(crate) mod r#ncn_config; +pub(crate) mod r#operator_reward_router; pub(crate) mod r#operator_snapshot; pub(crate) mod r#tracked_mints; pub(crate) mod r#weight_table; pub use self::{ - r#ballot_box::*, r#epoch_snapshot::*, r#ncn_config::*, r#operator_snapshot::*, - r#tracked_mints::*, r#weight_table::*, + r#ballot_box::*, r#epoch_reward_router::*, r#epoch_snapshot::*, r#ncn_config::*, + r#operator_reward_router::*, r#operator_snapshot::*, r#tracked_mints::*, r#weight_table::*, }; 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 eaca31b..6d4eb2a 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 @@ -7,7 +7,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::pubkey::Pubkey; -use crate::generated::types::Fees; +use crate::generated::types::FeeConfig; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -28,7 +28,7 @@ pub struct NcnConfig { serde(with = "serde_with::As::") )] pub fee_admin: Pubkey, - pub fees: Fees, + pub fee_config: FeeConfig, pub bump: u8, #[cfg_attr(feature = "serde", serde(with = "serde_with::As::"))] pub reserved: [u8; 127], diff --git a/clients/rust/jito_tip_router/src/generated/accounts/operator_reward_router.rs b/clients/rust/jito_tip_router/src/generated/accounts/operator_reward_router.rs new file mode 100644 index 0000000..6c0bc09 --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/accounts/operator_reward_router.rs @@ -0,0 +1,72 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! + +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +use crate::generated::types::VaultReward; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct OperatorRewardRouter { + pub discriminator: u64, + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub ncn: Pubkey, + pub ncn_epoch: u64, + pub bump: u8, + pub slot_created: u64, + #[cfg_attr(feature = "serde", serde(with = "serde_with::As::"))] + pub reserved: [u8; 128], + pub reward_pool: u64, + pub vault_rewards: [VaultReward; 32], +} + +impl OperatorRewardRouter { + #[inline(always)] + pub fn from_bytes(data: &[u8]) -> Result { + let mut data = data; + Self::deserialize(&mut data) + } +} + +impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for OperatorRewardRouter { + type Error = std::io::Error; + + fn try_from( + account_info: &solana_program::account_info::AccountInfo<'a>, + ) -> Result { + let mut data: &[u8] = &(*account_info.data).borrow(); + Self::deserialize(&mut data) + } +} + +#[cfg(feature = "anchor")] +impl anchor_lang::AccountDeserialize for OperatorRewardRouter { + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { + Ok(Self::deserialize(buf)?) + } +} + +#[cfg(feature = "anchor")] +impl anchor_lang::AccountSerialize for OperatorRewardRouter {} + +#[cfg(feature = "anchor")] +impl anchor_lang::Owner for OperatorRewardRouter { + fn owner() -> Pubkey { + crate::JITO_TIP_ROUTER_ID + } +} + +#[cfg(feature = "anchor-idl-build")] +impl anchor_lang::IdlBuild for OperatorRewardRouter {} + +#[cfg(feature = "anchor-idl-build")] +impl anchor_lang::Discriminator for OperatorRewardRouter { + const DISCRIMINATOR: [u8; 8] = [0; 8]; +} diff --git a/clients/rust/jito_tip_router/src/generated/accounts/operator_snapshot.rs b/clients/rust/jito_tip_router/src/generated/accounts/operator_snapshot.rs index a30fbde..a626a25 100644 --- a/clients/rust/jito_tip_router/src/generated/accounts/operator_snapshot.rs +++ b/clients/rust/jito_tip_router/src/generated/accounts/operator_snapshot.rs @@ -35,6 +35,7 @@ pub struct OperatorSnapshot { pub vault_operator_delegations_registered: u64, pub valid_operator_vault_delegations: u64, pub stake_weight: u128, + pub reward_stake_weight: u128, #[cfg_attr(feature = "serde", serde(with = "serde_with::As::"))] pub reserved: [u8; 256], pub vault_operator_stake_weight: [VaultOperatorStakeWeight; 32], 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 ce806cb..8613a37 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 @@ -114,6 +114,12 @@ pub enum JitoTipRouterError { /// 8733 - Consensus not reached #[error("Consensus not reached")] ConsensusNotReached = 0x221D, + /// 8734 - Not a valid NCN share group + #[error("Not a valid NCN share group")] + NotValidNcnShareGroup = 0x221E, + /// 8735 - Tracked Mints does not contain vault index + #[error("Tracked Mints does not contain vault index")] + TrackedMintsVaultIndexDne = 0x221F, } impl solana_program::program_error::PrintProgramError for JitoTipRouterError { diff --git a/clients/rust/jito_tip_router/src/generated/instructions/set_config_fees.rs b/clients/rust/jito_tip_router/src/generated/instructions/set_config_fees.rs index 877c04f..4ecd0fe 100644 --- a/clients/rust/jito_tip_router/src/generated/instructions/set_config_fees.rs +++ b/clients/rust/jito_tip_router/src/generated/instructions/set_config_fees.rs @@ -86,10 +86,11 @@ impl Default for SetConfigFeesInstructionData { #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct SetConfigFeesInstructionArgs { + pub new_fee_wallet: Option, + pub new_block_engine_fee_bps: Option, pub new_dao_fee_bps: Option, pub new_ncn_fee_bps: Option, - pub new_block_engine_fee_bps: Option, - pub new_fee_wallet: Option, + pub new_ncn_fee_group: Option, } /// Instruction builder for `SetConfigFees`. @@ -108,10 +109,11 @@ pub struct SetConfigFeesBuilder { ncn: Option, ncn_admin: Option, restaking_program: Option, + new_fee_wallet: Option, + new_block_engine_fee_bps: Option, new_dao_fee_bps: Option, new_ncn_fee_bps: Option, - new_block_engine_fee_bps: Option, - new_fee_wallet: Option, + new_ncn_fee_group: Option, __remaining_accounts: Vec, } @@ -152,26 +154,32 @@ impl SetConfigFeesBuilder { } /// `[optional argument]` #[inline(always)] - pub fn new_dao_fee_bps(&mut self, new_dao_fee_bps: u64) -> &mut Self { - self.new_dao_fee_bps = Some(new_dao_fee_bps); + pub fn new_fee_wallet(&mut self, new_fee_wallet: Pubkey) -> &mut Self { + self.new_fee_wallet = Some(new_fee_wallet); self } /// `[optional argument]` #[inline(always)] - pub fn new_ncn_fee_bps(&mut self, new_ncn_fee_bps: u64) -> &mut Self { - self.new_ncn_fee_bps = Some(new_ncn_fee_bps); + pub fn new_block_engine_fee_bps(&mut self, new_block_engine_fee_bps: u64) -> &mut Self { + self.new_block_engine_fee_bps = Some(new_block_engine_fee_bps); self } /// `[optional argument]` #[inline(always)] - pub fn new_block_engine_fee_bps(&mut self, new_block_engine_fee_bps: u64) -> &mut Self { - self.new_block_engine_fee_bps = Some(new_block_engine_fee_bps); + pub fn new_dao_fee_bps(&mut self, new_dao_fee_bps: u64) -> &mut Self { + self.new_dao_fee_bps = Some(new_dao_fee_bps); self } /// `[optional argument]` #[inline(always)] - pub fn new_fee_wallet(&mut self, new_fee_wallet: Pubkey) -> &mut Self { - self.new_fee_wallet = Some(new_fee_wallet); + pub fn new_ncn_fee_bps(&mut self, new_ncn_fee_bps: u64) -> &mut Self { + self.new_ncn_fee_bps = Some(new_ncn_fee_bps); + self + } + /// `[optional argument]` + #[inline(always)] + pub fn new_ncn_fee_group(&mut self, new_ncn_fee_group: u8) -> &mut Self { + self.new_ncn_fee_group = Some(new_ncn_fee_group); self } /// Add an additional account to the instruction. @@ -204,10 +212,11 @@ impl SetConfigFeesBuilder { .expect("restaking_program is not set"), }; let args = SetConfigFeesInstructionArgs { + new_fee_wallet: self.new_fee_wallet.clone(), + new_block_engine_fee_bps: self.new_block_engine_fee_bps.clone(), new_dao_fee_bps: self.new_dao_fee_bps.clone(), new_ncn_fee_bps: self.new_ncn_fee_bps.clone(), - new_block_engine_fee_bps: self.new_block_engine_fee_bps.clone(), - new_fee_wallet: self.new_fee_wallet.clone(), + new_ncn_fee_group: self.new_ncn_fee_group.clone(), }; accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) @@ -373,10 +382,11 @@ impl<'a, 'b> SetConfigFeesCpiBuilder<'a, 'b> { ncn: None, ncn_admin: None, restaking_program: None, + new_fee_wallet: None, + new_block_engine_fee_bps: None, new_dao_fee_bps: None, new_ncn_fee_bps: None, - new_block_engine_fee_bps: None, - new_fee_wallet: None, + new_ncn_fee_group: None, __remaining_accounts: Vec::new(), }); Self { instruction } @@ -420,26 +430,32 @@ impl<'a, 'b> SetConfigFeesCpiBuilder<'a, 'b> { } /// `[optional argument]` #[inline(always)] - pub fn new_dao_fee_bps(&mut self, new_dao_fee_bps: u64) -> &mut Self { - self.instruction.new_dao_fee_bps = Some(new_dao_fee_bps); + pub fn new_fee_wallet(&mut self, new_fee_wallet: Pubkey) -> &mut Self { + self.instruction.new_fee_wallet = Some(new_fee_wallet); self } /// `[optional argument]` #[inline(always)] - pub fn new_ncn_fee_bps(&mut self, new_ncn_fee_bps: u64) -> &mut Self { - self.instruction.new_ncn_fee_bps = Some(new_ncn_fee_bps); + pub fn new_block_engine_fee_bps(&mut self, new_block_engine_fee_bps: u64) -> &mut Self { + self.instruction.new_block_engine_fee_bps = Some(new_block_engine_fee_bps); self } /// `[optional argument]` #[inline(always)] - pub fn new_block_engine_fee_bps(&mut self, new_block_engine_fee_bps: u64) -> &mut Self { - self.instruction.new_block_engine_fee_bps = Some(new_block_engine_fee_bps); + pub fn new_dao_fee_bps(&mut self, new_dao_fee_bps: u64) -> &mut Self { + self.instruction.new_dao_fee_bps = Some(new_dao_fee_bps); self } /// `[optional argument]` #[inline(always)] - pub fn new_fee_wallet(&mut self, new_fee_wallet: Pubkey) -> &mut Self { - self.instruction.new_fee_wallet = Some(new_fee_wallet); + pub fn new_ncn_fee_bps(&mut self, new_ncn_fee_bps: u64) -> &mut Self { + self.instruction.new_ncn_fee_bps = Some(new_ncn_fee_bps); + self + } + /// `[optional argument]` + #[inline(always)] + pub fn new_ncn_fee_group(&mut self, new_ncn_fee_group: u8) -> &mut Self { + self.instruction.new_ncn_fee_group = Some(new_ncn_fee_group); self } /// Add an additional account to the instruction. @@ -484,10 +500,11 @@ impl<'a, 'b> SetConfigFeesCpiBuilder<'a, 'b> { signers_seeds: &[&[&[u8]]], ) -> solana_program::entrypoint::ProgramResult { let args = SetConfigFeesInstructionArgs { + new_fee_wallet: self.instruction.new_fee_wallet.clone(), + new_block_engine_fee_bps: self.instruction.new_block_engine_fee_bps.clone(), new_dao_fee_bps: self.instruction.new_dao_fee_bps.clone(), new_ncn_fee_bps: self.instruction.new_ncn_fee_bps.clone(), - new_block_engine_fee_bps: self.instruction.new_block_engine_fee_bps.clone(), - new_fee_wallet: self.instruction.new_fee_wallet.clone(), + new_ncn_fee_group: self.instruction.new_ncn_fee_group.clone(), }; let instruction = SetConfigFeesCpi { __program: self.instruction.__program, @@ -524,10 +541,11 @@ struct SetConfigFeesCpiBuilderInstruction<'a, 'b> { ncn: Option<&'b solana_program::account_info::AccountInfo<'a>>, ncn_admin: Option<&'b solana_program::account_info::AccountInfo<'a>>, restaking_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + new_fee_wallet: Option, + new_block_engine_fee_bps: Option, new_dao_fee_bps: Option, new_ncn_fee_bps: Option, - new_block_engine_fee_bps: Option, - new_fee_wallet: Option, + new_ncn_fee_group: Option, /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. __remaining_accounts: Vec<( &'b solana_program::account_info::AccountInfo<'a>, diff --git a/clients/rust/jito_tip_router/src/generated/instructions/snapshot_vault_operator_delegation.rs b/clients/rust/jito_tip_router/src/generated/instructions/snapshot_vault_operator_delegation.rs index 86b411b..051b40f 100644 --- a/clients/rust/jito_tip_router/src/generated/instructions/snapshot_vault_operator_delegation.rs +++ b/clients/rust/jito_tip_router/src/generated/instructions/snapshot_vault_operator_delegation.rs @@ -12,6 +12,8 @@ pub struct SnapshotVaultOperatorDelegation { pub restaking_config: solana_program::pubkey::Pubkey, + pub tracked_mints: solana_program::pubkey::Pubkey, + pub ncn: solana_program::pubkey::Pubkey, pub operator: solana_program::pubkey::Pubkey, @@ -48,7 +50,7 @@ impl SnapshotVaultOperatorDelegation { args: SnapshotVaultOperatorDelegationInstructionArgs, remaining_accounts: &[solana_program::instruction::AccountMeta], ) -> solana_program::instruction::Instruction { - let mut accounts = Vec::with_capacity(13 + remaining_accounts.len()); + let mut accounts = Vec::with_capacity(14 + remaining_accounts.len()); accounts.push(solana_program::instruction::AccountMeta::new_readonly( self.ncn_config, false, @@ -57,6 +59,10 @@ impl SnapshotVaultOperatorDelegation { self.restaking_config, false, )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.tracked_mints, + false, + )); accounts.push(solana_program::instruction::AccountMeta::new_readonly( self.ncn, false, )); @@ -143,21 +149,23 @@ pub struct SnapshotVaultOperatorDelegationInstructionArgs { /// /// 0. `[]` ncn_config /// 1. `[]` restaking_config -/// 2. `[]` ncn -/// 3. `[]` operator -/// 4. `[]` vault -/// 5. `[]` vault_ncn_ticket -/// 6. `[]` ncn_vault_ticket -/// 7. `[]` vault_operator_delegation -/// 8. `[]` weight_table -/// 9. `[writable]` epoch_snapshot -/// 10. `[writable]` operator_snapshot -/// 11. `[]` vault_program -/// 12. `[]` restaking_program +/// 2. `[]` tracked_mints +/// 3. `[]` ncn +/// 4. `[]` operator +/// 5. `[]` vault +/// 6. `[]` vault_ncn_ticket +/// 7. `[]` ncn_vault_ticket +/// 8. `[]` vault_operator_delegation +/// 9. `[]` weight_table +/// 10. `[writable]` epoch_snapshot +/// 11. `[writable]` operator_snapshot +/// 12. `[]` vault_program +/// 13. `[]` restaking_program #[derive(Clone, Debug, Default)] pub struct SnapshotVaultOperatorDelegationBuilder { ncn_config: Option, restaking_config: Option, + tracked_mints: Option, ncn: Option, operator: Option, vault: Option, @@ -191,6 +199,11 @@ impl SnapshotVaultOperatorDelegationBuilder { self } #[inline(always)] + pub fn tracked_mints(&mut self, tracked_mints: solana_program::pubkey::Pubkey) -> &mut Self { + self.tracked_mints = Some(tracked_mints); + self + } + #[inline(always)] pub fn ncn(&mut self, ncn: solana_program::pubkey::Pubkey) -> &mut Self { self.ncn = Some(ncn); self @@ -289,6 +302,7 @@ impl SnapshotVaultOperatorDelegationBuilder { let accounts = SnapshotVaultOperatorDelegation { ncn_config: self.ncn_config.expect("ncn_config is not set"), restaking_config: self.restaking_config.expect("restaking_config is not set"), + tracked_mints: self.tracked_mints.expect("tracked_mints is not set"), ncn: self.ncn.expect("ncn is not set"), operator: self.operator.expect("operator is not set"), vault: self.vault.expect("vault is not set"), @@ -321,6 +335,8 @@ pub struct SnapshotVaultOperatorDelegationCpiAccounts<'a, 'b> { pub restaking_config: &'b solana_program::account_info::AccountInfo<'a>, + pub tracked_mints: &'b solana_program::account_info::AccountInfo<'a>, + pub ncn: &'b solana_program::account_info::AccountInfo<'a>, pub operator: &'b solana_program::account_info::AccountInfo<'a>, @@ -353,6 +369,8 @@ pub struct SnapshotVaultOperatorDelegationCpi<'a, 'b> { pub restaking_config: &'b solana_program::account_info::AccountInfo<'a>, + pub tracked_mints: &'b solana_program::account_info::AccountInfo<'a>, + pub ncn: &'b solana_program::account_info::AccountInfo<'a>, pub operator: &'b solana_program::account_info::AccountInfo<'a>, @@ -388,6 +406,7 @@ impl<'a, 'b> SnapshotVaultOperatorDelegationCpi<'a, 'b> { __program: program, ncn_config: accounts.ncn_config, restaking_config: accounts.restaking_config, + tracked_mints: accounts.tracked_mints, ncn: accounts.ncn, operator: accounts.operator, vault: accounts.vault, @@ -435,7 +454,7 @@ impl<'a, 'b> SnapshotVaultOperatorDelegationCpi<'a, 'b> { bool, )], ) -> solana_program::entrypoint::ProgramResult { - let mut accounts = Vec::with_capacity(13 + remaining_accounts.len()); + let mut accounts = Vec::with_capacity(14 + remaining_accounts.len()); accounts.push(solana_program::instruction::AccountMeta::new_readonly( *self.ncn_config.key, false, @@ -444,6 +463,10 @@ impl<'a, 'b> SnapshotVaultOperatorDelegationCpi<'a, 'b> { *self.restaking_config.key, false, )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.tracked_mints.key, + false, + )); accounts.push(solana_program::instruction::AccountMeta::new_readonly( *self.ncn.key, false, @@ -506,10 +529,11 @@ impl<'a, 'b> SnapshotVaultOperatorDelegationCpi<'a, 'b> { accounts, data, }; - let mut account_infos = Vec::with_capacity(13 + 1 + remaining_accounts.len()); + let mut account_infos = Vec::with_capacity(14 + 1 + remaining_accounts.len()); account_infos.push(self.__program.clone()); account_infos.push(self.ncn_config.clone()); account_infos.push(self.restaking_config.clone()); + account_infos.push(self.tracked_mints.clone()); account_infos.push(self.ncn.clone()); account_infos.push(self.operator.clone()); account_infos.push(self.vault.clone()); @@ -539,17 +563,18 @@ impl<'a, 'b> SnapshotVaultOperatorDelegationCpi<'a, 'b> { /// /// 0. `[]` ncn_config /// 1. `[]` restaking_config -/// 2. `[]` ncn -/// 3. `[]` operator -/// 4. `[]` vault -/// 5. `[]` vault_ncn_ticket -/// 6. `[]` ncn_vault_ticket -/// 7. `[]` vault_operator_delegation -/// 8. `[]` weight_table -/// 9. `[writable]` epoch_snapshot -/// 10. `[writable]` operator_snapshot -/// 11. `[]` vault_program -/// 12. `[]` restaking_program +/// 2. `[]` tracked_mints +/// 3. `[]` ncn +/// 4. `[]` operator +/// 5. `[]` vault +/// 6. `[]` vault_ncn_ticket +/// 7. `[]` ncn_vault_ticket +/// 8. `[]` vault_operator_delegation +/// 9. `[]` weight_table +/// 10. `[writable]` epoch_snapshot +/// 11. `[writable]` operator_snapshot +/// 12. `[]` vault_program +/// 13. `[]` restaking_program #[derive(Clone, Debug)] pub struct SnapshotVaultOperatorDelegationCpiBuilder<'a, 'b> { instruction: Box>, @@ -561,6 +586,7 @@ impl<'a, 'b> SnapshotVaultOperatorDelegationCpiBuilder<'a, 'b> { __program: program, ncn_config: None, restaking_config: None, + tracked_mints: None, ncn: None, operator: None, vault: None, @@ -594,6 +620,14 @@ impl<'a, 'b> SnapshotVaultOperatorDelegationCpiBuilder<'a, 'b> { self } #[inline(always)] + pub fn tracked_mints( + &mut self, + tracked_mints: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.tracked_mints = Some(tracked_mints); + self + } + #[inline(always)] pub fn ncn(&mut self, ncn: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { self.instruction.ncn = Some(ncn); self @@ -735,6 +769,11 @@ impl<'a, 'b> SnapshotVaultOperatorDelegationCpiBuilder<'a, 'b> { .restaking_config .expect("restaking_config is not set"), + tracked_mints: self + .instruction + .tracked_mints + .expect("tracked_mints is not set"), + ncn: self.instruction.ncn.expect("ncn is not set"), operator: self.instruction.operator.expect("operator is not set"), @@ -794,6 +833,7 @@ struct SnapshotVaultOperatorDelegationCpiBuilderInstruction<'a, 'b> { __program: &'b solana_program::account_info::AccountInfo<'a>, ncn_config: Option<&'b solana_program::account_info::AccountInfo<'a>>, restaking_config: Option<&'b solana_program::account_info::AccountInfo<'a>>, + tracked_mints: Option<&'b solana_program::account_info::AccountInfo<'a>>, ncn: Option<&'b solana_program::account_info::AccountInfo<'a>>, operator: Option<&'b solana_program::account_info::AccountInfo<'a>>, vault: Option<&'b solana_program::account_info::AccountInfo<'a>>, diff --git a/clients/rust/jito_tip_router/src/generated/types/fee.rs b/clients/rust/jito_tip_router/src/generated/types/fee_config.rs similarity index 77% rename from clients/rust/jito_tip_router/src/generated/types/fee.rs rename to clients/rust/jito_tip_router/src/generated/types/fee_config.rs index e3ed1ce..71a6ba7 100644 --- a/clients/rust/jito_tip_router/src/generated/types/fee.rs +++ b/clients/rust/jito_tip_router/src/generated/types/fee_config.rs @@ -7,16 +7,16 @@ use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::pubkey::Pubkey; +use crate::generated::types::Fees; + #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Fee { +pub struct FeeConfig { #[cfg_attr( feature = "serde", serde(with = "serde_with::As::") )] - pub wallet: Pubkey, - pub dao_share_bps: u64, - pub ncn_share_bps: u64, - pub block_engine_fee_bps: u64, - pub activation_epoch: u64, + pub dao_fee_wallet: Pubkey, + pub fee1: Fees, + pub fee2: Fees, } diff --git a/clients/rust/jito_tip_router/src/generated/types/fees.rs b/clients/rust/jito_tip_router/src/generated/types/fees.rs index ffd0b22..a7596a2 100644 --- a/clients/rust/jito_tip_router/src/generated/types/fees.rs +++ b/clients/rust/jito_tip_router/src/generated/types/fees.rs @@ -6,11 +6,16 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use crate::generated::types::Fee; +use crate::generated::types::NcnFee; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Fees { - pub fee1: Fee, - pub fee2: Fee, + pub activation_epoch: u64, + pub block_engine_fee_bps: u64, + pub dao_fee_bps: u64, + pub ncn_default_fee_bps: u64, + pub ncn_fee_groups_bps: [NcnFee; 8], + #[cfg_attr(feature = "serde", serde(with = "serde_with::As::"))] + pub reserved: [u8; 64], } diff --git a/clients/rust/jito_tip_router/src/generated/types/mint_entry.rs b/clients/rust/jito_tip_router/src/generated/types/mint_entry.rs index 7100f19..0ee75ea 100644 --- a/clients/rust/jito_tip_router/src/generated/types/mint_entry.rs +++ b/clients/rust/jito_tip_router/src/generated/types/mint_entry.rs @@ -7,6 +7,8 @@ use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::pubkey::Pubkey; +use crate::generated::types::NcnFeeGroup; + #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct MintEntry { @@ -16,5 +18,6 @@ pub struct MintEntry { )] pub st_mint: Pubkey, pub vault_index: u64, + pub ncn_fee_group: NcnFeeGroup, pub reserved: [u8; 32], } diff --git a/clients/rust/jito_tip_router/src/generated/types/mod.rs b/clients/rust/jito_tip_router/src/generated/types/mod.rs index 82ebe6f..9aa2a66 100644 --- a/clients/rust/jito_tip_router/src/generated/types/mod.rs +++ b/clients/rust/jito_tip_router/src/generated/types/mod.rs @@ -7,14 +7,19 @@ pub(crate) mod r#ballot; pub(crate) mod r#ballot_tally; pub(crate) mod r#config_admin_role; -pub(crate) mod r#fee; +pub(crate) mod r#fee_config; pub(crate) mod r#fees; pub(crate) mod r#mint_entry; +pub(crate) mod r#ncn_fee; +pub(crate) mod r#ncn_fee_group; +pub(crate) mod r#operator_reward; pub(crate) mod r#operator_vote; pub(crate) mod r#vault_operator_stake_weight; +pub(crate) mod r#vault_reward; pub(crate) mod r#weight_entry; pub use self::{ - r#ballot::*, r#ballot_tally::*, r#config_admin_role::*, r#fee::*, r#fees::*, r#mint_entry::*, - r#operator_vote::*, r#vault_operator_stake_weight::*, r#weight_entry::*, + r#ballot::*, r#ballot_tally::*, r#config_admin_role::*, r#fee_config::*, r#fees::*, + r#mint_entry::*, r#ncn_fee::*, r#ncn_fee_group::*, r#operator_reward::*, r#operator_vote::*, + r#vault_operator_stake_weight::*, r#vault_reward::*, r#weight_entry::*, }; diff --git a/clients/rust/jito_tip_router/src/generated/types/ncn_fee.rs b/clients/rust/jito_tip_router/src/generated/types/ncn_fee.rs new file mode 100644 index 0000000..8616be6 --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/types/ncn_fee.rs @@ -0,0 +1,13 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! + +use borsh::{BorshDeserialize, BorshSerialize}; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct NcnFee { + pub fee: u64, +} diff --git a/clients/rust/jito_tip_router/src/generated/types/ncn_fee_group.rs b/clients/rust/jito_tip_router/src/generated/types/ncn_fee_group.rs new file mode 100644 index 0000000..204ccff --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/types/ncn_fee_group.rs @@ -0,0 +1,13 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! + +use borsh::{BorshDeserialize, BorshSerialize}; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct NcnFeeGroup { + pub group: u8, +} diff --git a/clients/rust/jito_tip_router/src/generated/types/operator_reward.rs b/clients/rust/jito_tip_router/src/generated/types/operator_reward.rs new file mode 100644 index 0000000..9fa79fe --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/types/operator_reward.rs @@ -0,0 +1,21 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! + +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct OperatorReward { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub operator: Pubkey, + pub reward: u64, + #[cfg_attr(feature = "serde", serde(with = "serde_with::As::"))] + pub reserved: [u8; 128], +} diff --git a/clients/rust/jito_tip_router/src/generated/types/vault_operator_stake_weight.rs b/clients/rust/jito_tip_router/src/generated/types/vault_operator_stake_weight.rs index e45eb5b..a0ee21d 100644 --- a/clients/rust/jito_tip_router/src/generated/types/vault_operator_stake_weight.rs +++ b/clients/rust/jito_tip_router/src/generated/types/vault_operator_stake_weight.rs @@ -16,6 +16,7 @@ pub struct VaultOperatorStakeWeight { )] pub vault: Pubkey, pub stake_weight: u128, + pub reward_stake_weight: u128, pub vault_index: u64, pub reserved: [u8; 32], } diff --git a/clients/rust/jito_tip_router/src/generated/types/vault_reward.rs b/clients/rust/jito_tip_router/src/generated/types/vault_reward.rs new file mode 100644 index 0000000..991da81 --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/types/vault_reward.rs @@ -0,0 +1,21 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! + +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct VaultReward { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub vault: Pubkey, + pub reward: u64, + #[cfg_attr(feature = "serde", serde(with = "serde_with::As::"))] + pub reserved: [u8; 128], +} diff --git a/core/src/ballot_box.rs b/core/src/ballot_box.rs index e038189..deb717e 100644 --- a/core/src/ballot_box.rs +++ b/core/src/ballot_box.rs @@ -53,6 +53,7 @@ impl Ballot { pub struct BallotTally { ballot: Ballot, stake_weight: PodU128, + reward_stake_weight: PodU128, tally: PodU64, reserved: [u8; 64], } @@ -62,6 +63,7 @@ impl Default for BallotTally { Self { ballot: Ballot::default(), stake_weight: PodU128::from(0), + reward_stake_weight: PodU128::from(0), tally: PodU64::from(0), reserved: [0; 64], } @@ -69,10 +71,11 @@ impl Default for BallotTally { } impl BallotTally { - pub fn new(ballot: Ballot, stake_weight: u128) -> Self { + pub fn new(ballot: Ballot, stake_weight: u128, reward_stake_weight: u128) -> Self { Self { ballot, stake_weight: PodU128::from(stake_weight), + reward_stake_weight: PodU128::from(reward_stake_weight), tally: PodU64::from(1), reserved: [0; 64], } @@ -86,6 +89,10 @@ impl BallotTally { self.stake_weight.into() } + pub fn reward_stake_weight(&self) -> u128 { + self.reward_stake_weight.into() + } + pub fn tally(&self) -> u64 { self.tally.into() } @@ -94,12 +101,21 @@ impl BallotTally { self.stake_weight() == 0 } - pub fn increment_tally(&mut self, stake_weight: u128) -> Result<(), TipRouterError> { + pub fn increment_tally( + &mut self, + stake_weight: u128, + reward_stake_weight: u128, + ) -> Result<(), TipRouterError> { self.stake_weight = PodU128::from( self.stake_weight() .checked_add(stake_weight) .ok_or(TipRouterError::ArithmeticOverflow)?, ); + self.reward_stake_weight = PodU128::from( + self.stake_weight() + .checked_add(reward_stake_weight) + .ok_or(TipRouterError::ArithmeticOverflow)?, + ); self.tally = PodU64::from( self.tally() .checked_add(1) @@ -116,6 +132,7 @@ pub struct OperatorVote { operator: Pubkey, slot_voted: PodU64, stake_weight: PodU128, + reward_stake_weight: PodU128, ballot_index: PodU16, reserved: [u8; 64], } @@ -126,6 +143,7 @@ impl Default for OperatorVote { operator: Pubkey::default(), slot_voted: PodU64::from(0), stake_weight: PodU128::from(0), + reward_stake_weight: PodU128::from(0), ballot_index: PodU16::from(0), reserved: [0; 64], } @@ -138,12 +156,14 @@ impl OperatorVote { operator: Pubkey, current_slot: u64, stake_weight: u128, + reward_stake_weight: u128, ) -> Self { Self { operator, ballot_index: PodU16::from(ballot_index as u16), slot_voted: PodU64::from(current_slot), stake_weight: PodU128::from(stake_weight), + reward_stake_weight: PodU128::from(reward_stake_weight), reserved: [0; 64], } } @@ -160,6 +180,10 @@ impl OperatorVote { self.stake_weight.into() } + pub fn reward_stake_weight(&self) -> u128 { + self.reward_stake_weight.into() + } + pub fn ballot_index(&self) -> u16 { self.ballot_index.into() } @@ -292,6 +316,10 @@ impl BallotBox { &self.ballot_tallies } + pub fn operator_votes(&self) -> &[OperatorVote; 32] { + &self.operator_votes + } + pub fn get_winning_ballot(&self) -> Result { if self.winning_ballot.is_valid() { Ok(self.winning_ballot) @@ -300,20 +328,36 @@ impl BallotBox { } } + pub fn get_winning_ballot_tally(&self) -> Result { + if !self.winning_ballot.is_valid() { + return Err(TipRouterError::ConsensusNotReached); + } + + let winning_ballot = self.get_winning_ballot()?; + + self.ballot_tallies() + .iter() + .find(|tally| tally.ballot.eq(&winning_ballot)) + .map_or(Err(TipRouterError::TrackedMintsVaultIndexDne), |entry| { + Ok(*entry) + }) + } + fn increment_or_create_ballot_tally( &mut self, ballot: &Ballot, stake_weight: u128, + reward_stake_weight: u128, ) -> Result { let mut tally_index: usize = 0; for tally in self.ballot_tallies.iter_mut() { if tally.ballot.eq(ballot) { - tally.increment_tally(stake_weight)?; + tally.increment_tally(stake_weight, reward_stake_weight)?; return Ok(tally_index); } if tally.is_empty() { - *tally = BallotTally::new(*ballot, stake_weight); + *tally = BallotTally::new(*ballot, stake_weight, reward_stake_weight); self.unique_ballots = PodU64::from( self.unique_ballots() @@ -337,9 +381,11 @@ impl BallotBox { operator: Pubkey, ballot: Ballot, stake_weight: u128, + reward_stake_weight: u128, current_slot: u64, ) -> Result<(), TipRouterError> { - let ballot_index = self.increment_or_create_ballot_tally(&ballot, stake_weight)?; + let ballot_index = + self.increment_or_create_ballot_tally(&ballot, stake_weight, reward_stake_weight)?; for vote in self.operator_votes.iter_mut() { if vote.operator().eq(&operator) { @@ -347,8 +393,13 @@ impl BallotBox { } if vote.is_empty() { - let operator_vote = - OperatorVote::new(ballot_index, operator, current_slot, stake_weight); + let operator_vote = OperatorVote::new( + ballot_index, + operator, + current_slot, + stake_weight, + reward_stake_weight, + ); *vote = operator_vote; self.operators_voted = PodU64::from( diff --git a/core/src/epoch_snapshot.rs b/core/src/epoch_snapshot.rs index 6427801..acabc7c 100644 --- a/core/src/epoch_snapshot.rs +++ b/core/src/epoch_snapshot.rs @@ -9,7 +9,10 @@ use solana_program::{account_info::AccountInfo, msg, program_error::ProgramError use spl_math::precise_number::PreciseNumber; use crate::{ - discriminators::Discriminators, error::TipRouterError, fees::Fees, weight_table::WeightTable, + discriminators::Discriminators, + error::TipRouterError, + fees::{FeeConfig, NcnFeeGroup}, + weight_table::WeightTable, }; // PDA'd ["epoch_snapshot", NCN, NCN_EPOCH_SLOT] @@ -30,7 +33,7 @@ pub struct EpochSnapshot { slot_created: PodU64, slot_finalized: PodU64, - ncn_fees: Fees, + fees: FeeConfig, operator_count: PodU64, vault_count: PodU64, @@ -40,6 +43,7 @@ pub struct EpochSnapshot { /// Counted as each delegate gets added ///TODO What happens if `finalized() && total_votes() == 0`? stake_weight: PodU128, + reward_stake_weight: PodU128, /// Reserved space reserved: [u8; 128], @@ -55,7 +59,7 @@ impl EpochSnapshot { ncn_epoch: u64, bump: u8, current_slot: u64, - ncn_fees: Fees, + ncn_fees: FeeConfig, operator_count: u64, vault_count: u64, ) -> Self { @@ -65,12 +69,13 @@ impl EpochSnapshot { slot_created: PodU64::from(current_slot), slot_finalized: PodU64::from(0), bump, - ncn_fees, + fees: ncn_fees, operator_count: PodU64::from(operator_count), vault_count: PodU64::from(vault_count), operators_registered: PodU64::from(0), valid_operator_vault_delegations: PodU64::from(0), stake_weight: PodU128::from(0), + reward_stake_weight: PodU128::from(0), reserved: [0; 128], } } @@ -151,6 +156,14 @@ impl EpochSnapshot { self.stake_weight.into() } + pub fn reward_stake_weight(&self) -> u128 { + self.reward_stake_weight.into() + } + + pub const fn fees(&self) -> &FeeConfig { + &self.fees + } + pub fn finalized(&self) -> bool { self.operators_registered() == self.operator_count() } @@ -214,6 +227,7 @@ pub struct OperatorSnapshot { valid_operator_vault_delegations: PodU64, stake_weight: PodU128, + reward_stake_weight: PodU128, reserved: [u8; 256], //TODO change to 64 @@ -225,6 +239,7 @@ pub struct OperatorSnapshot { pub struct VaultOperatorStakeWeight { vault: Pubkey, stake_weight: PodU128, + reward_stake_weight: PodU128, vault_index: PodU64, reserved: [u8; 32], } @@ -235,17 +250,24 @@ impl Default for VaultOperatorStakeWeight { vault: Pubkey::default(), vault_index: PodU64::from(u64::MAX), stake_weight: PodU128::from(0), + reward_stake_weight: PodU128::from(0), reserved: [0; 32], } } } impl VaultOperatorStakeWeight { - pub fn new(vault: Pubkey, stake_weight: u128, vault_index: u64) -> Self { + pub fn new( + vault: Pubkey, + stake_weight: u128, + reward_stake_weight: u128, + vault_index: u64, + ) -> Self { Self { vault, vault_index: PodU64::from(vault_index), stake_weight: PodU128::from(stake_weight), + reward_stake_weight: PodU128::from(reward_stake_weight), reserved: [0; 32], } } @@ -302,6 +324,7 @@ impl OperatorSnapshot { vault_operator_delegations_registered: PodU64::from(0), valid_operator_vault_delegations: PodU64::from(0), stake_weight: PodU128::from(0), + reward_stake_weight: PodU128::from(0), reserved: [0; 256], vault_operator_stake_weight: [VaultOperatorStakeWeight::default(); 32], }) @@ -434,6 +457,10 @@ impl OperatorSnapshot { self.stake_weight.into() } + pub fn reward_stake_weight(&self) -> u128 { + self.reward_stake_weight.into() + } + pub fn finalized(&self) -> bool { self.vault_operator_delegations_registered() == self.vault_operator_delegation_count() } @@ -449,6 +476,7 @@ impl OperatorSnapshot { vault: Pubkey, vault_index: u64, stake_weight: u128, + reward_stake_weight: u128, ) -> Result<(), TipRouterError> { if self.vault_operator_delegations_registered() > Self::MAX_VAULT_OPERATOR_STAKE_WEIGHT as u64 @@ -461,7 +489,7 @@ impl OperatorSnapshot { } self.vault_operator_stake_weight[self.vault_operator_delegations_registered() as usize] = - VaultOperatorStakeWeight::new(vault, stake_weight, vault_index); + VaultOperatorStakeWeight::new(vault, stake_weight, reward_stake_weight, vault_index); Ok(()) } @@ -472,12 +500,18 @@ impl OperatorSnapshot { vault: Pubkey, vault_index: u64, stake_weight: u128, + reward_stake_weight: u128, ) -> Result<(), TipRouterError> { if self.finalized() { return Err(TipRouterError::VaultOperatorDelegationFinalized); } - self.insert_vault_operator_stake_weight(vault, vault_index, stake_weight)?; + self.insert_vault_operator_stake_weight( + vault, + vault_index, + stake_weight, + reward_stake_weight, + )?; self.vault_operator_delegations_registered = PodU64::from( self.vault_operator_delegations_registered() @@ -499,6 +533,12 @@ impl OperatorSnapshot { .ok_or(TipRouterError::ArithmeticOverflow)?, ); + self.reward_stake_weight = PodU128::from( + self.reward_stake_weight() + .checked_add(reward_stake_weight) + .ok_or(TipRouterError::ArithmeticOverflow)?, + ); + if self.finalized() { self.slot_finalized = PodU64::from(current_slot); } @@ -506,7 +546,7 @@ impl OperatorSnapshot { Ok(()) } - pub fn calculate_total_stake_weight( + pub fn calculate_stake_weight( vault_operator_delegation: &VaultOperatorDelegation, weight_table: &WeightTable, st_mint: &Pubkey, @@ -518,16 +558,39 @@ impl OperatorSnapshot { let precise_total_security = PreciseNumber::new(total_security as u128) .ok_or(TipRouterError::NewPreciseNumberError)?; - let precise_weight = weight_table.get_precise_weight(st_mint)?; + let precise_weight: PreciseNumber = weight_table.get_precise_weight(st_mint)?; - let precise_total_stake_weight = precise_total_security + let precise_stake_weight = precise_total_security .checked_mul(&precise_weight) .ok_or(TipRouterError::ArithmeticOverflow)?; - let total_stake_weight = precise_total_stake_weight + let stake_weight = precise_stake_weight + .to_imprecise() + .ok_or(TipRouterError::CastToImpreciseNumberError)?; + + Ok(stake_weight) + } + + pub fn calculate_reward_stake_weight( + stake_weight: u128, + ncn_fee_group: NcnFeeGroup, + fee_config: &FeeConfig, + current_epoch: u64, + ) -> Result { + let precise_stake_weight = + PreciseNumber::new(stake_weight).ok_or(TipRouterError::NewPreciseNumberError)?; + + let precise_ncn_fee = + fee_config.adjusted_precise_ncn_fee_bps(ncn_fee_group, current_epoch)?; + + let precise_reward_stake_weight = precise_stake_weight + .checked_mul(&precise_ncn_fee) + .ok_or(TipRouterError::ArithmeticOverflow)?; + + let reward_stake_weight: u128 = precise_reward_stake_weight .to_imprecise() .ok_or(TipRouterError::CastToImpreciseNumberError)?; - Ok(total_stake_weight) + Ok(reward_stake_weight) } } diff --git a/core/src/error.rs b/core/src/error.rs index 4c5f7da..f6c6141 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -7,12 +7,18 @@ pub enum TipRouterError { DenominatorIsZero = 0x2100, #[error("Overflow")] ArithmeticOverflow, + #[error("Arithmetic Underflow Error")] + ArithmeticUnderflowError, + #[error("Arithmetic Floor Error")] + ArithmeticFloorError, #[error("Modulo Overflow")] ModuloOverflow, #[error("New precise number error")] NewPreciseNumberError, #[error("Cast to imprecise number error")] CastToImpreciseNumberError, + #[error("Cast from u128 to u64 error")] + CastToU64Error, #[error("Incorrect weight table admin")] IncorrectWeightTableAdmin = 0x2200, @@ -74,6 +80,14 @@ pub enum TipRouterError { ConsensusAlreadyReached, #[error("Consensus not reached")] ConsensusNotReached, + #[error("Not a valid NCN share group")] + NotValidNcnShareGroup, + #[error("Tracked Mints does not contain vault index")] + TrackedMintsVaultIndexDne, + #[error("OperatorRewardList is full")] + OperatorRewardListFull, + #[error("Operator not found in OperatorRewardList")] + OperatorRewardNotFound, } impl DecodeError for TipRouterError { diff --git a/core/src/fees.rs b/core/src/fees.rs index 2ec6982..0d47b55 100644 --- a/core/src/fees.rs +++ b/core/src/fees.rs @@ -1,42 +1,44 @@ use bytemuck::{Pod, Zeroable}; use jito_bytemuck::types::PodU64; use shank::ShankType; -use solana_program::{msg, pubkey::Pubkey}; +use solana_program::pubkey::Pubkey; use spl_math::precise_number::PreciseNumber; use crate::{constants::MAX_FEE_BPS, error::TipRouterError}; -/// Fee account. Allows for fee updates to take place in a future epoch without requiring an update. +/// Fee Config. 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, +pub struct FeeConfig { + dao_fee_wallet: Pubkey, + + fee_1: Fees, + fee_2: Fees, } -impl Fees { +impl FeeConfig { pub fn new( - wallet: Pubkey, - dao_fee_share_bps: u64, - ncn_fee_share_bps: u64, + dao_fee_wallet: Pubkey, block_engine_fee_bps: u64, + dao_fee_bps: u64, + default_ncn_fee_bps: u64, current_epoch: u64, ) -> Self { - let fee = Fee::new( - wallet, - dao_fee_share_bps, - ncn_fee_share_bps, + let fee = Fees::new( block_engine_fee_bps, + dao_fee_bps, + default_ncn_fee_bps, current_epoch, ); Self { + dao_fee_wallet, fee_1: fee, fee_2: fee, } } - fn current_fee(&self, current_epoch: u64) -> &Fee { + fn current_fees(&self, current_epoch: u64) -> &Fees { // If either fee is not yet active, return the other one if self.fee_1.activation_epoch() > current_epoch { return &self.fee_2; @@ -54,107 +56,131 @@ impl Fees { } pub fn check_fees_okay(&self, current_epoch: u64) -> Result<(), TipRouterError> { - let _ = self.precise_block_engine_fee(current_epoch)?; - let _ = self.precise_dao_fee(current_epoch)?; - let _ = self.precise_ncn_fee(current_epoch)?; + let _ = self.precise_block_engine_fee_bps(current_epoch)?; + let _ = self.adjusted_precise_dao_fee_bps(current_epoch)?; + + let all_fee_groups = NcnFeeGroup::all_groups(); + + for group in all_fee_groups.iter() { + let _ = self.adjusted_precise_ncn_fee_bps(*group, current_epoch)?; + } Ok(()) } - pub fn block_engine_fee(&self, current_epoch: u64) -> u64 { - self.current_fee(current_epoch).block_engine_fee_bps() + pub fn block_engine_fee_bps(&self, current_epoch: u64) -> u64 { + let current_fees = self.current_fees(current_epoch); + current_fees.block_engine_fee_bps() } - pub fn precise_block_engine_fee( + pub fn precise_block_engine_fee_bps( &self, current_epoch: u64, ) -> Result { - let fee = self.current_fee(current_epoch); + let current_fees = self.current_fees(current_epoch); - PreciseNumber::new(fee.block_engine_fee_bps() as u128) + PreciseNumber::new(current_fees.block_engine_fee_bps() as u128) .ok_or(TipRouterError::NewPreciseNumberError) } + pub fn dao_fee_bps(&self, current_epoch: u64) -> u64 { + let fees = self.current_fees(current_epoch); + fees.dao_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::DenominatorIsZero) + pub fn adjusted_dao_fee_bps(&self, current_epoch: u64) -> Result { + let fees = self.current_fees(current_epoch); + + self.adjusted_fee_bps(fees.dao_fee_bps(), current_epoch) } - pub fn precise_dao_fee(&self, current_epoch: u64) -> Result { - let fee = self.current_fee(current_epoch); + pub fn adjusted_precise_dao_fee_bps( + &self, + current_epoch: u64, + ) -> Result { + let fees = self.current_fees(current_epoch); - let remaining_bps = MAX_FEE_BPS - .checked_sub(fee.block_engine_fee_bps()) - .ok_or(TipRouterError::ArithmeticOverflow)?; + self.adjusted_precise_fee_bps(fees.dao_fee_bps(), current_epoch) + } - let precise_remaining_bps = PreciseNumber::new(remaining_bps as u128) - .ok_or(TipRouterError::NewPreciseNumberError)?; + pub fn ncn_fee_bps( + &self, + ncn_fee_group: NcnFeeGroup, + current_epoch: u64, + ) -> Result { + let fees = self.current_fees(current_epoch); + fees.ncn_fee_bps(ncn_fee_group) + } - let dao_fee = fee - .ncn_share_bps() - .checked_mul(MAX_FEE_BPS) - .ok_or(TipRouterError::ArithmeticOverflow)?; + pub fn adjusted_ncn_fee_bps( + &self, + ncn_fee_group: NcnFeeGroup, + current_epoch: u64, + ) -> Result { + let fees = self.current_fees(current_epoch); - let precise_dao_fee = - PreciseNumber::new(dao_fee as u128).ok_or(TipRouterError::NewPreciseNumberError)?; + let fee = fees.ncn_fee_bps(ncn_fee_group)?; - precise_dao_fee - .checked_div(&precise_remaining_bps) - .ok_or(TipRouterError::DenominatorIsZero) + self.adjusted_fee_bps(fee, current_epoch) } - /// 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); + pub fn adjusted_precise_ncn_fee_bps( + &self, + ncn_fee_group: NcnFeeGroup, + current_epoch: u64, + ) -> Result { + let fees = self.current_fees(current_epoch); + + let fee = fees.ncn_fee_bps(ncn_fee_group)?; + + self.adjusted_precise_fee_bps(fee, current_epoch) + } + + pub const fn fee_wallet(&self) -> Pubkey { + self.dao_fee_wallet + } + + fn adjusted_fee_bps(&self, fee: u64, current_epoch: u64) -> Result { + let current_fees = self.current_fees(current_epoch); let remaining_bps = MAX_FEE_BPS - .checked_sub(fee.block_engine_fee_bps()) + .checked_sub(current_fees.block_engine_fee_bps()) .ok_or(TipRouterError::ArithmeticOverflow)?; - fee.ncn_share_bps() - .checked_mul(MAX_FEE_BPS) + fee.checked_mul(MAX_FEE_BPS) .and_then(|x| x.checked_div(remaining_bps)) .ok_or(TipRouterError::DenominatorIsZero) } - pub fn precise_ncn_fee(&self, current_epoch: u64) -> Result { - let fee = self.current_fee(current_epoch); + fn adjusted_precise_fee_bps( + &self, + fee: u64, + current_epoch: u64, + ) -> Result { + let fees = self.current_fees(current_epoch); let remaining_bps = MAX_FEE_BPS - .checked_sub(fee.block_engine_fee_bps()) + .checked_sub(fees.block_engine_fee_bps()) .ok_or(TipRouterError::ArithmeticOverflow)?; let precise_remaining_bps = PreciseNumber::new(remaining_bps as u128) .ok_or(TipRouterError::NewPreciseNumberError)?; - let ncn_fee = fee - .ncn_share_bps() + let adjusted_fee = fee .checked_mul(MAX_FEE_BPS) .ok_or(TipRouterError::ArithmeticOverflow)?; - let precise_ncn_fee = - PreciseNumber::new(ncn_fee as u128).ok_or(TipRouterError::NewPreciseNumberError)?; + let precise_adjusted_fee = PreciseNumber::new(adjusted_fee as u128) + .ok_or(TipRouterError::NewPreciseNumberError)?; - precise_ncn_fee + precise_adjusted_fee .checked_div(&precise_remaining_bps) .ok_or(TipRouterError::DenominatorIsZero) } - pub fn fee_wallet(&self, current_epoch: u64) -> Pubkey { - self.current_fee(current_epoch).wallet - } - - fn get_updatable_fee_mut(&mut self, current_epoch: u64) -> &mut Fee { + fn get_updatable_fee_mut(&mut self, current_epoch: u64) -> &mut Fees { // If either fee is scheduled for next epoch, return that one if self.fee_1.activation_epoch() > current_epoch { return &mut self.fee_1; @@ -171,51 +197,67 @@ impl Fees { } } - pub fn set_new_fees( + /// Updates the Fee Config + /// Any option set to None will be ignored + /// `new_wallet`` and `new_block_engine_fee_bps` will take effect immediately + /// `new_ncn_fee_bps` will set the fee group specified in `new_ncn_fee_group` + /// if no `new_ncn_fee_group` is specified, the default ncn group will be set + pub fn update_fee_config( &mut self, + new_wallet: Option, + new_block_engine_fee_bps: Option, new_dao_fee_bps: Option, new_ncn_fee_bps: Option, - new_block_engine_fee_bps: Option, - new_wallet: Option, + new_ncn_fee_group: 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; + // Set Wallet + if let Some(new_wallet) = new_wallet { + self.dao_fee_wallet = new_wallet; + } - 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.set_dao_share_bps(new_dao_fee_bps); + // Set new block engine fee + if let Some(new_block_engine_fee_bps) = new_block_engine_fee_bps { + self.fee_1 + .set_block_engine_fee_bps(new_block_engine_fee_bps); + self.fee_2 + .set_block_engine_fee_bps(new_block_engine_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); + + // Change Fees + { + let current_fees = *self.current_fees(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.set_dao_fee_bps(new_dao_fee_bps); } - new_fees.set_ncn_share_bps(new_ncn_fee_bps); - } - if let Some(new_block_engine_fee_bps) = new_block_engine_fee_bps { - // Block engine fee must be less than MAX_FEE_BPS, - // otherwise we'll divide by zero when calculating - // the other fees - if new_block_engine_fee_bps >= MAX_FEE_BPS { - msg!("Block engine fee cannot equal or exceed MAX_FEE_BPS"); - return Err(TipRouterError::FeeCapExceeded); + + // If no fee group is set, use the default + if let Some(new_ncn_fee_bps) = new_ncn_fee_bps { + if new_ncn_fee_bps > MAX_FEE_BPS { + return Err(TipRouterError::FeeCapExceeded); + } + + if let Some(new_ncn_fee_group) = new_ncn_fee_group { + new_fees.set_ncn_fee_bps(new_ncn_fee_group, new_ncn_fee_bps)?; + } else { + new_fees.set_ncn_fee_bps(NcnFeeGroup::default(), new_ncn_fee_bps)?; + } } - new_fees.set_block_engine_fee_bps(new_block_engine_fee_bps); - } - if let Some(new_wallet) = new_wallet { - new_fees.wallet = new_wallet; - } - let next_epoch = current_epoch - .checked_add(1) - .ok_or(TipRouterError::ArithmeticOverflow)?; + let next_epoch = current_epoch + .checked_add(1) + .ok_or(TipRouterError::ArithmeticOverflow)?; - new_fees.set_activation_epoch(next_epoch); + new_fees.set_activation_epoch(next_epoch); - self.check_fees_okay(next_epoch)?; + self.check_fees_okay(next_epoch)?; + } Ok(()) } @@ -223,186 +265,414 @@ impl Fees { #[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod)] #[repr(C)] -pub struct Fee { - wallet: Pubkey, - dao_share_bps: PodU64, - ncn_share_bps: PodU64, - block_engine_fee_bps: PodU64, +pub struct Fees { activation_epoch: PodU64, + + block_engine_fee_bps: PodU64, + dao_fee_bps: PodU64, + + ncn_fee_groups_bps: [NcnFee; 8], + + // Reserves + reserved: [u8; 64], } -impl Fee { +impl Fees { pub fn new( - wallet: Pubkey, - dao_share_bps: u64, - ncn_share_bps: u64, block_engine_fee_bps: u64, + dao_fee_bps: u64, + default_ncn_fee_bps: u64, epoch: u64, ) -> Self { Self { - wallet, - dao_share_bps: PodU64::from(dao_share_bps), - ncn_share_bps: PodU64::from(ncn_share_bps), - block_engine_fee_bps: PodU64::from(block_engine_fee_bps), activation_epoch: PodU64::from(epoch), + block_engine_fee_bps: PodU64::from(block_engine_fee_bps), + dao_fee_bps: PodU64::from(dao_fee_bps), + ncn_fee_groups_bps: [NcnFee::new(default_ncn_fee_bps); 8], + reserved: [0; 64], } } - pub fn dao_share_bps(&self) -> u64 { - self.dao_share_bps.into() - } - - pub fn ncn_share_bps(&self) -> u64 { - self.ncn_share_bps.into() + pub fn activation_epoch(&self) -> u64 { + self.activation_epoch.into() } pub fn block_engine_fee_bps(&self) -> u64 { self.block_engine_fee_bps.into() } - pub fn activation_epoch(&self) -> u64 { - self.activation_epoch.into() + pub fn dao_fee_bps(&self) -> u64 { + self.dao_fee_bps.into() } - fn set_dao_share_bps(&mut self, value: u64) { - self.dao_share_bps = PodU64::from(value); + pub fn ncn_fee_bps(&self, ncn_fee_group: NcnFeeGroup) -> Result { + let group = ncn_fee_group.group()?; + + match group { + NcnFeeGroupType::Default => Ok(self.ncn_fee_groups_bps[group as usize].fee()), + NcnFeeGroupType::JTO => Ok(self.ncn_fee_groups_bps[group as usize].fee()), + NcnFeeGroupType::Reserved2 => Ok(self.ncn_fee_groups_bps[group as usize].fee()), + NcnFeeGroupType::Reserved3 => Ok(self.ncn_fee_groups_bps[group as usize].fee()), + NcnFeeGroupType::Reserved4 => Ok(self.ncn_fee_groups_bps[group as usize].fee()), + NcnFeeGroupType::Reserved5 => Ok(self.ncn_fee_groups_bps[group as usize].fee()), + NcnFeeGroupType::Reserved6 => Ok(self.ncn_fee_groups_bps[group as usize].fee()), + NcnFeeGroupType::Reserved7 => Ok(self.ncn_fee_groups_bps[group as usize].fee()), + } } - fn set_ncn_share_bps(&mut self, value: u64) { - self.ncn_share_bps = PodU64::from(value); + fn set_activation_epoch(&mut self, value: u64) { + self.activation_epoch = PodU64::from(value); } fn set_block_engine_fee_bps(&mut self, value: u64) { self.block_engine_fee_bps = PodU64::from(value); } - fn set_activation_epoch(&mut self, value: u64) { - self.activation_epoch = PodU64::from(value); + fn set_dao_fee_bps(&mut self, value: u64) { + self.dao_fee_bps = PodU64::from(value); } -} -#[cfg(test)] -mod tests { - use solana_program::pubkey::Pubkey; - - use super::*; + pub fn set_ncn_fee_bps( + &mut self, + ncn_fee_group: NcnFeeGroup, + value: u64, + ) -> Result<(), TipRouterError> { + let group = ncn_fee_group.group()?; - #[test] - fn test_update_fees() { - let mut fees = Fees::new(Pubkey::new_unique(), 100, 200, 300, 5); - let new_wallet = Pubkey::new_unique(); + match group { + NcnFeeGroupType::Default => { + self.ncn_fee_groups_bps[group as usize] = NcnFee::new(value); + } + NcnFeeGroupType::JTO => { + self.ncn_fee_groups_bps[group as usize] = NcnFee::new(value); + } + NcnFeeGroupType::Reserved2 => { + self.ncn_fee_groups_bps[group as usize] = NcnFee::new(value); + } + NcnFeeGroupType::Reserved3 => { + self.ncn_fee_groups_bps[group as usize] = NcnFee::new(value); + } + NcnFeeGroupType::Reserved4 => { + self.ncn_fee_groups_bps[group as usize] = NcnFee::new(value); + } + NcnFeeGroupType::Reserved5 => { + self.ncn_fee_groups_bps[group as usize] = NcnFee::new(value); + } + NcnFeeGroupType::Reserved6 => { + self.ncn_fee_groups_bps[group as usize] = NcnFee::new(value); + } + NcnFeeGroupType::Reserved7 => { + self.ncn_fee_groups_bps[group as usize] = NcnFee::new(value); + } + } - fees.set_new_fees(Some(400), None, None, Some(new_wallet), 10) - .unwrap(); - assert_eq!(fees.fee_1.dao_share_bps(), 400); - assert_eq!(fees.fee_1.wallet, new_wallet); - assert_eq!(fees.fee_1.activation_epoch(), 11); + Ok(()) } +} - #[test] - fn test_update_all_fees() { - let mut fees = Fees::new(Pubkey::new_unique(), 0, 0, 0, 5); +#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod)] +#[repr(C)] +pub struct NcnFee { + fee: PodU64, +} - fees.set_new_fees(Some(100), Some(200), Some(300), None, 10) - .unwrap(); - assert_eq!(fees.fee_1.dao_share_bps(), 100); - assert_eq!(fees.fee_1.ncn_share_bps(), 200); - assert_eq!(fees.fee_1.block_engine_fee_bps(), 300); - assert_eq!(fees.fee_1.activation_epoch(), 11); +impl NcnFee { + pub fn new(fee: u64) -> Self { + Self { + fee: PodU64::from(fee), + } } - #[test] - fn test_update_fees_no_changes() { - let original = Fee::new(Pubkey::new_unique(), 100, 200, 300, 5); - let mut fees = Fees::new(Pubkey::new_unique(), 100, 200, 300, 5); - fees.fee_1 = original; - - fees.set_new_fees(None, None, None, None, 10).unwrap(); - assert_eq!(fees.fee_1.dao_share_bps(), original.dao_share_bps()); - assert_eq!(fees.fee_1.ncn_share_bps(), original.ncn_share_bps()); - assert_eq!( - fees.fee_1.block_engine_fee_bps(), - original.block_engine_fee_bps() - ); - assert_eq!(fees.fee_1.wallet, original.wallet); - assert_eq!(fees.fee_1.activation_epoch(), 11); + pub fn fee(&self) -> u64 { + self.fee.into() } +} - #[test] - fn test_update_fees_errors() { - let mut fees = Fees::new(Pubkey::new_unique(), 100, 200, 300, 5); +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum NcnFeeGroupType { + Default = 0, + JTO = 1, + Reserved2 = 2, + Reserved3 = 3, + Reserved4 = 4, + Reserved5 = 5, + Reserved6 = 6, + Reserved7 = 7, +} - assert_eq!( - fees.set_new_fees(Some(10001), None, None, None, 10), - Err(TipRouterError::FeeCapExceeded) - ); +#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod)] +#[repr(C)] +pub struct NcnFeeGroup { + pub group: u8, +} - let mut fees = Fees::new(Pubkey::new_unique(), 100, 200, 300, 5); +impl Default for NcnFeeGroup { + fn default() -> Self { + Self { + group: NcnFeeGroupType::Default as u8, + } + } +} - assert_eq!( - fees.set_new_fees(None, None, None, None, u64::MAX), - Err(TipRouterError::ArithmeticOverflow) - ); +impl NcnFeeGroup { + pub const fn new(group: NcnFeeGroupType) -> Self { + Self { group: group as u8 } + } - let mut fees = Fees::new(Pubkey::new_unique(), 100, 200, 300, 5); + pub const fn from_u8(group: u8) -> Result { + match group { + 0 => Ok(Self::new(NcnFeeGroupType::Default)), + 1 => Ok(Self::new(NcnFeeGroupType::JTO)), + 2 => Ok(Self::new(NcnFeeGroupType::Reserved2)), + 3 => Ok(Self::new(NcnFeeGroupType::Reserved3)), + 4 => Ok(Self::new(NcnFeeGroupType::Reserved4)), + 5 => Ok(Self::new(NcnFeeGroupType::Reserved5)), + 6 => Ok(Self::new(NcnFeeGroupType::Reserved6)), + 7 => Ok(Self::new(NcnFeeGroupType::Reserved7)), + _ => Err(TipRouterError::NotValidNcnShareGroup), + } + } - assert_eq!( - fees.set_new_fees(None, None, Some(MAX_FEE_BPS), None, 10), - Err(TipRouterError::FeeCapExceeded) - ); + pub const fn group(&self) -> Result { + match self.group { + 0 => Ok(NcnFeeGroupType::Default), + 1 => Ok(NcnFeeGroupType::JTO), + 2 => Ok(NcnFeeGroupType::Reserved2), + 3 => Ok(NcnFeeGroupType::Reserved3), + 4 => Ok(NcnFeeGroupType::Reserved4), + 5 => Ok(NcnFeeGroupType::Reserved5), + 6 => Ok(NcnFeeGroupType::Reserved6), + 7 => Ok(NcnFeeGroupType::Reserved7), + _ => Err(TipRouterError::NotValidNcnShareGroup), + } } - #[test] - fn test_check_fees_okay() { - let fees = Fees::new(Pubkey::new_unique(), 0, 0, 0, 5); + pub fn all_groups() -> Vec { + vec![ + Self::new(NcnFeeGroupType::Default), + Self::new(NcnFeeGroupType::JTO), + Self::new(NcnFeeGroupType::Reserved2), + Self::new(NcnFeeGroupType::Reserved3), + Self::new(NcnFeeGroupType::Reserved4), + Self::new(NcnFeeGroupType::Reserved5), + Self::new(NcnFeeGroupType::Reserved6), + Self::new(NcnFeeGroupType::Reserved7), + ] + } +} + +#[cfg(test)] +mod tests { + use solana_program::pubkey::Pubkey; - fees.check_fees_okay(5).unwrap(); + use super::*; + + #[test] + fn test_update_fees() { + const BLOCK_ENGINE_FEE: u64 = 100; + const DAO_FEE: u64 = 200; + const DEFAULT_NCN_FEE: u64 = 300; + const STARTING_EPOCH: u64 = 10; + + let dao_fee_wallet = Pubkey::new_unique(); + + let mut fee_config = FeeConfig::new( + dao_fee_wallet, + BLOCK_ENGINE_FEE, + DAO_FEE, + DEFAULT_NCN_FEE, + STARTING_EPOCH, + ); - let fees = Fees::new(Pubkey::new_unique(), 0, 0, MAX_FEE_BPS, 5); + assert_eq!(fee_config.fee_wallet(), dao_fee_wallet); + assert_eq!(fee_config.fee_1.activation_epoch(), STARTING_EPOCH); + assert_eq!(fee_config.fee_1.block_engine_fee_bps(), BLOCK_ENGINE_FEE); + assert_eq!(fee_config.fee_1.dao_fee_bps(), DAO_FEE); assert_eq!( - fees.check_fees_okay(5), - Err(TipRouterError::DenominatorIsZero) + fee_config + .fee_1 + .ncn_fee_bps(NcnFeeGroup::default()) + .unwrap(), + DEFAULT_NCN_FEE ); - } - #[test] - fn test_current_fee() { - let mut fees = Fees::new(Pubkey::new_unique(), 100, 200, 300, 5); + assert_eq!(fee_config.fee_2.activation_epoch(), STARTING_EPOCH); + assert_eq!(fee_config.fee_2.block_engine_fee_bps(), BLOCK_ENGINE_FEE); + assert_eq!(fee_config.fee_2.dao_fee_bps(), DAO_FEE); + assert_eq!( + fee_config + .fee_2 + .ncn_fee_bps(NcnFeeGroup::default()) + .unwrap(), + DEFAULT_NCN_FEE + ); - assert_eq!(fees.current_fee(5).activation_epoch(), 5); + let new_fees = Fees::new(500, 600, 700, 10); + let new_wallet = Pubkey::new_unique(); - fees.fee_1.set_activation_epoch(10); + fee_config + .update_fee_config( + Some(new_wallet), + Some(new_fees.block_engine_fee_bps()), + Some(new_fees.dao_fee_bps()), + Some(new_fees.ncn_fee_bps(NcnFeeGroup::default()).unwrap()), + None, + STARTING_EPOCH, + ) + .unwrap(); - assert_eq!(fees.current_fee(5).activation_epoch(), 5); - assert_eq!(fees.current_fee(10).activation_epoch(), 10); + assert_eq!(fee_config.fee_wallet(), new_wallet); - fees.fee_2.set_activation_epoch(15); + assert_eq!(fee_config.fee_1.activation_epoch(), STARTING_EPOCH + 1); + assert_eq!(fee_config.fee_1.block_engine_fee_bps(), 500); + assert_eq!(fee_config.fee_1.dao_fee_bps(), 600); + assert_eq!( + fee_config + .fee_1 + .ncn_fee_bps(NcnFeeGroup::default()) + .unwrap(), + 700 + ); - assert_eq!(fees.current_fee(12).activation_epoch(), 10); - assert_eq!(fees.current_fee(15).activation_epoch(), 15); + assert_eq!(fee_config.fee_2.activation_epoch(), STARTING_EPOCH); + assert_eq!(fee_config.fee_2.block_engine_fee_bps(), 500); // This will change regardless + assert_eq!(fee_config.fee_2.dao_fee_bps(), DAO_FEE); + assert_eq!( + fee_config + .fee_2 + .ncn_fee_bps(NcnFeeGroup::default()) + .unwrap(), + DEFAULT_NCN_FEE + ); } - #[test] - fn test_get_updatable_fee_mut() { - let mut fees = Fees::new(Pubkey::new_unique(), 100, 200, 300, 5); + // #[test] + // fn test_update_fees() { + // let mut fees = Fees::new(Pubkey::new_unique(), 100, 200, 300, 5); + // let new_wallet = Pubkey::new_unique(); + + // fees.set_new_fees(Some(400), None, None, Some(new_wallet), 10) + // .unwrap(); + // assert_eq!(fees.fee_1.dao_share_bps(), 400); + // assert_eq!(fees.wallet, new_wallet); + // assert_eq!(fees.fee_1.activation_epoch(), 11); + // } + + // #[test] + // fn test_update_all_fees() { + // let mut fees = Fees::new(Pubkey::new_unique(), 0, 0, 0, 5); + + // fees.set_new_fees(Some(100), Some(200), Some(300), None, 10) + // .unwrap(); + // assert_eq!(fees.fee_1.dao_share_bps(), 100); + // assert_eq!(fees.fee_1.ncn_share_bps(), 200); + // assert_eq!(fees.block_engine_fee_bps(), 300); + // assert_eq!(fees.fee_1.activation_epoch(), 11); + // } + + // #[test] + // fn test_update_fees_no_changes() { + // const DAO_SHARE_FEE_BPS: u64 = 100; + // const NCN_SHARE_FEE_BPS: u64 = 100; + // const BLOCK_ENGINE_FEE: u64 = 100; + // const STARTING_EPOCH: u64 = 10; + + // let wallet = Pubkey::new_unique(); + + // let mut fees = Fees::new( + // wallet, + // DAO_SHARE_FEE_BPS, + // NCN_SHARE_FEE_BPS, + // BLOCK_ENGINE_FEE, + // STARTING_EPOCH, + // ); + + // fees.set_new_fees(None, None, None, None, STARTING_EPOCH) + // .unwrap(); + // assert_eq!(fees.fee_1.dao_share_bps(), DAO_SHARE_FEE_BPS); + // assert_eq!(fees.fee_1.ncn_share_bps(), NCN_SHARE_FEE_BPS); + // assert_eq!(fees.block_engine_fee_bps(), BLOCK_ENGINE_FEE); + // assert_eq!(fees.wallet, wallet); + // assert_eq!(fees.fee_1.activation_epoch(), STARTING_EPOCH + 1); + // } + + // #[test] + // fn test_update_fees_errors() { + // let mut fees = Fees::new(Pubkey::new_unique(), 100, 200, 300, 5); + + // assert_eq!( + // fees.set_new_fees(Some(10001), None, None, None, 10), + // Err(TipRouterError::FeeCapExceeded) + // ); + + // let mut fees = Fees::new(Pubkey::new_unique(), 100, 200, 300, 5); + + // assert_eq!( + // fees.set_new_fees(None, None, None, None, u64::MAX), + // Err(TipRouterError::ArithmeticOverflow) + // ); + + // let mut fees = Fees::new(Pubkey::new_unique(), 100, 200, 300, 5); + + // assert_eq!( + // fees.set_new_fees(None, None, Some(MAX_FEE_BPS), None, 10), + // Err(TipRouterError::FeeCapExceeded) + // ); + // } + + // #[test] + // fn test_check_fees_okay() { + // let fees = Fees::new(Pubkey::new_unique(), 0, 0, 0, 5); + + // fees.check_fees_okay(5).unwrap(); + + // let fees = Fees::new(Pubkey::new_unique(), 0, 0, MAX_FEE_BPS, 5); + + // assert_eq!( + // fees.check_fees_okay(5), + // Err(TipRouterError::DenominatorIsZero) + // ); + // } + + // #[test] + // fn test_current_fee() { + // let mut fees = Fees::new(Pubkey::new_unique(), 100, 200, 300, 5); + + // assert_eq!(fees.current_fee(5).activation_epoch(), 5); + + // fees.fee_1.set_activation_epoch(10); + + // assert_eq!(fees.current_fee(5).activation_epoch(), 5); + // assert_eq!(fees.current_fee(10).activation_epoch(), 10); + + // fees.fee_2.set_activation_epoch(15); + + // assert_eq!(fees.current_fee(12).activation_epoch(), 10); + // assert_eq!(fees.current_fee(15).activation_epoch(), 15); + // } - let fee = fees.get_updatable_fee_mut(10); - fee.set_dao_share_bps(400); - fee.set_activation_epoch(11); + // #[test] + // fn test_get_updatable_fee_mut() { + // let mut fees = Fees::new(Pubkey::new_unique(), 100, 200, 300, 5); - assert_eq!(fees.fee_1.dao_share_bps(), 400); - assert_eq!(fees.fee_1.activation_epoch(), 11); + // let fee = fees.get_updatable_fee_mut(10); + // fee.set_dao_share_bps(400); + // fee.set_activation_epoch(11); - fees.fee_2.set_activation_epoch(13); + // assert_eq!(fees.fee_1.dao_share_bps(), 400); + // assert_eq!(fees.fee_1.activation_epoch(), 11); - let fee = fees.get_updatable_fee_mut(12); - fee.set_dao_share_bps(500); - fee.set_activation_epoch(13); + // fees.fee_2.set_activation_epoch(13); - assert_eq!(fees.fee_2.dao_share_bps(), 500); - assert_eq!(fees.fee_2.activation_epoch(), 13); + // let fee = fees.get_updatable_fee_mut(12); + // fee.set_dao_share_bps(500); + // fee.set_activation_epoch(13); - assert_eq!(fees.get_updatable_fee_mut(u64::MAX).activation_epoch(), 11); - } + // assert_eq!(fees.fee_2.dao_share_bps(), 500); + // assert_eq!(fees.fee_2.activation_epoch(), 13); + + // assert_eq!(fees.get_updatable_fee_mut(u64::MAX).activation_epoch(), 11); + // } } diff --git a/core/src/instruction.rs b/core/src/instruction.rs index bc42ba6..d264850 100644 --- a/core/src/instruction.rs +++ b/core/src/instruction.rs @@ -35,10 +35,11 @@ pub enum TipRouterInstruction { #[account(3, signer, name = "ncn_admin")] #[account(4, name = "restaking_program")] SetConfigFees { + new_fee_wallet: Option, + new_block_engine_fee_bps: Option, new_dao_fee_bps: Option, new_ncn_fee_bps: Option, - new_block_engine_fee_bps: Option, - new_fee_wallet: Option, + new_ncn_fee_group: Option, }, /// Sets a new secondary admin for the NCN @@ -103,20 +104,21 @@ pub enum TipRouterInstruction { first_slot_of_ncn_epoch: Option, }, - /// Initializes the Vault Operator Delegation Snapshot + /// Snapshots the Vault Operator Delegation Snapshot #[account(0, name = "ncn_config")] #[account(1, name = "restaking_config")] - #[account(2, name = "ncn")] - #[account(3, name = "operator")] - #[account(4, name = "vault")] - #[account(5, name = "vault_ncn_ticket")] - #[account(6, name = "ncn_vault_ticket")] - #[account(7, name = "vault_operator_delegation")] - #[account(8, name = "weight_table")] - #[account(9, writable, name = "epoch_snapshot")] - #[account(10, writable, name = "operator_snapshot")] - #[account(11, name = "vault_program")] - #[account(12, name = "restaking_program")] + #[account(2, name = "tracked_mints")] + #[account(3, name = "ncn")] + #[account(4, name = "operator")] + #[account(5, name = "vault")] + #[account(6, name = "vault_ncn_ticket")] + #[account(7, name = "ncn_vault_ticket")] + #[account(8, name = "vault_operator_delegation")] + #[account(9, name = "weight_table")] + #[account(10, writable, name = "epoch_snapshot")] + #[account(11, writable, name = "operator_snapshot")] + #[account(12, name = "vault_program")] + #[account(13, name = "restaking_program")] SnapshotVaultOperatorDelegation{ first_slot_of_ncn_epoch: Option, }, diff --git a/core/src/ncn_config.rs b/core/src/ncn_config.rs index 2ca4808..d26c299 100644 --- a/core/src/ncn_config.rs +++ b/core/src/ncn_config.rs @@ -3,7 +3,7 @@ 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, fees::Fees}; +use crate::{discriminators::Discriminators, fees::FeeConfig}; #[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod, AccountDeserialize, ShankAccount)] #[repr(C)] @@ -15,7 +15,7 @@ pub struct NcnConfig { pub fee_admin: Pubkey, - pub fees: Fees, + pub fee_config: FeeConfig, /// Bump seed for the PDA pub bump: u8, @@ -32,13 +32,13 @@ impl NcnConfig { ncn: Pubkey, tie_breaker_admin: Pubkey, fee_admin: Pubkey, - fees: Fees, + fees: FeeConfig, ) -> Self { Self { ncn, tie_breaker_admin, fee_admin, - fees, + fee_config: fees, bump: 0, reserved: [0; 127], } diff --git a/core/src/reward_router.rs b/core/src/reward_router.rs index efd5a53..b96ab00 100644 --- a/core/src/reward_router.rs +++ b/core/src/reward_router.rs @@ -1,22 +1,53 @@ use bytemuck::{Pod, Zeroable}; use jito_bytemuck::{types::PodU64, AccountDeserialize, Discriminator}; +use jito_vault_core::MAX_BPS; use shank::{ShankAccount, ShankType}; use solana_program::pubkey::Pubkey; +use spl_math::precise_number::PreciseNumber; use crate::{ - ballot_box::{self, BallotBox}, + ballot_box::BallotBox, discriminators::Discriminators, error::TipRouterError, + fees::{FeeConfig, Fees}, }; #[derive(Debug, Clone, PartialEq, Eq, Copy, Zeroable, ShankType, Pod, ShankType)] #[repr(C)] pub struct OperatorReward { operator: Pubkey, - reward: PodU64, + rewards: PodU64, reserved: [u8; 128], } +impl OperatorReward { + pub const fn operator(&self) -> Pubkey { + self.operator + } + + pub fn rewards(&self) -> u64 { + self.rewards.into() + } + + pub fn increment_rewards(&mut self, rewards: u64) -> Result<(), TipRouterError> { + self.rewards = PodU64::from( + self.rewards() + .checked_add(rewards) + .ok_or(TipRouterError::ArithmeticOverflow)?, + ); + Ok(()) + } + + pub fn decrement_rewards(&mut self, rewards: u64) -> Result<(), TipRouterError> { + self.rewards = PodU64::from( + self.rewards() + .checked_sub(rewards) + .ok_or(TipRouterError::ArithmeticUnderflowError)?, + ); + Ok(()) + } +} + // PDA'd ["epoch_reward_router", NCN, NCN_EPOCH_SLOT] #[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod, AccountDeserialize, ShankAccount)] #[repr(C)] @@ -33,6 +64,8 @@ pub struct EpochRewardRouter { reward_pool: PodU64, + dao_rewards: PodU64, + operator_rewards: [OperatorReward; 32], } @@ -41,14 +74,179 @@ impl Discriminator for EpochRewardRouter { } impl EpochRewardRouter { - pub fn process_new_rewards( + pub fn process_reward_pool( &mut self, + fee_config: &FeeConfig, ballot_box: &BallotBox, - new_rewards: u64, + current_epoch: u64, + ) -> Result<(), TipRouterError> { + let rewards_to_process: u64 = self.reward_pool(); + + let mut precise_rewards_to_process = PreciseNumber::new(rewards_to_process as u128) + .ok_or(TipRouterError::NewPreciseNumberError)?; + + // Dao Rewards + { + let adjusted_precise_dao_fee = + fee_config.adjusted_precise_dao_fee_bps(current_epoch)?; + + let precise_max_bps = + PreciseNumber::new(MAX_BPS as u128).ok_or(TipRouterError::NewPreciseNumberError)?; + + let precise_dao_rewards = precise_rewards_to_process + .checked_mul(&adjusted_precise_dao_fee) + .and_then(|x| x.checked_div(&precise_max_bps)) + .ok_or(TipRouterError::ArithmeticOverflow)?; + + let floored_precise_dao_rewards = precise_dao_rewards + .floor() + .ok_or(TipRouterError::ArithmeticFloorError)?; + + let dao_rewards_u128: u128 = floored_precise_dao_rewards + .to_imprecise() + .ok_or(TipRouterError::CastToImpreciseNumberError)?; + + let dao_rewards: u64 = dao_rewards_u128 + .try_into() + .map_err(|_| TipRouterError::CastToU64Error)?; + + self.increment_dao_rewards(dao_rewards)?; + + self.decrement_reward_pool(dao_rewards)?; + + precise_rewards_to_process = precise_rewards_to_process + .checked_sub(&floored_precise_dao_rewards) + .ok_or(TipRouterError::ArithmeticUnderflowError)?; + } + + // Operator Rewards + { + let total_reward_stake_weight = + ballot_box.get_winning_ballot_tally()?.reward_stake_weight(); + let precise_total_reward_stake_weight = PreciseNumber::new(total_reward_stake_weight) + .ok_or(TipRouterError::NewPreciseNumberError)?; + + for ballot in ballot_box.operator_votes().iter() { + let operator_reward_stake_weight = ballot.reward_stake_weight(); + let precise_operator_reward_stake_weight = + PreciseNumber::new(operator_reward_stake_weight) + .ok_or(TipRouterError::NewPreciseNumberError)?; + + let precise_reward_split = precise_operator_reward_stake_weight + .checked_div(&precise_total_reward_stake_weight) + .ok_or(TipRouterError::DenominatorIsZero)?; + + let precise_rewards = precise_rewards_to_process + .checked_div(&precise_reward_split) + .ok_or(TipRouterError::DenominatorIsZero)?; + + let floored_precise_rewards = precise_rewards + .floor() + .ok_or(TipRouterError::ArithmeticFloorError)?; + + let operator_rewards_u128: u128 = floored_precise_rewards + .to_imprecise() + .ok_or(TipRouterError::CastToImpreciseNumberError)?; + + let operator_rewards: u64 = operator_rewards_u128 + .try_into() + .map_err(|_| TipRouterError::CastToU64Error)?; + + self.insert_or_increment_operator_rewards(ballot.operator(), operator_rewards)?; + self.decrement_reward_pool(operator_rewards)?; + } + } + + // Any Leftovers go to DAO + { + let leftover_rewards = self.reward_pool(); + + self.increment_dao_rewards(leftover_rewards)?; + self.decrement_reward_pool(leftover_rewards)?; + } + + Ok(()) + } + + pub fn insert_or_increment_operator_rewards( + &mut self, + operator: Pubkey, + rewards: u64, + ) -> Result<(), TipRouterError> { + for operator_reward in self.operator_rewards.iter_mut() { + if operator_reward.operator == operator { + operator_reward.increment_rewards(rewards)?; + return Ok(()); + } + } + + for operator_reward in self.operator_rewards.iter_mut() { + if operator_reward.operator == Pubkey::default() { + operator_reward.operator = operator; + operator_reward.rewards = PodU64::from(rewards); + return Ok(()); + } + } + + Err(TipRouterError::OperatorRewardListFull.into()) + } + + pub fn decrement_operator_rewards( + &mut self, + operator: Pubkey, + rewards: u64, ) -> Result<(), TipRouterError> { - let winning_ballot = ballot_box.get_winning_ballot()?; - for tally in ballot_box.ballot_tallies() {} + for operator_reward in self.operator_rewards.iter_mut() { + if operator_reward.operator == operator { + operator_reward.decrement_rewards(rewards)?; + return Ok(()); + } + } + + Err(TipRouterError::OperatorRewardNotFound.into()) + } + + pub fn reward_pool(&self) -> u64 { + self.reward_pool.into() + } + + pub fn increment_reward_pool(&mut self, rewards: u64) -> Result<(), TipRouterError> { + self.reward_pool = PodU64::from( + self.reward_pool() + .checked_add(rewards) + .ok_or(TipRouterError::ArithmeticOverflow)?, + ); + Ok(()) + } + + pub fn decrement_reward_pool(&mut self, rewards: u64) -> Result<(), TipRouterError> { + self.reward_pool = PodU64::from( + self.reward_pool() + .checked_sub(rewards) + .ok_or(TipRouterError::ArithmeticUnderflowError)?, + ); + Ok(()) + } + + pub fn dao_rewards(&self) -> u64 { + self.dao_rewards.into() + } + + pub fn increment_dao_rewards(&mut self, rewards: u64) -> Result<(), TipRouterError> { + self.dao_rewards = PodU64::from( + self.dao_rewards() + .checked_add(rewards) + .ok_or(TipRouterError::ArithmeticOverflow)?, + ); + Ok(()) + } + pub fn decrement_dao_rewards(&mut self, rewards: u64) -> Result<(), TipRouterError> { + self.dao_rewards = PodU64::from( + self.dao_rewards() + .checked_sub(rewards) + .ok_or(TipRouterError::ArithmeticUnderflowError)?, + ); Ok(()) } } diff --git a/core/src/tracked_mints.rs b/core/src/tracked_mints.rs index fc6bcbc..62320c0 100644 --- a/core/src/tracked_mints.rs +++ b/core/src/tracked_mints.rs @@ -5,13 +5,14 @@ use jito_bytemuck::{types::PodU64, AccountDeserialize, Discriminator}; use shank::{ShankAccount, ShankType}; use solana_program::{account_info::AccountInfo, msg, program_error::ProgramError, pubkey::Pubkey}; -use crate::{discriminators::Discriminators, error::TipRouterError}; +use crate::{discriminators::Discriminators, error::TipRouterError, fees::NcnFeeGroup}; #[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod)] #[repr(C)] pub struct MintEntry { st_mint: Pubkey, vault_index: PodU64, + ncn_fee_group: NcnFeeGroup, reserved: [u8; 32], } @@ -20,6 +21,7 @@ impl MintEntry { Self { st_mint: mint, vault_index: PodU64::from(vault_index), + ncn_fee_group: NcnFeeGroup::default(), reserved: [0; 32], } } @@ -27,6 +29,10 @@ impl MintEntry { pub fn vault_index(&self) -> u64 { self.vault_index.into() } + + pub const fn ncn_fee_group(&self) -> NcnFeeGroup { + self.ncn_fee_group + } } impl Default for MintEntry { @@ -121,6 +127,15 @@ impl TrackedMints { unique_mints.into_iter().collect() } + pub fn get_mint_entry(&self, vault_index: u64) -> Result { + self.st_mint_list + .iter() + .find(|entry| entry.vault_index() == vault_index) + .map_or(Err(TipRouterError::TrackedMintsVaultIndexDne), |entry| { + Ok(*entry) + }) + } + pub fn load( program_id: &Pubkey, ncn: &Pubkey, diff --git a/idl/jito_tip_router.json b/idl/jito_tip_router.json index dbac2f4..b0ea76e 100644 --- a/idl/jito_tip_router.json +++ b/idl/jito_tip_router.json @@ -96,27 +96,33 @@ ], "args": [ { - "name": "newDaoFeeBps", + "name": "newFeeWallet", + "type": { + "option": "publicKey" + } + }, + { + "name": "newBlockEngineFeeBps", "type": { "option": "u64" } }, { - "name": "newNcnFeeBps", + "name": "newDaoFeeBps", "type": { "option": "u64" } }, { - "name": "newBlockEngineFeeBps", + "name": "newNcnFeeBps", "type": { "option": "u64" } }, { - "name": "newFeeWallet", + "name": "newNcnFeeGroup", "type": { - "option": "publicKey" + "option": "u8" } } ], @@ -405,6 +411,11 @@ "isMut": false, "isSigner": false }, + { + "name": "trackedMints", + "isMut": false, + "isSigner": false + }, { "name": "ncn", "isMut": false, @@ -681,9 +692,9 @@ } }, { - "name": "ncnFees", + "name": "fees", "type": { - "defined": "Fees" + "defined": "FeeConfig" } }, { @@ -716,6 +727,12 @@ "defined": "PodU128" } }, + { + "name": "rewardStakeWeight", + "type": { + "defined": "PodU128" + } + }, { "name": "reserved", "type": { @@ -811,6 +828,12 @@ "defined": "PodU128" } }, + { + "name": "rewardStakeWeight", + "type": { + "defined": "PodU128" + } + }, { "name": "reserved", "type": { @@ -852,9 +875,9 @@ "type": "publicKey" }, { - "name": "fees", + "name": "feeConfig", "type": { - "defined": "Fees" + "defined": "FeeConfig" } }, { @@ -873,6 +896,114 @@ ] } }, + { + "name": "EpochRewardRouter", + "type": { + "kind": "struct", + "fields": [ + { + "name": "ncn", + "type": "publicKey" + }, + { + "name": "ncnEpoch", + "type": { + "defined": "PodU64" + } + }, + { + "name": "bump", + "type": "u8" + }, + { + "name": "slotCreated", + "type": { + "defined": "PodU64" + } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 128 + ] + } + }, + { + "name": "rewardPool", + "type": { + "defined": "PodU64" + } + }, + { + "name": "operatorRewards", + "type": { + "array": [ + { + "defined": "OperatorReward" + }, + 32 + ] + } + } + ] + } + }, + { + "name": "OperatorRewardRouter", + "type": { + "kind": "struct", + "fields": [ + { + "name": "ncn", + "type": "publicKey" + }, + { + "name": "ncnEpoch", + "type": { + "defined": "PodU64" + } + }, + { + "name": "bump", + "type": "u8" + }, + { + "name": "slotCreated", + "type": { + "defined": "PodU64" + } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 128 + ] + } + }, + { + "name": "rewardPool", + "type": { + "defined": "PodU64" + } + }, + { + "name": "vaultRewards", + "type": { + "array": [ + { + "defined": "VaultReward" + }, + 32 + ] + } + } + ] + } + }, { "name": "TrackedMints", "type": { @@ -1074,6 +1205,12 @@ "defined": "PodU128" } }, + { + "name": "rewardStakeWeight", + "type": { + "defined": "PodU128" + } + }, { "name": "vaultIndex", "type": { @@ -1093,54 +1230,100 @@ } }, { - "name": "Fees", + "name": "FeeConfig", "type": { "kind": "struct", "fields": [ + { + "name": "daoFeeWallet", + "type": "publicKey" + }, { "name": "fee1", "type": { - "defined": "Fee" + "defined": "Fees" } }, { "name": "fee2", "type": { - "defined": "Fee" + "defined": "Fees" } } ] } }, { - "name": "Fee", + "name": "NcnFeeGroup", "type": { "kind": "struct", "fields": [ { - "name": "wallet", - "type": "publicKey" + "name": "group", + "type": "u8" + } + ] + } + }, + { + "name": "Fees", + "type": { + "kind": "struct", + "fields": [ + { + "name": "activationEpoch", + "type": { + "defined": "PodU64" + } }, { - "name": "daoShareBps", + "name": "blockEngineFeeBps", "type": { "defined": "PodU64" } }, { - "name": "ncnShareBps", + "name": "daoFeeBps", "type": { "defined": "PodU64" } }, { - "name": "blockEngineFeeBps", + "name": "ncnDefaultFeeBps", "type": { "defined": "PodU64" } }, { - "name": "activationEpoch", + "name": "ncnFeeGroupsBps", + "type": { + "array": [ + { + "defined": "NcnFee" + }, + 8 + ] + } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 64 + ] + } + } + ] + } + }, + { + "name": "NcnFee", + "type": { + "kind": "struct", + "fields": [ + { + "name": "fee", "type": { "defined": "PodU64" } @@ -1148,6 +1331,60 @@ ] } }, + { + "name": "OperatorReward", + "type": { + "kind": "struct", + "fields": [ + { + "name": "operator", + "type": "publicKey" + }, + { + "name": "reward", + "type": { + "defined": "PodU64" + } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 128 + ] + } + } + ] + } + }, + { + "name": "VaultReward", + "type": { + "kind": "struct", + "fields": [ + { + "name": "vault", + "type": "publicKey" + }, + { + "name": "reward", + "type": { + "defined": "PodU64" + } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 128 + ] + } + } + ] + } + }, { "name": "MintEntry", "type": { @@ -1163,6 +1400,12 @@ "defined": "PodU64" } }, + { + "name": "ncnFeeGroup", + "type": { + "defined": "NcnFeeGroup" + } + }, { "name": "reserved", "type": { @@ -1404,6 +1647,16 @@ "code": 8733, "name": "ConsensusNotReached", "msg": "Consensus not reached" + }, + { + "code": 8734, + "name": "NotValidNcnShareGroup", + "msg": "Not a valid NCN share group" + }, + { + "code": 8735, + "name": "TrackedMintsVaultIndexDne", + "msg": "Tracked Mints does not contain vault index" } ], "metadata": { diff --git a/integration_tests/tests/fixtures/tip_router_client.rs b/integration_tests/tests/fixtures/tip_router_client.rs index c4dea87..d032a24 100644 --- a/integration_tests/tests/fixtures/tip_router_client.rs +++ b/integration_tests/tests/fixtures/tip_router_client.rs @@ -650,9 +650,13 @@ impl TipRouterClient { let weight_table = WeightTable::find_program_address(&jito_tip_router_program::id(), &ncn, ncn_epoch).0; + let tracked_mints = + TrackedMints::find_program_address(&jito_tip_router_program::id(), &ncn).0; + let ix = SnapshotVaultOperatorDelegationBuilder::new() .ncn_config(config_pda) .restaking_config(restaking_config) + .tracked_mints(tracked_mints) .ncn(ncn) .operator(operator) .vault(vault) diff --git a/integration_tests/tests/tip_router/set_config_fees.rs b/integration_tests/tests/tip_router/set_config_fees.rs index b99312d..5a3fd69 100644 --- a/integration_tests/tests/tip_router/set_config_fees.rs +++ b/integration_tests/tests/tip_router/set_config_fees.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod tests { - use jito_tip_router_core::error::TipRouterError; + use jito_tip_router_core::{error::TipRouterError, fees::NcnFeeGroup}; use solana_sdk::{ clock::DEFAULT_SLOTS_PER_EPOCH, signature::{Keypair, Signer}, @@ -102,13 +102,19 @@ mod tests { .get_ncn_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); - assert_eq!(config.fees.block_engine_fee(clock.epoch as u64), 0); + assert_eq!(config.fee_config.dao_fee_bps(clock.epoch as u64), 100); assert_eq!( - config.fees.fee_wallet(clock.epoch as u64), - new_fee_wallet.pubkey() + config + .fee_config + .ncn_fee_bps(NcnFeeGroup::default(), clock.epoch as u64) + .unwrap(), + 200 ); + assert_eq!( + config.fee_config.block_engine_fee_bps(clock.epoch as u64), + 0 + ); + assert_eq!(config.fee_config.fee_wallet(), new_fee_wallet.pubkey()); Ok(()) } diff --git a/program/src/initialize_epoch_snapshot.rs b/program/src/initialize_epoch_snapshot.rs index 1814bb4..c7fd6a6 100644 --- a/program/src/initialize_epoch_snapshot.rs +++ b/program/src/initialize_epoch_snapshot.rs @@ -83,10 +83,10 @@ pub fn process_initialize_epoch_snapshot( &epoch_snapshot_seeds, )?; - let ncn_fees: fees::Fees = { + let ncn_fees: fees::FeeConfig = { let ncn_config_data = ncn_config.data.borrow(); let ncn_config_account = NcnConfig::try_from_slice_unchecked(&ncn_config_data)?; - ncn_config_account.fees + ncn_config_account.fee_config }; let operator_count: u64 = { diff --git a/program/src/initialize_ncn_config.rs b/program/src/initialize_ncn_config.rs index 83b450e..eb12a4a 100644 --- a/program/src/initialize_ncn_config.rs +++ b/program/src/initialize_ncn_config.rs @@ -5,7 +5,7 @@ use jito_jsm_core::{ }; use jito_restaking_core::{config::Config, ncn::Ncn}; use jito_tip_router_core::{ - constants::MAX_FEE_BPS, error::TipRouterError, fees::Fees, ncn_config::NcnConfig, + constants::MAX_FEE_BPS, error::TipRouterError, fees::FeeConfig, ncn_config::NcnConfig, }; use solana_program::{ account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, @@ -88,7 +88,7 @@ pub fn process_initialize_ncn_config( *ncn_account.key, *tie_breaker_admin.key, *ncn_admin.key, - Fees::new( + FeeConfig::new( *fee_wallet.key, dao_fee_bps, ncn_fee_bps, @@ -98,7 +98,7 @@ pub fn process_initialize_ncn_config( ); config.bump = config_bump; - config.fees.check_fees_okay(epoch)?; + config.fee_config.check_fees_okay(epoch)?; Ok(()) } diff --git a/program/src/lib.rs b/program/src/lib.rs index df0b144..5e21542 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -98,7 +98,7 @@ pub fn process_instruction( TipRouterInstruction::SnapshotVaultOperatorDelegation { first_slot_of_ncn_epoch, } => { - msg!("Instruction: InitializeVaultOperatorDelegationSnapshot"); + msg!("Instruction: SnapshotVaultOperatorDelegation"); process_snapshot_vault_operator_delegation( program_id, accounts, @@ -113,19 +113,21 @@ pub fn process_instruction( process_admin_update_weight_table(program_id, accounts, ncn_epoch, weight) } TipRouterInstruction::SetConfigFees { + new_fee_wallet, + new_block_engine_fee_bps, new_dao_fee_bps, new_ncn_fee_bps, - new_block_engine_fee_bps, - new_fee_wallet, + new_ncn_fee_group, } => { msg!("Instruction: SetConfigFees"); process_set_config_fees( program_id, accounts, + new_fee_wallet, + new_block_engine_fee_bps, new_dao_fee_bps, new_ncn_fee_bps, - new_block_engine_fee_bps, - new_fee_wallet, + new_ncn_fee_group, ) } TipRouterInstruction::SetNewAdmin { role } => { diff --git a/program/src/set_config_fees.rs b/program/src/set_config_fees.rs index 7df9a95..fd5de10 100644 --- a/program/src/set_config_fees.rs +++ b/program/src/set_config_fees.rs @@ -1,7 +1,7 @@ use jito_bytemuck::{AccountDeserialize, Discriminator}; use jito_jsm_core::loader::load_signer; use jito_restaking_core::{config::Config, ncn::Ncn}; -use jito_tip_router_core::{error::TipRouterError, ncn_config::NcnConfig}; +use jito_tip_router_core::{error::TipRouterError, fees::NcnFeeGroup, ncn_config::NcnConfig}; use solana_program::{ account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, @@ -10,10 +10,11 @@ use solana_program::{ pub fn process_set_config_fees( program_id: &Pubkey, accounts: &[AccountInfo], + new_fee_wallet: Option, + new_block_engine_fee_bps: Option, new_dao_fee_bps: Option, new_ncn_fee_bps: Option, - new_block_engine_fee_bps: Option, - new_fee_wallet: Option, + new_ncn_fee_group: Option, ) -> ProgramResult { let [restaking_config, config, ncn_account, fee_admin, restaking_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); @@ -53,11 +54,18 @@ pub fn process_set_config_fees( return Err(TipRouterError::IncorrectFeeAdmin.into()); } - config.fees.set_new_fees( + let new_ncn_fee_group = if let Some(new_ncn_fee_group) = new_ncn_fee_group { + Some(NcnFeeGroup::from_u8(new_ncn_fee_group)?) + } else { + None + }; + + config.fee_config.update_fee_config( + new_fee_wallet, new_dao_fee_bps, new_ncn_fee_bps, new_block_engine_fee_bps, - new_fee_wallet, + new_ncn_fee_group, epoch, )?; diff --git a/program/src/snapshot_vault_operator_delegation.rs b/program/src/snapshot_vault_operator_delegation.rs index fdb2dba..d78f74d 100644 --- a/program/src/snapshot_vault_operator_delegation.rs +++ b/program/src/snapshot_vault_operator_delegation.rs @@ -6,6 +6,7 @@ use jito_tip_router_core::{ epoch_snapshot::{EpochSnapshot, OperatorSnapshot}, loaders::load_ncn_epoch, ncn_config::NcnConfig, + tracked_mints::TrackedMints, weight_table::WeightTable, }; use jito_vault_core::{ @@ -22,7 +23,7 @@ pub fn process_snapshot_vault_operator_delegation( accounts: &[AccountInfo], first_slot_of_ncn_epoch: Option, ) -> ProgramResult { - let [ncn_config, restaking_config, ncn, operator, vault, vault_ncn_ticket, ncn_vault_ticket, vault_operator_delegation, weight_table, epoch_snapshot, operator_snapshot, vault_program, restaking_program] = + let [ncn_config, restaking_config, tracked_mints, ncn, operator, vault, vault_ncn_ticket, ncn_vault_ticket, vault_operator_delegation, weight_table, epoch_snapshot, operator_snapshot, vault_program, restaking_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); @@ -108,7 +109,7 @@ pub fn process_snapshot_vault_operator_delegation( vault_ncn_okay && ncn_vault_okay && !delegation_dne }; - let total_votes: u128 = if is_active { + let stake_weight: u128 = if is_active { let vault_operator_delegation_data = vault_operator_delegation.data.borrow(); let vault_operator_delegation_account = VaultOperatorDelegation::try_from_slice_unchecked(&vault_operator_delegation_data)?; @@ -116,7 +117,7 @@ pub fn process_snapshot_vault_operator_delegation( let weight_table_data = weight_table.data.borrow(); let weight_table_account = WeightTable::try_from_slice_unchecked(&weight_table_data)?; - OperatorSnapshot::calculate_total_stake_weight( + OperatorSnapshot::calculate_stake_weight( vault_operator_delegation_account, weight_table_account, &st_mint, @@ -125,6 +126,23 @@ pub fn process_snapshot_vault_operator_delegation( 0u128 }; + let reward_stake_weight = { + let tracked_mints_data = tracked_mints.try_borrow_data()?; + let tracked_mints_account = TrackedMints::try_from_slice_unchecked(&tracked_mints_data)?; + + let tracked_mint = tracked_mints_account.get_mint_entry(vault_index)?; + + let epoch_snapshot_data = epoch_snapshot.try_borrow_data()?; + let epoch_snapshot_account = EpochSnapshot::try_from_slice_unchecked(&epoch_snapshot_data)?; + + OperatorSnapshot::calculate_reward_stake_weight( + stake_weight, + tracked_mint.ncn_fee_group(), + epoch_snapshot_account.fees(), + ncn_epoch, + )? + }; + // Increment vault operator delegation let mut operator_snapshot_data = operator_snapshot.try_borrow_mut_data()?; let operator_snapshot_account = @@ -134,7 +152,8 @@ pub fn process_snapshot_vault_operator_delegation( current_slot, *vault.key, vault_index, - total_votes, + stake_weight, + reward_stake_weight, )?; // If operator is finalized, increment operator registration