diff --git a/.cargo/config.toml b/.cargo/config.toml index d5a344c..888a140 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,5 +2,5 @@ # Program IDs may not be correct [env] TIP_ROUTER_PROGRAM_ID = "Fv9aHCgvPQSr4jg9W8eTS6Ys1SNmh2qjyATrbsjEMaSH" -RESTAKING_PROGRAM_ID = "6Weyp6uFyjJ3pzYz7XbXvCPAyvzLUhESEhHarFRm53Nb" -VAULT_PROGRAM_ID = "4vB3bvqKEmxV68J7XtS1HqZomjok4ZXc1ELJH9p1K1D2" \ No newline at end of file +RESTAKING_PROGRAM_ID = "RestkWeAVL8fRGgzhfeoqFhsqKRchg6aa1XrcH96z4Q" +VAULT_PROGRAM_ID = "Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8" \ No newline at end of file diff --git a/.cargo/programs.env b/.cargo/programs.env index 1176efb..468a094 100644 --- a/.cargo/programs.env +++ b/.cargo/programs.env @@ -1,3 +1,3 @@ TIP_ROUTER_PROGRAM_ID=Fv9aHCgvPQSr4jg9W8eTS6Ys1SNmh2qjyATrbsjEMaSH -RESTAKING_PROGRAM_ID=6Weyp6uFyjJ3pzYz7XbXvCPAyvzLUhESEhHarFRm53Nb -VAULT_PROGRAM_ID=4vB3bvqKEmxV68J7XtS1HqZomjok4ZXc1ELJH9p1K1D2 \ No newline at end of file +RESTAKING_PROGRAM_ID=RestkWeAVL8fRGgzhfeoqFhsqKRchg6aa1XrcH96z4Q +VAULT_PROGRAM_ID=Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8 \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index b1ddcd1..41b7a03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2519,6 +2519,8 @@ dependencies = [ "jito-restaking-program", "jito-restaking-sdk", "jito-tip-router-client", + "jito-tip-router-core", + "jito-tip-router-program", "jito-vault-core", "jito-vault-program", "jito-vault-sdk", diff --git a/clients/js/jito_tip_router/accounts/index.ts b/clients/js/jito_tip_router/accounts/index.ts index e04ad55..033882c 100644 --- a/clients/js/jito_tip_router/accounts/index.ts +++ b/clients/js/jito_tip_router/accounts/index.ts @@ -6,4 +6,5 @@ * @see https://github.com/kinobi-so/kinobi */ +export * from './ncnConfig'; export * from './weightTable'; diff --git a/clients/js/jito_tip_router/accounts/ncnConfig.ts b/clients/js/jito_tip_router/accounts/ncnConfig.ts new file mode 100644 index 0000000..2d385eb --- /dev/null +++ b/clients/js/jito_tip_router/accounts/ncnConfig.ts @@ -0,0 +1,147 @@ +/** + * 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 { + getFeesDecoder, + getFeesEncoder, + type Fees, + type FeesArgs, +} from '../types'; + +export type NcnConfig = { + discriminator: bigint; + ncn: Address; + tieBreakerAdmin: Address; + feeAdmin: Address; + fees: Fees; + bump: number; + reserved: Array; +}; + +export type NcnConfigArgs = { + discriminator: number | bigint; + ncn: Address; + tieBreakerAdmin: Address; + feeAdmin: Address; + fees: FeesArgs; + bump: number; + reserved: Array; +}; + +export function getNcnConfigEncoder(): Encoder { + return getStructEncoder([ + ['discriminator', getU64Encoder()], + ['ncn', getAddressEncoder()], + ['tieBreakerAdmin', getAddressEncoder()], + ['feeAdmin', getAddressEncoder()], + ['fees', getFeesEncoder()], + ['bump', getU8Encoder()], + ['reserved', getArrayEncoder(getU8Encoder(), { size: 127 })], + ]); +} + +export function getNcnConfigDecoder(): Decoder { + return getStructDecoder([ + ['discriminator', getU64Decoder()], + ['ncn', getAddressDecoder()], + ['tieBreakerAdmin', getAddressDecoder()], + ['feeAdmin', getAddressDecoder()], + ['fees', getFeesDecoder()], + ['bump', getU8Decoder()], + ['reserved', getArrayDecoder(getU8Decoder(), { size: 127 })], + ]); +} + +export function getNcnConfigCodec(): Codec { + return combineCodec(getNcnConfigEncoder(), getNcnConfigDecoder()); +} + +export function decodeNcnConfig( + encodedAccount: EncodedAccount +): Account; +export function decodeNcnConfig( + encodedAccount: MaybeEncodedAccount +): MaybeAccount; +export function decodeNcnConfig( + encodedAccount: EncodedAccount | MaybeEncodedAccount +): Account | MaybeAccount { + return decodeAccount( + encodedAccount as MaybeEncodedAccount, + getNcnConfigDecoder() + ); +} + +export async function fetchNcnConfig( + rpc: Parameters[0], + address: Address, + config?: FetchAccountConfig +): Promise> { + const maybeAccount = await fetchMaybeNcnConfig(rpc, address, config); + assertAccountExists(maybeAccount); + return maybeAccount; +} + +export async function fetchMaybeNcnConfig( + rpc: Parameters[0], + address: Address, + config?: FetchAccountConfig +): Promise> { + const maybeAccount = await fetchEncodedAccount(rpc, address, config); + return decodeNcnConfig(maybeAccount); +} + +export async function fetchAllNcnConfig( + rpc: Parameters[0], + addresses: Array
, + config?: FetchAccountsConfig +): Promise[]> { + const maybeAccounts = await fetchAllMaybeNcnConfig(rpc, addresses, config); + assertAccountsExist(maybeAccounts); + return maybeAccounts; +} + +export async function fetchAllMaybeNcnConfig( + rpc: Parameters[0], + addresses: Array
, + config?: FetchAccountsConfig +): Promise[]> { + const maybeAccounts = await fetchEncodedAccounts(rpc, addresses, config); + return maybeAccounts.map((maybeAccount) => decodeNcnConfig(maybeAccount)); +} + +export function getNcnConfigSize(): number { + return 352; +} diff --git a/clients/js/jito_tip_router/errors/jitoTipRouter.ts b/clients/js/jito_tip_router/errors/jitoTipRouter.ts index 9fe38be..e121839 100644 --- a/clients/js/jito_tip_router/errors/jitoTipRouter.ts +++ b/clients/js/jito_tip_router/errors/jitoTipRouter.ts @@ -26,11 +26,23 @@ export const JITO_TIP_ROUTER_ERROR__MODULO_OVERFLOW = 0x2102; // 8450 export const JITO_TIP_ROUTER_ERROR__INCORRECT_WEIGHT_TABLE_ADMIN = 0x2200; // 8704 /** CannotCreateFutureWeightTables: Cannnot create future weight tables */ export const JITO_TIP_ROUTER_ERROR__CANNOT_CREATE_FUTURE_WEIGHT_TABLES = 0x2201; // 8705 +/** FeeCapExceeded: Fee cap exceeded */ +export const JITO_TIP_ROUTER_ERROR__FEE_CAP_EXCEEDED = 0x2300; // 8960 +/** IncorrectNcnAdmin: Incorrect NCN Admin */ +export const JITO_TIP_ROUTER_ERROR__INCORRECT_NCN_ADMIN = 0x2400; // 9216 +/** IncorrectNcn: Incorrect NCN */ +export const JITO_TIP_ROUTER_ERROR__INCORRECT_NCN = 0x2401; // 9217 +/** IncorrectFeeAdmin: Incorrect fee admin */ +export const JITO_TIP_ROUTER_ERROR__INCORRECT_FEE_ADMIN = 0x2402; // 9218 export type JitoTipRouterError = | typeof JITO_TIP_ROUTER_ERROR__ARITHMETIC_OVERFLOW | typeof JITO_TIP_ROUTER_ERROR__CANNOT_CREATE_FUTURE_WEIGHT_TABLES | typeof JITO_TIP_ROUTER_ERROR__DENOMINATOR_IS_ZERO + | typeof JITO_TIP_ROUTER_ERROR__FEE_CAP_EXCEEDED + | typeof JITO_TIP_ROUTER_ERROR__INCORRECT_FEE_ADMIN + | typeof JITO_TIP_ROUTER_ERROR__INCORRECT_NCN + | typeof JITO_TIP_ROUTER_ERROR__INCORRECT_NCN_ADMIN | typeof JITO_TIP_ROUTER_ERROR__INCORRECT_WEIGHT_TABLE_ADMIN | typeof JITO_TIP_ROUTER_ERROR__MODULO_OVERFLOW | typeof JITO_TIP_ROUTER_ERROR__NO_MORE_TABLE_SLOTS; @@ -41,6 +53,10 @@ if (process.env.NODE_ENV !== 'production') { [JITO_TIP_ROUTER_ERROR__ARITHMETIC_OVERFLOW]: `Overflow`, [JITO_TIP_ROUTER_ERROR__CANNOT_CREATE_FUTURE_WEIGHT_TABLES]: `Cannnot create future weight tables`, [JITO_TIP_ROUTER_ERROR__DENOMINATOR_IS_ZERO]: `Zero in the denominator`, + [JITO_TIP_ROUTER_ERROR__FEE_CAP_EXCEEDED]: `Fee cap exceeded`, + [JITO_TIP_ROUTER_ERROR__INCORRECT_FEE_ADMIN]: `Incorrect fee admin`, + [JITO_TIP_ROUTER_ERROR__INCORRECT_NCN]: `Incorrect NCN`, + [JITO_TIP_ROUTER_ERROR__INCORRECT_NCN_ADMIN]: `Incorrect NCN Admin`, [JITO_TIP_ROUTER_ERROR__INCORRECT_WEIGHT_TABLE_ADMIN]: `Incorrect weight table admin`, [JITO_TIP_ROUTER_ERROR__MODULO_OVERFLOW]: `Modulo Overflow`, [JITO_TIP_ROUTER_ERROR__NO_MORE_TABLE_SLOTS]: `No more table slots available`, diff --git a/clients/js/jito_tip_router/instructions/finalizeWeightTable.ts b/clients/js/jito_tip_router/instructions/finalizeWeightTable.ts index f492b8a..df722b3 100644 --- a/clients/js/jito_tip_router/instructions/finalizeWeightTable.ts +++ b/clients/js/jito_tip_router/instructions/finalizeWeightTable.ts @@ -32,7 +32,7 @@ import { import { JITO_TIP_ROUTER_PROGRAM_ADDRESS } from '../programs'; import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; -export const FINALIZE_WEIGHT_TABLE_DISCRIMINATOR = 2; +export const FINALIZE_WEIGHT_TABLE_DISCRIMINATOR = 3; export function getFinalizeWeightTableDiscriminatorBytes() { return getU8Encoder().encode(FINALIZE_WEIGHT_TABLE_DISCRIMINATOR); diff --git a/clients/js/jito_tip_router/instructions/index.ts b/clients/js/jito_tip_router/instructions/index.ts index e121ff7..0f849b9 100644 --- a/clients/js/jito_tip_router/instructions/index.ts +++ b/clients/js/jito_tip_router/instructions/index.ts @@ -7,5 +7,8 @@ */ export * from './finalizeWeightTable'; +export * from './initializeConfig'; export * from './initializeWeightTable'; +export * from './setConfigFees'; +export * from './setNewAdmin'; export * from './updateWeightTable'; diff --git a/clients/js/jito_tip_router/instructions/initializeConfig.ts b/clients/js/jito_tip_router/instructions/initializeConfig.ts new file mode 100644 index 0000000..e5c99aa --- /dev/null +++ b/clients/js/jito_tip_router/instructions/initializeConfig.ts @@ -0,0 +1,287 @@ +/** + * 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, + getU8Decoder, + getU8Encoder, + transformEncoder, + type Address, + type Codec, + type Decoder, + type Encoder, + type IAccountMeta, + type IAccountSignerMeta, + type IInstruction, + type IInstructionWithAccounts, + type IInstructionWithData, + type ReadonlyAccount, + type ReadonlySignerAccount, + type TransactionSigner, + type WritableAccount, +} from '@solana/web3.js'; +import { JITO_TIP_ROUTER_PROGRAM_ADDRESS } from '../programs'; +import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; + +export const INITIALIZE_CONFIG_DISCRIMINATOR = 0; + +export function getInitializeConfigDiscriminatorBytes() { + return getU8Encoder().encode(INITIALIZE_CONFIG_DISCRIMINATOR); +} + +export type InitializeConfigInstruction< + TProgram extends string = typeof JITO_TIP_ROUTER_PROGRAM_ADDRESS, + TAccountConfig extends string | IAccountMeta = string, + TAccountNcn extends string | IAccountMeta = string, + TAccountNcnAdmin extends string | IAccountMeta = string, + TAccountFeeWallet extends string | IAccountMeta = string, + TAccountTieBreakerAdmin extends string | IAccountMeta = string, + TAccountRestakingProgramId extends string | IAccountMeta = string, + TAccountSystemProgram extends + | string + | IAccountMeta = '11111111111111111111111111111111', + TRemainingAccounts extends readonly IAccountMeta[] = [], +> = IInstruction & + IInstructionWithData & + IInstructionWithAccounts< + [ + TAccountConfig extends string + ? WritableAccount + : TAccountConfig, + TAccountNcn extends string ? ReadonlyAccount : TAccountNcn, + TAccountNcnAdmin extends string + ? ReadonlySignerAccount & + IAccountSignerMeta + : TAccountNcnAdmin, + TAccountFeeWallet extends string + ? ReadonlyAccount + : TAccountFeeWallet, + TAccountTieBreakerAdmin extends string + ? ReadonlyAccount + : TAccountTieBreakerAdmin, + TAccountRestakingProgramId extends string + ? ReadonlyAccount + : TAccountRestakingProgramId, + TAccountSystemProgram extends string + ? ReadonlyAccount + : TAccountSystemProgram, + ...TRemainingAccounts, + ] + >; + +export type InitializeConfigInstructionData = { + discriminator: number; + daoFeeBps: bigint; + ncnFeeBps: bigint; + blockEngineFeeBps: bigint; +}; + +export type InitializeConfigInstructionDataArgs = { + daoFeeBps: number | bigint; + ncnFeeBps: number | bigint; + blockEngineFeeBps: number | bigint; +}; + +export function getInitializeConfigInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([ + ['discriminator', getU8Encoder()], + ['daoFeeBps', getU64Encoder()], + ['ncnFeeBps', getU64Encoder()], + ['blockEngineFeeBps', getU64Encoder()], + ]), + (value) => ({ ...value, discriminator: INITIALIZE_CONFIG_DISCRIMINATOR }) + ); +} + +export function getInitializeConfigInstructionDataDecoder(): Decoder { + return getStructDecoder([ + ['discriminator', getU8Decoder()], + ['daoFeeBps', getU64Decoder()], + ['ncnFeeBps', getU64Decoder()], + ['blockEngineFeeBps', getU64Decoder()], + ]); +} + +export function getInitializeConfigInstructionDataCodec(): Codec< + InitializeConfigInstructionDataArgs, + InitializeConfigInstructionData +> { + return combineCodec( + getInitializeConfigInstructionDataEncoder(), + getInitializeConfigInstructionDataDecoder() + ); +} + +export type InitializeConfigInput< + TAccountConfig extends string = string, + TAccountNcn extends string = string, + TAccountNcnAdmin extends string = string, + TAccountFeeWallet extends string = string, + TAccountTieBreakerAdmin extends string = string, + TAccountRestakingProgramId extends string = string, + TAccountSystemProgram extends string = string, +> = { + config: Address; + ncn: Address; + ncnAdmin: TransactionSigner; + feeWallet: Address; + tieBreakerAdmin: Address; + restakingProgramId: Address; + systemProgram?: Address; + daoFeeBps: InitializeConfigInstructionDataArgs['daoFeeBps']; + ncnFeeBps: InitializeConfigInstructionDataArgs['ncnFeeBps']; + blockEngineFeeBps: InitializeConfigInstructionDataArgs['blockEngineFeeBps']; +}; + +export function getInitializeConfigInstruction< + TAccountConfig extends string, + TAccountNcn extends string, + TAccountNcnAdmin extends string, + TAccountFeeWallet extends string, + TAccountTieBreakerAdmin extends string, + TAccountRestakingProgramId extends string, + TAccountSystemProgram extends string, + TProgramAddress extends Address = typeof JITO_TIP_ROUTER_PROGRAM_ADDRESS, +>( + input: InitializeConfigInput< + TAccountConfig, + TAccountNcn, + TAccountNcnAdmin, + TAccountFeeWallet, + TAccountTieBreakerAdmin, + TAccountRestakingProgramId, + TAccountSystemProgram + >, + config?: { programAddress?: TProgramAddress } +): InitializeConfigInstruction< + TProgramAddress, + TAccountConfig, + TAccountNcn, + TAccountNcnAdmin, + TAccountFeeWallet, + TAccountTieBreakerAdmin, + TAccountRestakingProgramId, + TAccountSystemProgram +> { + // Program address. + const programAddress = + config?.programAddress ?? JITO_TIP_ROUTER_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + config: { value: input.config ?? null, isWritable: true }, + ncn: { value: input.ncn ?? null, isWritable: false }, + ncnAdmin: { value: input.ncnAdmin ?? null, isWritable: false }, + feeWallet: { value: input.feeWallet ?? null, isWritable: false }, + tieBreakerAdmin: { + value: input.tieBreakerAdmin ?? null, + isWritable: false, + }, + restakingProgramId: { + value: input.restakingProgramId ?? null, + isWritable: false, + }, + systemProgram: { value: input.systemProgram ?? null, isWritable: false }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + // Original args. + const args = { ...input }; + + // Resolve default values. + if (!accounts.systemProgram.value) { + accounts.systemProgram.value = + '11111111111111111111111111111111' as Address<'11111111111111111111111111111111'>; + } + + const getAccountMeta = getAccountMetaFactory(programAddress, 'programId'); + const instruction = { + accounts: [ + getAccountMeta(accounts.config), + getAccountMeta(accounts.ncn), + getAccountMeta(accounts.ncnAdmin), + getAccountMeta(accounts.feeWallet), + getAccountMeta(accounts.tieBreakerAdmin), + getAccountMeta(accounts.restakingProgramId), + getAccountMeta(accounts.systemProgram), + ], + programAddress, + data: getInitializeConfigInstructionDataEncoder().encode( + args as InitializeConfigInstructionDataArgs + ), + } as InitializeConfigInstruction< + TProgramAddress, + TAccountConfig, + TAccountNcn, + TAccountNcnAdmin, + TAccountFeeWallet, + TAccountTieBreakerAdmin, + TAccountRestakingProgramId, + TAccountSystemProgram + >; + + return instruction; +} + +export type ParsedInitializeConfigInstruction< + TProgram extends string = typeof JITO_TIP_ROUTER_PROGRAM_ADDRESS, + TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], +> = { + programAddress: Address; + accounts: { + config: TAccountMetas[0]; + ncn: TAccountMetas[1]; + ncnAdmin: TAccountMetas[2]; + feeWallet: TAccountMetas[3]; + tieBreakerAdmin: TAccountMetas[4]; + restakingProgramId: TAccountMetas[5]; + systemProgram: TAccountMetas[6]; + }; + data: InitializeConfigInstructionData; +}; + +export function parseInitializeConfigInstruction< + TProgram extends string, + TAccountMetas extends readonly IAccountMeta[], +>( + instruction: IInstruction & + IInstructionWithAccounts & + IInstructionWithData +): ParsedInitializeConfigInstruction { + if (instruction.accounts.length < 7) { + // TODO: Coded error. + throw new Error('Not enough accounts'); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = instruction.accounts![accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { + config: getNextAccount(), + ncn: getNextAccount(), + ncnAdmin: getNextAccount(), + feeWallet: getNextAccount(), + tieBreakerAdmin: getNextAccount(), + restakingProgramId: getNextAccount(), + systemProgram: getNextAccount(), + }, + data: getInitializeConfigInstructionDataDecoder().decode(instruction.data), + }; +} diff --git a/clients/js/jito_tip_router/instructions/initializeWeightTable.ts b/clients/js/jito_tip_router/instructions/initializeWeightTable.ts index 04154c3..1f64e0c 100644 --- a/clients/js/jito_tip_router/instructions/initializeWeightTable.ts +++ b/clients/js/jito_tip_router/instructions/initializeWeightTable.ts @@ -35,7 +35,7 @@ import { import { JITO_TIP_ROUTER_PROGRAM_ADDRESS } from '../programs'; import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; -export const INITIALIZE_WEIGHT_TABLE_DISCRIMINATOR = 0; +export const INITIALIZE_WEIGHT_TABLE_DISCRIMINATOR = 1; export function getInitializeWeightTableDiscriminatorBytes() { return getU8Encoder().encode(INITIALIZE_WEIGHT_TABLE_DISCRIMINATOR); diff --git a/clients/js/jito_tip_router/instructions/setConfigFees.ts b/clients/js/jito_tip_router/instructions/setConfigFees.ts new file mode 100644 index 0000000..7aba950 --- /dev/null +++ b/clients/js/jito_tip_router/instructions/setConfigFees.ts @@ -0,0 +1,245 @@ +/** + * 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, + getOptionDecoder, + getOptionEncoder, + getStructDecoder, + getStructEncoder, + getU64Decoder, + getU64Encoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type Address, + type Codec, + type Decoder, + type Encoder, + type IAccountMeta, + type IAccountSignerMeta, + type IInstruction, + type IInstructionWithAccounts, + type IInstructionWithData, + type Option, + type OptionOrNullable, + type ReadonlyAccount, + type ReadonlySignerAccount, + type TransactionSigner, + type WritableAccount, +} from '@solana/web3.js'; +import { JITO_TIP_ROUTER_PROGRAM_ADDRESS } from '../programs'; +import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; + +export const SET_CONFIG_FEES_DISCRIMINATOR = 4; + +export function getSetConfigFeesDiscriminatorBytes() { + return getU8Encoder().encode(SET_CONFIG_FEES_DISCRIMINATOR); +} + +export type SetConfigFeesInstruction< + TProgram extends string = typeof JITO_TIP_ROUTER_PROGRAM_ADDRESS, + TAccountConfig extends string | IAccountMeta = string, + TAccountNcn extends string | IAccountMeta = string, + TAccountNcnAdmin extends string | IAccountMeta = string, + TAccountRestakingProgramId extends string | IAccountMeta = string, + TRemainingAccounts extends readonly IAccountMeta[] = [], +> = IInstruction & + IInstructionWithData & + IInstructionWithAccounts< + [ + TAccountConfig extends string + ? WritableAccount + : TAccountConfig, + TAccountNcn extends string ? ReadonlyAccount : TAccountNcn, + TAccountNcnAdmin extends string + ? ReadonlySignerAccount & + IAccountSignerMeta + : TAccountNcnAdmin, + TAccountRestakingProgramId extends string + ? ReadonlyAccount + : TAccountRestakingProgramId, + ...TRemainingAccounts, + ] + >; + +export type SetConfigFeesInstructionData = { + discriminator: number; + newDaoFeeBps: Option; + newNcnFeeBps: Option; + newBlockEngineFeeBps: Option; + newFeeWallet: Option
; +}; + +export type SetConfigFeesInstructionDataArgs = { + newDaoFeeBps: OptionOrNullable; + newNcnFeeBps: OptionOrNullable; + newBlockEngineFeeBps: OptionOrNullable; + newFeeWallet: OptionOrNullable
; +}; + +export function getSetConfigFeesInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([ + ['discriminator', getU8Encoder()], + ['newDaoFeeBps', getOptionEncoder(getU64Encoder())], + ['newNcnFeeBps', getOptionEncoder(getU64Encoder())], + ['newBlockEngineFeeBps', getOptionEncoder(getU64Encoder())], + ['newFeeWallet', getOptionEncoder(getAddressEncoder())], + ]), + (value) => ({ ...value, discriminator: SET_CONFIG_FEES_DISCRIMINATOR }) + ); +} + +export function getSetConfigFeesInstructionDataDecoder(): Decoder { + return getStructDecoder([ + ['discriminator', getU8Decoder()], + ['newDaoFeeBps', getOptionDecoder(getU64Decoder())], + ['newNcnFeeBps', getOptionDecoder(getU64Decoder())], + ['newBlockEngineFeeBps', getOptionDecoder(getU64Decoder())], + ['newFeeWallet', getOptionDecoder(getAddressDecoder())], + ]); +} + +export function getSetConfigFeesInstructionDataCodec(): Codec< + SetConfigFeesInstructionDataArgs, + SetConfigFeesInstructionData +> { + return combineCodec( + getSetConfigFeesInstructionDataEncoder(), + getSetConfigFeesInstructionDataDecoder() + ); +} + +export type SetConfigFeesInput< + TAccountConfig extends string = string, + TAccountNcn extends string = string, + TAccountNcnAdmin extends string = string, + TAccountRestakingProgramId extends string = string, +> = { + config: Address; + ncn: Address; + ncnAdmin: TransactionSigner; + restakingProgramId: Address; + newDaoFeeBps: SetConfigFeesInstructionDataArgs['newDaoFeeBps']; + newNcnFeeBps: SetConfigFeesInstructionDataArgs['newNcnFeeBps']; + newBlockEngineFeeBps: SetConfigFeesInstructionDataArgs['newBlockEngineFeeBps']; + newFeeWallet: SetConfigFeesInstructionDataArgs['newFeeWallet']; +}; + +export function getSetConfigFeesInstruction< + TAccountConfig extends string, + TAccountNcn extends string, + TAccountNcnAdmin extends string, + TAccountRestakingProgramId extends string, + TProgramAddress extends Address = typeof JITO_TIP_ROUTER_PROGRAM_ADDRESS, +>( + input: SetConfigFeesInput< + TAccountConfig, + TAccountNcn, + TAccountNcnAdmin, + TAccountRestakingProgramId + >, + config?: { programAddress?: TProgramAddress } +): SetConfigFeesInstruction< + TProgramAddress, + TAccountConfig, + TAccountNcn, + TAccountNcnAdmin, + TAccountRestakingProgramId +> { + // Program address. + const programAddress = + config?.programAddress ?? JITO_TIP_ROUTER_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + config: { value: input.config ?? null, isWritable: true }, + ncn: { value: input.ncn ?? null, isWritable: false }, + ncnAdmin: { value: input.ncnAdmin ?? null, isWritable: false }, + restakingProgramId: { + value: input.restakingProgramId ?? null, + isWritable: false, + }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + // Original args. + const args = { ...input }; + + const getAccountMeta = getAccountMetaFactory(programAddress, 'programId'); + const instruction = { + accounts: [ + getAccountMeta(accounts.config), + getAccountMeta(accounts.ncn), + getAccountMeta(accounts.ncnAdmin), + getAccountMeta(accounts.restakingProgramId), + ], + programAddress, + data: getSetConfigFeesInstructionDataEncoder().encode( + args as SetConfigFeesInstructionDataArgs + ), + } as SetConfigFeesInstruction< + TProgramAddress, + TAccountConfig, + TAccountNcn, + TAccountNcnAdmin, + TAccountRestakingProgramId + >; + + return instruction; +} + +export type ParsedSetConfigFeesInstruction< + TProgram extends string = typeof JITO_TIP_ROUTER_PROGRAM_ADDRESS, + TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], +> = { + programAddress: Address; + accounts: { + config: TAccountMetas[0]; + ncn: TAccountMetas[1]; + ncnAdmin: TAccountMetas[2]; + restakingProgramId: TAccountMetas[3]; + }; + data: SetConfigFeesInstructionData; +}; + +export function parseSetConfigFeesInstruction< + TProgram extends string, + TAccountMetas extends readonly IAccountMeta[], +>( + instruction: IInstruction & + IInstructionWithAccounts & + IInstructionWithData +): ParsedSetConfigFeesInstruction { + if (instruction.accounts.length < 4) { + // TODO: Coded error. + throw new Error('Not enough accounts'); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = instruction.accounts![accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { + config: getNextAccount(), + ncn: getNextAccount(), + ncnAdmin: getNextAccount(), + restakingProgramId: getNextAccount(), + }, + data: getSetConfigFeesInstructionDataDecoder().decode(instruction.data), + }; +} diff --git a/clients/js/jito_tip_router/instructions/setNewAdmin.ts b/clients/js/jito_tip_router/instructions/setNewAdmin.ts new file mode 100644 index 0000000..c2272f3 --- /dev/null +++ b/clients/js/jito_tip_router/instructions/setNewAdmin.ts @@ -0,0 +1,240 @@ +/** + * 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, + transformEncoder, + type Address, + type Codec, + type Decoder, + type Encoder, + type IAccountMeta, + type IAccountSignerMeta, + type IInstruction, + type IInstructionWithAccounts, + type IInstructionWithData, + type ReadonlyAccount, + type ReadonlySignerAccount, + type TransactionSigner, + type WritableAccount, +} from '@solana/web3.js'; +import { JITO_TIP_ROUTER_PROGRAM_ADDRESS } from '../programs'; +import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; +import { + getConfigAdminRoleDecoder, + getConfigAdminRoleEncoder, + type ConfigAdminRole, + type ConfigAdminRoleArgs, +} from '../types'; + +export const SET_NEW_ADMIN_DISCRIMINATOR = 5; + +export function getSetNewAdminDiscriminatorBytes() { + return getU8Encoder().encode(SET_NEW_ADMIN_DISCRIMINATOR); +} + +export type SetNewAdminInstruction< + TProgram extends string = typeof JITO_TIP_ROUTER_PROGRAM_ADDRESS, + TAccountConfig extends string | IAccountMeta = string, + TAccountNcn extends string | IAccountMeta = string, + TAccountNcnAdmin extends string | IAccountMeta = string, + TAccountNewAdmin extends string | IAccountMeta = string, + TAccountRestakingProgramId extends string | IAccountMeta = string, + TRemainingAccounts extends readonly IAccountMeta[] = [], +> = IInstruction & + IInstructionWithData & + IInstructionWithAccounts< + [ + TAccountConfig extends string + ? WritableAccount + : TAccountConfig, + TAccountNcn extends string ? ReadonlyAccount : TAccountNcn, + TAccountNcnAdmin extends string + ? ReadonlySignerAccount & + IAccountSignerMeta + : TAccountNcnAdmin, + TAccountNewAdmin extends string + ? ReadonlyAccount + : TAccountNewAdmin, + TAccountRestakingProgramId extends string + ? ReadonlyAccount + : TAccountRestakingProgramId, + ...TRemainingAccounts, + ] + >; + +export type SetNewAdminInstructionData = { + discriminator: number; + role: ConfigAdminRole; +}; + +export type SetNewAdminInstructionDataArgs = { role: ConfigAdminRoleArgs }; + +export function getSetNewAdminInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([ + ['discriminator', getU8Encoder()], + ['role', getConfigAdminRoleEncoder()], + ]), + (value) => ({ ...value, discriminator: SET_NEW_ADMIN_DISCRIMINATOR }) + ); +} + +export function getSetNewAdminInstructionDataDecoder(): Decoder { + return getStructDecoder([ + ['discriminator', getU8Decoder()], + ['role', getConfigAdminRoleDecoder()], + ]); +} + +export function getSetNewAdminInstructionDataCodec(): Codec< + SetNewAdminInstructionDataArgs, + SetNewAdminInstructionData +> { + return combineCodec( + getSetNewAdminInstructionDataEncoder(), + getSetNewAdminInstructionDataDecoder() + ); +} + +export type SetNewAdminInput< + TAccountConfig extends string = string, + TAccountNcn extends string = string, + TAccountNcnAdmin extends string = string, + TAccountNewAdmin extends string = string, + TAccountRestakingProgramId extends string = string, +> = { + config: Address; + ncn: Address; + ncnAdmin: TransactionSigner; + newAdmin: Address; + restakingProgramId: Address; + role: SetNewAdminInstructionDataArgs['role']; +}; + +export function getSetNewAdminInstruction< + TAccountConfig extends string, + TAccountNcn extends string, + TAccountNcnAdmin extends string, + TAccountNewAdmin extends string, + TAccountRestakingProgramId extends string, + TProgramAddress extends Address = typeof JITO_TIP_ROUTER_PROGRAM_ADDRESS, +>( + input: SetNewAdminInput< + TAccountConfig, + TAccountNcn, + TAccountNcnAdmin, + TAccountNewAdmin, + TAccountRestakingProgramId + >, + config?: { programAddress?: TProgramAddress } +): SetNewAdminInstruction< + TProgramAddress, + TAccountConfig, + TAccountNcn, + TAccountNcnAdmin, + TAccountNewAdmin, + TAccountRestakingProgramId +> { + // Program address. + const programAddress = + config?.programAddress ?? JITO_TIP_ROUTER_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + config: { value: input.config ?? null, isWritable: true }, + ncn: { value: input.ncn ?? null, isWritable: false }, + ncnAdmin: { value: input.ncnAdmin ?? null, isWritable: false }, + newAdmin: { value: input.newAdmin ?? null, isWritable: false }, + restakingProgramId: { + value: input.restakingProgramId ?? null, + isWritable: false, + }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + // Original args. + const args = { ...input }; + + const getAccountMeta = getAccountMetaFactory(programAddress, 'programId'); + const instruction = { + accounts: [ + getAccountMeta(accounts.config), + getAccountMeta(accounts.ncn), + getAccountMeta(accounts.ncnAdmin), + getAccountMeta(accounts.newAdmin), + getAccountMeta(accounts.restakingProgramId), + ], + programAddress, + data: getSetNewAdminInstructionDataEncoder().encode( + args as SetNewAdminInstructionDataArgs + ), + } as SetNewAdminInstruction< + TProgramAddress, + TAccountConfig, + TAccountNcn, + TAccountNcnAdmin, + TAccountNewAdmin, + TAccountRestakingProgramId + >; + + return instruction; +} + +export type ParsedSetNewAdminInstruction< + TProgram extends string = typeof JITO_TIP_ROUTER_PROGRAM_ADDRESS, + TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], +> = { + programAddress: Address; + accounts: { + config: TAccountMetas[0]; + ncn: TAccountMetas[1]; + ncnAdmin: TAccountMetas[2]; + newAdmin: TAccountMetas[3]; + restakingProgramId: TAccountMetas[4]; + }; + data: SetNewAdminInstructionData; +}; + +export function parseSetNewAdminInstruction< + TProgram extends string, + TAccountMetas extends readonly IAccountMeta[], +>( + instruction: IInstruction & + IInstructionWithAccounts & + IInstructionWithData +): ParsedSetNewAdminInstruction { + if (instruction.accounts.length < 5) { + // TODO: Coded error. + throw new Error('Not enough accounts'); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = instruction.accounts![accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { + config: getNextAccount(), + ncn: getNextAccount(), + ncnAdmin: getNextAccount(), + newAdmin: getNextAccount(), + restakingProgramId: getNextAccount(), + }, + data: getSetNewAdminInstructionDataDecoder().decode(instruction.data), + }; +} diff --git a/clients/js/jito_tip_router/instructions/updateWeightTable.ts b/clients/js/jito_tip_router/instructions/updateWeightTable.ts index 9157708..41fb6be 100644 --- a/clients/js/jito_tip_router/instructions/updateWeightTable.ts +++ b/clients/js/jito_tip_router/instructions/updateWeightTable.ts @@ -32,7 +32,7 @@ import { import { JITO_TIP_ROUTER_PROGRAM_ADDRESS } from '../programs'; import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; -export const UPDATE_WEIGHT_TABLE_DISCRIMINATOR = 1; +export const UPDATE_WEIGHT_TABLE_DISCRIMINATOR = 2; export function getUpdateWeightTableDiscriminatorBytes() { return getU8Encoder().encode(UPDATE_WEIGHT_TABLE_DISCRIMINATOR); diff --git a/clients/js/jito_tip_router/programs/jitoTipRouter.ts b/clients/js/jito_tip_router/programs/jitoTipRouter.ts index c29d1e5..7320b4f 100644 --- a/clients/js/jito_tip_router/programs/jitoTipRouter.ts +++ b/clients/js/jito_tip_router/programs/jitoTipRouter.ts @@ -14,7 +14,10 @@ import { } from '@solana/web3.js'; import { type ParsedFinalizeWeightTableInstruction, + type ParsedInitializeConfigInstruction, type ParsedInitializeWeightTableInstruction, + type ParsedSetConfigFeesInstruction, + type ParsedSetNewAdminInstruction, type ParsedUpdateWeightTableInstruction, } from '../instructions'; @@ -22,13 +25,17 @@ export const JITO_TIP_ROUTER_PROGRAM_ADDRESS = 'Fv9aHCgvPQSr4jg9W8eTS6Ys1SNmh2qjyATrbsjEMaSH' as Address<'Fv9aHCgvPQSr4jg9W8eTS6Ys1SNmh2qjyATrbsjEMaSH'>; export enum JitoTipRouterAccount { + NcnConfig, WeightTable, } export enum JitoTipRouterInstruction { + InitializeConfig, InitializeWeightTable, UpdateWeightTable, FinalizeWeightTable, + SetConfigFees, + SetNewAdmin, } export function identifyJitoTipRouterInstruction( @@ -36,14 +43,23 @@ export function identifyJitoTipRouterInstruction( ): JitoTipRouterInstruction { const data = 'data' in instruction ? instruction.data : instruction; if (containsBytes(data, getU8Encoder().encode(0), 0)) { - return JitoTipRouterInstruction.InitializeWeightTable; + return JitoTipRouterInstruction.InitializeConfig; } if (containsBytes(data, getU8Encoder().encode(1), 0)) { - return JitoTipRouterInstruction.UpdateWeightTable; + return JitoTipRouterInstruction.InitializeWeightTable; } if (containsBytes(data, getU8Encoder().encode(2), 0)) { + return JitoTipRouterInstruction.UpdateWeightTable; + } + if (containsBytes(data, getU8Encoder().encode(3), 0)) { return JitoTipRouterInstruction.FinalizeWeightTable; } + if (containsBytes(data, getU8Encoder().encode(4), 0)) { + return JitoTipRouterInstruction.SetConfigFees; + } + if (containsBytes(data, getU8Encoder().encode(5), 0)) { + return JitoTipRouterInstruction.SetNewAdmin; + } throw new Error( 'The provided instruction could not be identified as a jitoTipRouter instruction.' ); @@ -52,6 +68,9 @@ export function identifyJitoTipRouterInstruction( export type ParsedJitoTipRouterInstruction< TProgram extends string = 'Fv9aHCgvPQSr4jg9W8eTS6Ys1SNmh2qjyATrbsjEMaSH', > = + | ({ + instructionType: JitoTipRouterInstruction.InitializeConfig; + } & ParsedInitializeConfigInstruction) | ({ instructionType: JitoTipRouterInstruction.InitializeWeightTable; } & ParsedInitializeWeightTableInstruction) @@ -60,4 +79,10 @@ export type ParsedJitoTipRouterInstruction< } & ParsedUpdateWeightTableInstruction) | ({ instructionType: JitoTipRouterInstruction.FinalizeWeightTable; - } & ParsedFinalizeWeightTableInstruction); + } & ParsedFinalizeWeightTableInstruction) + | ({ + instructionType: JitoTipRouterInstruction.SetConfigFees; + } & ParsedSetConfigFeesInstruction) + | ({ + instructionType: JitoTipRouterInstruction.SetNewAdmin; + } & ParsedSetNewAdminInstruction); diff --git a/clients/js/jito_tip_router/types/configAdminRole.ts b/clients/js/jito_tip_router/types/configAdminRole.ts new file mode 100644 index 0000000..e00adbc --- /dev/null +++ b/clients/js/jito_tip_router/types/configAdminRole.ts @@ -0,0 +1,38 @@ +/** + * 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, + getEnumDecoder, + getEnumEncoder, + type Codec, + type Decoder, + type Encoder, +} from '@solana/web3.js'; + +export enum ConfigAdminRole { + FeeAdmin, + TieBreakerAdmin, +} + +export type ConfigAdminRoleArgs = ConfigAdminRole; + +export function getConfigAdminRoleEncoder(): Encoder { + return getEnumEncoder(ConfigAdminRole); +} + +export function getConfigAdminRoleDecoder(): Decoder { + return getEnumDecoder(ConfigAdminRole); +} + +export function getConfigAdminRoleCodec(): Codec< + ConfigAdminRoleArgs, + ConfigAdminRole +> { + return combineCodec(getConfigAdminRoleEncoder(), getConfigAdminRoleDecoder()); +} diff --git a/clients/js/jito_tip_router/types/fee.ts b/clients/js/jito_tip_router/types/fee.ts new file mode 100644 index 0000000..f91f0c4 --- /dev/null +++ b/clients/js/jito_tip_router/types/fee.ts @@ -0,0 +1,61 @@ +/** + * 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/fees.ts b/clients/js/jito_tip_router/types/fees.ts new file mode 100644 index 0000000..2803f66 --- /dev/null +++ b/clients/js/jito_tip_router/types/fees.ts @@ -0,0 +1,39 @@ +/** + * 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, + type Codec, + type Decoder, + type Encoder, +} from '@solana/web3.js'; +import { getFeeDecoder, getFeeEncoder, type Fee, type FeeArgs } from '.'; + +export type Fees = { fee1: Fee; fee2: Fee }; + +export type FeesArgs = { fee1: FeeArgs; fee2: FeeArgs }; + +export function getFeesEncoder(): Encoder { + return getStructEncoder([ + ['fee1', getFeeEncoder()], + ['fee2', getFeeEncoder()], + ]); +} + +export function getFeesDecoder(): Decoder { + return getStructDecoder([ + ['fee1', getFeeDecoder()], + ['fee2', getFeeDecoder()], + ]); +} + +export function getFeesCodec(): Codec { + return combineCodec(getFeesEncoder(), getFeesDecoder()); +} diff --git a/clients/js/jito_tip_router/types/index.ts b/clients/js/jito_tip_router/types/index.ts index 5597466..a78e605 100644 --- a/clients/js/jito_tip_router/types/index.ts +++ b/clients/js/jito_tip_router/types/index.ts @@ -6,4 +6,7 @@ * @see https://github.com/kinobi-so/kinobi */ +export * from './configAdminRole'; +export * from './fee'; +export * from './fees'; export * from './weightEntry'; 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 cbf30af..6feb3df 100644 --- a/clients/rust/jito_tip_router/src/generated/accounts/mod.rs +++ b/clients/rust/jito_tip_router/src/generated/accounts/mod.rs @@ -4,6 +4,7 @@ //! //! +pub(crate) mod r#ncn_config; pub(crate) mod r#weight_table; -pub use self::r#weight_table::*; +pub use self::{r#ncn_config::*, 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 new file mode 100644 index 0000000..8897227 --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/accounts/ncn_config.rs @@ -0,0 +1,81 @@ +//! 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::Fees; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct NcnConfig { + pub discriminator: u64, + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub ncn: Pubkey, + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub tie_breaker_admin: Pubkey, + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub fee_admin: Pubkey, + pub fees: Fees, + pub bump: u8, + #[cfg_attr(feature = "serde", serde(with = "serde_with::As::"))] + pub reserved: [u8; 127], +} + +impl NcnConfig { + pub const LEN: usize = 352; + + #[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 NcnConfig { + 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 NcnConfig { + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { + Ok(Self::deserialize(buf)?) + } +} + +#[cfg(feature = "anchor")] +impl anchor_lang::AccountSerialize for NcnConfig {} + +#[cfg(feature = "anchor")] +impl anchor_lang::Owner for NcnConfig { + fn owner() -> Pubkey { + crate::JITO_TIP_ROUTER_ID + } +} + +#[cfg(feature = "anchor-idl-build")] +impl anchor_lang::IdlBuild for NcnConfig {} + +#[cfg(feature = "anchor-idl-build")] +impl anchor_lang::Discriminator for NcnConfig { + const DISCRIMINATOR: [u8; 8] = [0; 8]; +} 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 99bb832..cfd022b 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 @@ -27,6 +27,18 @@ pub enum JitoTipRouterError { /// 8705 - Cannnot create future weight tables #[error("Cannnot create future weight tables")] CannotCreateFutureWeightTables = 0x2201, + /// 8960 - Fee cap exceeded + #[error("Fee cap exceeded")] + FeeCapExceeded = 0x2300, + /// 9216 - Incorrect NCN Admin + #[error("Incorrect NCN Admin")] + IncorrectNcnAdmin = 0x2400, + /// 9217 - Incorrect NCN + #[error("Incorrect NCN")] + IncorrectNcn = 0x2401, + /// 9218 - Incorrect fee admin + #[error("Incorrect fee admin")] + IncorrectFeeAdmin = 0x2402, } impl solana_program::program_error::PrintProgramError for JitoTipRouterError { diff --git a/clients/rust/jito_tip_router/src/generated/instructions/finalize_weight_table.rs b/clients/rust/jito_tip_router/src/generated/instructions/finalize_weight_table.rs index ab49228..16cac74 100644 --- a/clients/rust/jito_tip_router/src/generated/instructions/finalize_weight_table.rs +++ b/clients/rust/jito_tip_router/src/generated/instructions/finalize_weight_table.rs @@ -68,7 +68,7 @@ pub struct FinalizeWeightTableInstructionData { impl FinalizeWeightTableInstructionData { pub fn new() -> Self { - Self { discriminator: 2 } + Self { discriminator: 3 } } } diff --git a/clients/rust/jito_tip_router/src/generated/instructions/initialize_config.rs b/clients/rust/jito_tip_router/src/generated/instructions/initialize_config.rs new file mode 100644 index 0000000..2e718af --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/instructions/initialize_config.rs @@ -0,0 +1,609 @@ +//! 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}; + +/// Accounts. +pub struct InitializeConfig { + pub config: solana_program::pubkey::Pubkey, + + pub ncn: solana_program::pubkey::Pubkey, + + pub ncn_admin: solana_program::pubkey::Pubkey, + + pub fee_wallet: solana_program::pubkey::Pubkey, + + pub tie_breaker_admin: solana_program::pubkey::Pubkey, + + pub restaking_program_id: solana_program::pubkey::Pubkey, + + pub system_program: solana_program::pubkey::Pubkey, +} + +impl InitializeConfig { + pub fn instruction( + &self, + args: InitializeConfigInstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: InitializeConfigInstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(7 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.config, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.ncn, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.ncn_admin, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.fee_wallet, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.tie_breaker_admin, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.restaking_program_id, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + accounts.extend_from_slice(remaining_accounts); + let mut data = InitializeConfigInstructionData::new().try_to_vec().unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::JITO_TIP_ROUTER_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +pub struct InitializeConfigInstructionData { + discriminator: u8, +} + +impl InitializeConfigInstructionData { + pub fn new() -> Self { + Self { discriminator: 0 } + } +} + +impl Default for InitializeConfigInstructionData { + fn default() -> Self { + Self::new() + } +} + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct InitializeConfigInstructionArgs { + pub dao_fee_bps: u64, + pub ncn_fee_bps: u64, + pub block_engine_fee_bps: u64, +} + +/// Instruction builder for `InitializeConfig`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` config +/// 1. `[]` ncn +/// 2. `[signer]` ncn_admin +/// 3. `[]` fee_wallet +/// 4. `[]` tie_breaker_admin +/// 5. `[]` restaking_program_id +/// 6. `[optional]` system_program (default to `11111111111111111111111111111111`) +#[derive(Clone, Debug, Default)] +pub struct InitializeConfigBuilder { + config: Option, + ncn: Option, + ncn_admin: Option, + fee_wallet: Option, + tie_breaker_admin: Option, + restaking_program_id: Option, + system_program: Option, + dao_fee_bps: Option, + ncn_fee_bps: Option, + block_engine_fee_bps: Option, + __remaining_accounts: Vec, +} + +impl InitializeConfigBuilder { + pub fn new() -> Self { + Self::default() + } + #[inline(always)] + pub fn config(&mut self, config: solana_program::pubkey::Pubkey) -> &mut Self { + self.config = Some(config); + self + } + #[inline(always)] + pub fn ncn(&mut self, ncn: solana_program::pubkey::Pubkey) -> &mut Self { + self.ncn = Some(ncn); + self + } + #[inline(always)] + pub fn ncn_admin(&mut self, ncn_admin: solana_program::pubkey::Pubkey) -> &mut Self { + self.ncn_admin = Some(ncn_admin); + self + } + #[inline(always)] + pub fn fee_wallet(&mut self, fee_wallet: solana_program::pubkey::Pubkey) -> &mut Self { + self.fee_wallet = Some(fee_wallet); + self + } + #[inline(always)] + pub fn tie_breaker_admin( + &mut self, + tie_breaker_admin: solana_program::pubkey::Pubkey, + ) -> &mut Self { + self.tie_breaker_admin = Some(tie_breaker_admin); + self + } + #[inline(always)] + pub fn restaking_program_id( + &mut self, + restaking_program_id: solana_program::pubkey::Pubkey, + ) -> &mut Self { + self.restaking_program_id = Some(restaking_program_id); + self + } + /// `[optional account, default to '11111111111111111111111111111111']` + #[inline(always)] + pub fn system_program(&mut self, system_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.system_program = Some(system_program); + self + } + #[inline(always)] + pub fn dao_fee_bps(&mut self, dao_fee_bps: u64) -> &mut Self { + self.dao_fee_bps = Some(dao_fee_bps); + self + } + #[inline(always)] + pub fn ncn_fee_bps(&mut self, ncn_fee_bps: u64) -> &mut Self { + self.ncn_fee_bps = Some(ncn_fee_bps); + self + } + #[inline(always)] + pub fn block_engine_fee_bps(&mut self, block_engine_fee_bps: u64) -> &mut Self { + self.block_engine_fee_bps = Some(block_engine_fee_bps); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = InitializeConfig { + config: self.config.expect("config is not set"), + ncn: self.ncn.expect("ncn is not set"), + ncn_admin: self.ncn_admin.expect("ncn_admin is not set"), + fee_wallet: self.fee_wallet.expect("fee_wallet is not set"), + tie_breaker_admin: self + .tie_breaker_admin + .expect("tie_breaker_admin is not set"), + restaking_program_id: self + .restaking_program_id + .expect("restaking_program_id is not set"), + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + }; + let args = InitializeConfigInstructionArgs { + dao_fee_bps: self.dao_fee_bps.clone().expect("dao_fee_bps is not set"), + ncn_fee_bps: self.ncn_fee_bps.clone().expect("ncn_fee_bps is not set"), + block_engine_fee_bps: self + .block_engine_fee_bps + .clone() + .expect("block_engine_fee_bps is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `initialize_config` CPI accounts. +pub struct InitializeConfigCpiAccounts<'a, 'b> { + pub config: &'b solana_program::account_info::AccountInfo<'a>, + + pub ncn: &'b solana_program::account_info::AccountInfo<'a>, + + pub ncn_admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub fee_wallet: &'b solana_program::account_info::AccountInfo<'a>, + + pub tie_breaker_admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub restaking_program_id: &'b solana_program::account_info::AccountInfo<'a>, + + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `initialize_config` CPI instruction. +pub struct InitializeConfigCpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + + pub config: &'b solana_program::account_info::AccountInfo<'a>, + + pub ncn: &'b solana_program::account_info::AccountInfo<'a>, + + pub ncn_admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub fee_wallet: &'b solana_program::account_info::AccountInfo<'a>, + + pub tie_breaker_admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub restaking_program_id: &'b solana_program::account_info::AccountInfo<'a>, + + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, + /// The arguments for the instruction. + pub __args: InitializeConfigInstructionArgs, +} + +impl<'a, 'b> InitializeConfigCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: InitializeConfigCpiAccounts<'a, 'b>, + args: InitializeConfigInstructionArgs, + ) -> Self { + Self { + __program: program, + config: accounts.config, + ncn: accounts.ncn, + ncn_admin: accounts.ncn_admin, + fee_wallet: accounts.fee_wallet, + tie_breaker_admin: accounts.tie_breaker_admin, + restaking_program_id: accounts.restaking_program_id, + system_program: accounts.system_program, + __args: args, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(7 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.config.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.ncn.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.ncn_admin.key, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.fee_wallet.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.tie_breaker_admin.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.restaking_program_id.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.system_program.key, + false, + )); + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let mut data = InitializeConfigInstructionData::new().try_to_vec().unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::JITO_TIP_ROUTER_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(7 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.config.clone()); + account_infos.push(self.ncn.clone()); + account_infos.push(self.ncn_admin.clone()); + account_infos.push(self.fee_wallet.clone()); + account_infos.push(self.tie_breaker_admin.clone()); + account_infos.push(self.restaking_program_id.clone()); + account_infos.push(self.system_program.clone()); + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// Instruction builder for `InitializeConfig` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` config +/// 1. `[]` ncn +/// 2. `[signer]` ncn_admin +/// 3. `[]` fee_wallet +/// 4. `[]` tie_breaker_admin +/// 5. `[]` restaking_program_id +/// 6. `[]` system_program +#[derive(Clone, Debug)] +pub struct InitializeConfigCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> InitializeConfigCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(InitializeConfigCpiBuilderInstruction { + __program: program, + config: None, + ncn: None, + ncn_admin: None, + fee_wallet: None, + tie_breaker_admin: None, + restaking_program_id: None, + system_program: None, + dao_fee_bps: None, + ncn_fee_bps: None, + block_engine_fee_bps: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + #[inline(always)] + pub fn config( + &mut self, + config: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.config = Some(config); + self + } + #[inline(always)] + pub fn ncn(&mut self, ncn: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.ncn = Some(ncn); + self + } + #[inline(always)] + pub fn ncn_admin( + &mut self, + ncn_admin: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.ncn_admin = Some(ncn_admin); + self + } + #[inline(always)] + pub fn fee_wallet( + &mut self, + fee_wallet: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.fee_wallet = Some(fee_wallet); + self + } + #[inline(always)] + pub fn tie_breaker_admin( + &mut self, + tie_breaker_admin: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.tie_breaker_admin = Some(tie_breaker_admin); + self + } + #[inline(always)] + pub fn restaking_program_id( + &mut self, + restaking_program_id: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.restaking_program_id = Some(restaking_program_id); + self + } + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + self + } + #[inline(always)] + pub fn dao_fee_bps(&mut self, dao_fee_bps: u64) -> &mut Self { + self.instruction.dao_fee_bps = Some(dao_fee_bps); + self + } + #[inline(always)] + pub fn ncn_fee_bps(&mut self, ncn_fee_bps: u64) -> &mut Self { + self.instruction.ncn_fee_bps = Some(ncn_fee_bps); + self + } + #[inline(always)] + pub fn block_engine_fee_bps(&mut self, block_engine_fee_bps: u64) -> &mut Self { + self.instruction.block_engine_fee_bps = Some(block_engine_fee_bps); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let args = InitializeConfigInstructionArgs { + dao_fee_bps: self + .instruction + .dao_fee_bps + .clone() + .expect("dao_fee_bps is not set"), + ncn_fee_bps: self + .instruction + .ncn_fee_bps + .clone() + .expect("ncn_fee_bps is not set"), + block_engine_fee_bps: self + .instruction + .block_engine_fee_bps + .clone() + .expect("block_engine_fee_bps is not set"), + }; + let instruction = InitializeConfigCpi { + __program: self.instruction.__program, + + config: self.instruction.config.expect("config is not set"), + + ncn: self.instruction.ncn.expect("ncn is not set"), + + ncn_admin: self.instruction.ncn_admin.expect("ncn_admin is not set"), + + fee_wallet: self.instruction.fee_wallet.expect("fee_wallet is not set"), + + tie_breaker_admin: self + .instruction + .tie_breaker_admin + .expect("tie_breaker_admin is not set"), + + restaking_program_id: self + .instruction + .restaking_program_id + .expect("restaking_program_id is not set"), + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +#[derive(Clone, Debug)] +struct InitializeConfigCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + config: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ncn: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ncn_admin: Option<&'b solana_program::account_info::AccountInfo<'a>>, + fee_wallet: Option<&'b solana_program::account_info::AccountInfo<'a>>, + tie_breaker_admin: Option<&'b solana_program::account_info::AccountInfo<'a>>, + restaking_program_id: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + dao_fee_bps: Option, + ncn_fee_bps: Option, + block_engine_fee_bps: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/jito_tip_router/src/generated/instructions/initialize_weight_table.rs b/clients/rust/jito_tip_router/src/generated/instructions/initialize_weight_table.rs index 5647197..b839b5e 100644 --- a/clients/rust/jito_tip_router/src/generated/instructions/initialize_weight_table.rs +++ b/clients/rust/jito_tip_router/src/generated/instructions/initialize_weight_table.rs @@ -80,7 +80,7 @@ pub struct InitializeWeightTableInstructionData { impl InitializeWeightTableInstructionData { pub fn new() -> Self { - Self { discriminator: 0 } + Self { discriminator: 1 } } } diff --git a/clients/rust/jito_tip_router/src/generated/instructions/mod.rs b/clients/rust/jito_tip_router/src/generated/instructions/mod.rs index db9b2e2..ad897c0 100644 --- a/clients/rust/jito_tip_router/src/generated/instructions/mod.rs +++ b/clients/rust/jito_tip_router/src/generated/instructions/mod.rs @@ -5,9 +5,13 @@ //! pub(crate) mod r#finalize_weight_table; +pub(crate) mod r#initialize_config; pub(crate) mod r#initialize_weight_table; +pub(crate) mod r#set_config_fees; +pub(crate) mod r#set_new_admin; pub(crate) mod r#update_weight_table; pub use self::{ - r#finalize_weight_table::*, r#initialize_weight_table::*, r#update_weight_table::*, + r#finalize_weight_table::*, r#initialize_config::*, r#initialize_weight_table::*, + r#set_config_fees::*, r#set_new_admin::*, r#update_weight_table::*, }; 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 new file mode 100644 index 0000000..686f5d8 --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/instructions/set_config_fees.rs @@ -0,0 +1,494 @@ +//! 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; + +/// Accounts. +pub struct SetConfigFees { + pub config: solana_program::pubkey::Pubkey, + + pub ncn: solana_program::pubkey::Pubkey, + + pub ncn_admin: solana_program::pubkey::Pubkey, + + pub restaking_program_id: solana_program::pubkey::Pubkey, +} + +impl SetConfigFees { + pub fn instruction( + &self, + args: SetConfigFeesInstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: SetConfigFeesInstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(4 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.config, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.ncn, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.ncn_admin, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.restaking_program_id, + false, + )); + accounts.extend_from_slice(remaining_accounts); + let mut data = SetConfigFeesInstructionData::new().try_to_vec().unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::JITO_TIP_ROUTER_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +pub struct SetConfigFeesInstructionData { + discriminator: u8, +} + +impl SetConfigFeesInstructionData { + pub fn new() -> Self { + Self { discriminator: 4 } + } +} + +impl Default for SetConfigFeesInstructionData { + fn default() -> Self { + Self::new() + } +} + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SetConfigFeesInstructionArgs { + pub new_dao_fee_bps: Option, + pub new_ncn_fee_bps: Option, + pub new_block_engine_fee_bps: Option, + pub new_fee_wallet: Option, +} + +/// Instruction builder for `SetConfigFees`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` config +/// 1. `[]` ncn +/// 2. `[signer]` ncn_admin +/// 3. `[]` restaking_program_id +#[derive(Clone, Debug, Default)] +pub struct SetConfigFeesBuilder { + config: Option, + ncn: Option, + ncn_admin: Option, + restaking_program_id: Option, + new_dao_fee_bps: Option, + new_ncn_fee_bps: Option, + new_block_engine_fee_bps: Option, + new_fee_wallet: Option, + __remaining_accounts: Vec, +} + +impl SetConfigFeesBuilder { + pub fn new() -> Self { + Self::default() + } + #[inline(always)] + pub fn config(&mut self, config: solana_program::pubkey::Pubkey) -> &mut Self { + self.config = Some(config); + self + } + #[inline(always)] + pub fn ncn(&mut self, ncn: solana_program::pubkey::Pubkey) -> &mut Self { + self.ncn = Some(ncn); + self + } + #[inline(always)] + pub fn ncn_admin(&mut self, ncn_admin: solana_program::pubkey::Pubkey) -> &mut Self { + self.ncn_admin = Some(ncn_admin); + self + } + #[inline(always)] + pub fn restaking_program_id( + &mut self, + restaking_program_id: solana_program::pubkey::Pubkey, + ) -> &mut Self { + self.restaking_program_id = Some(restaking_program_id); + self + } + /// `[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); + 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); + 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); + 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); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = SetConfigFees { + config: self.config.expect("config is not set"), + ncn: self.ncn.expect("ncn is not set"), + ncn_admin: self.ncn_admin.expect("ncn_admin is not set"), + restaking_program_id: self + .restaking_program_id + .expect("restaking_program_id is not set"), + }; + let args = SetConfigFeesInstructionArgs { + 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(), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `set_config_fees` CPI accounts. +pub struct SetConfigFeesCpiAccounts<'a, 'b> { + pub config: &'b solana_program::account_info::AccountInfo<'a>, + + pub ncn: &'b solana_program::account_info::AccountInfo<'a>, + + pub ncn_admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub restaking_program_id: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `set_config_fees` CPI instruction. +pub struct SetConfigFeesCpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + + pub config: &'b solana_program::account_info::AccountInfo<'a>, + + pub ncn: &'b solana_program::account_info::AccountInfo<'a>, + + pub ncn_admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub restaking_program_id: &'b solana_program::account_info::AccountInfo<'a>, + /// The arguments for the instruction. + pub __args: SetConfigFeesInstructionArgs, +} + +impl<'a, 'b> SetConfigFeesCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: SetConfigFeesCpiAccounts<'a, 'b>, + args: SetConfigFeesInstructionArgs, + ) -> Self { + Self { + __program: program, + config: accounts.config, + ncn: accounts.ncn, + ncn_admin: accounts.ncn_admin, + restaking_program_id: accounts.restaking_program_id, + __args: args, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(4 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.config.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.ncn.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.ncn_admin.key, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.restaking_program_id.key, + false, + )); + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let mut data = SetConfigFeesInstructionData::new().try_to_vec().unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::JITO_TIP_ROUTER_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(4 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.config.clone()); + account_infos.push(self.ncn.clone()); + account_infos.push(self.ncn_admin.clone()); + account_infos.push(self.restaking_program_id.clone()); + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// Instruction builder for `SetConfigFees` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` config +/// 1. `[]` ncn +/// 2. `[signer]` ncn_admin +/// 3. `[]` restaking_program_id +#[derive(Clone, Debug)] +pub struct SetConfigFeesCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> SetConfigFeesCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(SetConfigFeesCpiBuilderInstruction { + __program: program, + config: None, + ncn: None, + ncn_admin: None, + restaking_program_id: None, + new_dao_fee_bps: None, + new_ncn_fee_bps: None, + new_block_engine_fee_bps: None, + new_fee_wallet: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + #[inline(always)] + pub fn config( + &mut self, + config: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.config = Some(config); + self + } + #[inline(always)] + pub fn ncn(&mut self, ncn: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.ncn = Some(ncn); + self + } + #[inline(always)] + pub fn ncn_admin( + &mut self, + ncn_admin: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.ncn_admin = Some(ncn_admin); + self + } + #[inline(always)] + pub fn restaking_program_id( + &mut self, + restaking_program_id: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.restaking_program_id = Some(restaking_program_id); + self + } + /// `[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); + 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); + 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); + 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); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let args = SetConfigFeesInstructionArgs { + 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(), + }; + let instruction = SetConfigFeesCpi { + __program: self.instruction.__program, + + config: self.instruction.config.expect("config is not set"), + + ncn: self.instruction.ncn.expect("ncn is not set"), + + ncn_admin: self.instruction.ncn_admin.expect("ncn_admin is not set"), + + restaking_program_id: self + .instruction + .restaking_program_id + .expect("restaking_program_id is not set"), + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +#[derive(Clone, Debug)] +struct SetConfigFeesCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + config: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ncn: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ncn_admin: Option<&'b solana_program::account_info::AccountInfo<'a>>, + restaking_program_id: Option<&'b solana_program::account_info::AccountInfo<'a>>, + new_dao_fee_bps: Option, + new_ncn_fee_bps: Option, + new_block_engine_fee_bps: Option, + new_fee_wallet: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/jito_tip_router/src/generated/instructions/set_new_admin.rs b/clients/rust/jito_tip_router/src/generated/instructions/set_new_admin.rs new file mode 100644 index 0000000..aa1021d --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/instructions/set_new_admin.rs @@ -0,0 +1,476 @@ +//! 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 crate::generated::types::ConfigAdminRole; + +/// Accounts. +pub struct SetNewAdmin { + pub config: solana_program::pubkey::Pubkey, + + pub ncn: solana_program::pubkey::Pubkey, + + pub ncn_admin: solana_program::pubkey::Pubkey, + + pub new_admin: solana_program::pubkey::Pubkey, + + pub restaking_program_id: solana_program::pubkey::Pubkey, +} + +impl SetNewAdmin { + pub fn instruction( + &self, + args: SetNewAdminInstructionArgs, + ) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(args, &[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + args: SetNewAdminInstructionArgs, + remaining_accounts: &[solana_program::instruction::AccountMeta], + ) -> solana_program::instruction::Instruction { + let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.config, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.ncn, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.ncn_admin, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.new_admin, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.restaking_program_id, + false, + )); + accounts.extend_from_slice(remaining_accounts); + let mut data = SetNewAdminInstructionData::new().try_to_vec().unwrap(); + let mut args = args.try_to_vec().unwrap(); + data.append(&mut args); + + solana_program::instruction::Instruction { + program_id: crate::JITO_TIP_ROUTER_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +pub struct SetNewAdminInstructionData { + discriminator: u8, +} + +impl SetNewAdminInstructionData { + pub fn new() -> Self { + Self { discriminator: 5 } + } +} + +impl Default for SetNewAdminInstructionData { + fn default() -> Self { + Self::new() + } +} + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SetNewAdminInstructionArgs { + pub role: ConfigAdminRole, +} + +/// Instruction builder for `SetNewAdmin`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` config +/// 1. `[]` ncn +/// 2. `[signer]` ncn_admin +/// 3. `[]` new_admin +/// 4. `[]` restaking_program_id +#[derive(Clone, Debug, Default)] +pub struct SetNewAdminBuilder { + config: Option, + ncn: Option, + ncn_admin: Option, + new_admin: Option, + restaking_program_id: Option, + role: Option, + __remaining_accounts: Vec, +} + +impl SetNewAdminBuilder { + pub fn new() -> Self { + Self::default() + } + #[inline(always)] + pub fn config(&mut self, config: solana_program::pubkey::Pubkey) -> &mut Self { + self.config = Some(config); + self + } + #[inline(always)] + pub fn ncn(&mut self, ncn: solana_program::pubkey::Pubkey) -> &mut Self { + self.ncn = Some(ncn); + self + } + #[inline(always)] + pub fn ncn_admin(&mut self, ncn_admin: solana_program::pubkey::Pubkey) -> &mut Self { + self.ncn_admin = Some(ncn_admin); + self + } + #[inline(always)] + pub fn new_admin(&mut self, new_admin: solana_program::pubkey::Pubkey) -> &mut Self { + self.new_admin = Some(new_admin); + self + } + #[inline(always)] + pub fn restaking_program_id( + &mut self, + restaking_program_id: solana_program::pubkey::Pubkey, + ) -> &mut Self { + self.restaking_program_id = Some(restaking_program_id); + self + } + #[inline(always)] + pub fn role(&mut self, role: ConfigAdminRole) -> &mut Self { + self.role = Some(role); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: solana_program::instruction::AccountMeta, + ) -> &mut Self { + self.__remaining_accounts.push(account); + self + } + /// Add additional accounts to the instruction. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[solana_program::instruction::AccountMeta], + ) -> &mut Self { + self.__remaining_accounts.extend_from_slice(accounts); + self + } + #[allow(clippy::clone_on_copy)] + pub fn instruction(&self) -> solana_program::instruction::Instruction { + let accounts = SetNewAdmin { + config: self.config.expect("config is not set"), + ncn: self.ncn.expect("ncn is not set"), + ncn_admin: self.ncn_admin.expect("ncn_admin is not set"), + new_admin: self.new_admin.expect("new_admin is not set"), + restaking_program_id: self + .restaking_program_id + .expect("restaking_program_id is not set"), + }; + let args = SetNewAdminInstructionArgs { + role: self.role.clone().expect("role is not set"), + }; + + accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) + } +} + +/// `set_new_admin` CPI accounts. +pub struct SetNewAdminCpiAccounts<'a, 'b> { + pub config: &'b solana_program::account_info::AccountInfo<'a>, + + pub ncn: &'b solana_program::account_info::AccountInfo<'a>, + + pub ncn_admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub new_admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub restaking_program_id: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `set_new_admin` CPI instruction. +pub struct SetNewAdminCpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + + pub config: &'b solana_program::account_info::AccountInfo<'a>, + + pub ncn: &'b solana_program::account_info::AccountInfo<'a>, + + pub ncn_admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub new_admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub restaking_program_id: &'b solana_program::account_info::AccountInfo<'a>, + /// The arguments for the instruction. + pub __args: SetNewAdminInstructionArgs, +} + +impl<'a, 'b> SetNewAdminCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: SetNewAdminCpiAccounts<'a, 'b>, + args: SetNewAdminInstructionArgs, + ) -> Self { + Self { + __program: program, + config: accounts.config, + ncn: accounts.ncn, + ncn_admin: accounts.ncn_admin, + new_admin: accounts.new_admin, + restaking_program_id: accounts.restaking_program_id, + __args: args, + } + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], &[]) + } + #[inline(always)] + pub fn invoke_with_remaining_accounts( + &self, + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) + } + #[inline(always)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed_with_remaining_accounts( + &self, + signers_seeds: &[&[&[u8]]], + remaining_accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> solana_program::entrypoint::ProgramResult { + let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.config.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.ncn.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.ncn_admin.key, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.new_admin.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.restaking_program_id.key, + false, + )); + remaining_accounts.iter().for_each(|remaining_account| { + accounts.push(solana_program::instruction::AccountMeta { + pubkey: *remaining_account.0.key, + is_signer: remaining_account.1, + is_writable: remaining_account.2, + }) + }); + let mut data = SetNewAdminInstructionData::new().try_to_vec().unwrap(); + let mut args = self.__args.try_to_vec().unwrap(); + data.append(&mut args); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::JITO_TIP_ROUTER_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(5 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.config.clone()); + account_infos.push(self.ncn.clone()); + account_infos.push(self.ncn_admin.clone()); + account_infos.push(self.new_admin.clone()); + account_infos.push(self.restaking_program_id.clone()); + remaining_accounts + .iter() + .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); + + if signers_seeds.is_empty() { + solana_program::program::invoke(&instruction, &account_infos) + } else { + solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) + } + } +} + +/// Instruction builder for `SetNewAdmin` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` config +/// 1. `[]` ncn +/// 2. `[signer]` ncn_admin +/// 3. `[]` new_admin +/// 4. `[]` restaking_program_id +#[derive(Clone, Debug)] +pub struct SetNewAdminCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> SetNewAdminCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(SetNewAdminCpiBuilderInstruction { + __program: program, + config: None, + ncn: None, + ncn_admin: None, + new_admin: None, + restaking_program_id: None, + role: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + #[inline(always)] + pub fn config( + &mut self, + config: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.config = Some(config); + self + } + #[inline(always)] + pub fn ncn(&mut self, ncn: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.ncn = Some(ncn); + self + } + #[inline(always)] + pub fn ncn_admin( + &mut self, + ncn_admin: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.ncn_admin = Some(ncn_admin); + self + } + #[inline(always)] + pub fn new_admin( + &mut self, + new_admin: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.new_admin = Some(new_admin); + self + } + #[inline(always)] + pub fn restaking_program_id( + &mut self, + restaking_program_id: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.restaking_program_id = Some(restaking_program_id); + self + } + #[inline(always)] + pub fn role(&mut self, role: ConfigAdminRole) -> &mut Self { + self.instruction.role = Some(role); + self + } + /// Add an additional account to the instruction. + #[inline(always)] + pub fn add_remaining_account( + &mut self, + account: &'b solana_program::account_info::AccountInfo<'a>, + is_writable: bool, + is_signer: bool, + ) -> &mut Self { + self.instruction + .__remaining_accounts + .push((account, is_writable, is_signer)); + self + } + /// Add additional accounts to the instruction. + /// + /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, + /// and a `bool` indicating whether the account is a signer or not. + #[inline(always)] + pub fn add_remaining_accounts( + &mut self, + accounts: &[( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )], + ) -> &mut Self { + self.instruction + .__remaining_accounts + .extend_from_slice(accounts); + self + } + #[inline(always)] + pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { + self.invoke_signed(&[]) + } + #[allow(clippy::clone_on_copy)] + #[allow(clippy::vec_init_then_push)] + pub fn invoke_signed( + &self, + signers_seeds: &[&[&[u8]]], + ) -> solana_program::entrypoint::ProgramResult { + let args = SetNewAdminInstructionArgs { + role: self.instruction.role.clone().expect("role is not set"), + }; + let instruction = SetNewAdminCpi { + __program: self.instruction.__program, + + config: self.instruction.config.expect("config is not set"), + + ncn: self.instruction.ncn.expect("ncn is not set"), + + ncn_admin: self.instruction.ncn_admin.expect("ncn_admin is not set"), + + new_admin: self.instruction.new_admin.expect("new_admin is not set"), + + restaking_program_id: self + .instruction + .restaking_program_id + .expect("restaking_program_id is not set"), + __args: args, + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +#[derive(Clone, Debug)] +struct SetNewAdminCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + config: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ncn: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ncn_admin: Option<&'b solana_program::account_info::AccountInfo<'a>>, + new_admin: Option<&'b solana_program::account_info::AccountInfo<'a>>, + restaking_program_id: Option<&'b solana_program::account_info::AccountInfo<'a>>, + role: Option, + /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. + __remaining_accounts: Vec<( + &'b solana_program::account_info::AccountInfo<'a>, + bool, + bool, + )>, +} diff --git a/clients/rust/jito_tip_router/src/generated/instructions/update_weight_table.rs b/clients/rust/jito_tip_router/src/generated/instructions/update_weight_table.rs index 224b8ae..c10f0ba 100644 --- a/clients/rust/jito_tip_router/src/generated/instructions/update_weight_table.rs +++ b/clients/rust/jito_tip_router/src/generated/instructions/update_weight_table.rs @@ -68,7 +68,7 @@ pub struct UpdateWeightTableInstructionData { impl UpdateWeightTableInstructionData { pub fn new() -> Self { - Self { discriminator: 1 } + Self { discriminator: 2 } } } diff --git a/clients/rust/jito_tip_router/src/generated/types/config_admin_role.rs b/clients/rust/jito_tip_router/src/generated/types/config_admin_role.rs new file mode 100644 index 0000000..352c002 --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/types/config_admin_role.rs @@ -0,0 +1,26 @@ +//! 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 num_derive::FromPrimitive; + +#[derive( + BorshSerialize, + BorshDeserialize, + Clone, + Debug, + Eq, + PartialEq, + Copy, + PartialOrd, + Hash, + FromPrimitive, +)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum ConfigAdminRole { + FeeAdmin, + TieBreakerAdmin, +} diff --git a/clients/rust/jito_tip_router/src/generated/types/fee.rs b/clients/rust/jito_tip_router/src/generated/types/fee.rs new file mode 100644 index 0000000..e3ed1ce --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/types/fee.rs @@ -0,0 +1,22 @@ +//! 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 Fee { + #[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, +} diff --git a/clients/rust/jito_tip_router/src/generated/types/fees.rs b/clients/rust/jito_tip_router/src/generated/types/fees.rs new file mode 100644 index 0000000..ffd0b22 --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/types/fees.rs @@ -0,0 +1,16 @@ +//! 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 crate::generated::types::Fee; + +#[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, +} 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 f552bf2..ea35df8 100644 --- a/clients/rust/jito_tip_router/src/generated/types/mod.rs +++ b/clients/rust/jito_tip_router/src/generated/types/mod.rs @@ -4,6 +4,9 @@ //! //! +pub(crate) mod r#config_admin_role; +pub(crate) mod r#fee; +pub(crate) mod r#fees; pub(crate) mod r#weight_entry; -pub use self::r#weight_entry::*; +pub use self::{r#config_admin_role::*, r#fee::*, r#fees::*, r#weight_entry::*}; diff --git a/core/src/discriminators.rs b/core/src/discriminators.rs index 9eebb23..18f0d31 100644 --- a/core/src/discriminators.rs +++ b/core/src/discriminators.rs @@ -1,4 +1,5 @@ #[repr(u8)] pub enum Discriminators { - WeightTable = 1, + Config = 1, + WeightTable = 2, } diff --git a/core/src/error.rs b/core/src/error.rs index 1e35939..48e597c 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -16,6 +16,14 @@ pub enum TipRouterError { IncorrectWeightTableAdmin = 0x2200, #[error("Cannnot create future weight tables")] CannotCreateFutureWeightTables = 0x2201, + #[error("Fee cap exceeded")] + FeeCapExceeded = 0x2300, + #[error("Incorrect NCN Admin")] + IncorrectNcnAdmin = 0x2400, + #[error("Incorrect NCN")] + IncorrectNcn = 0x2401, + #[error("Incorrect fee admin")] + IncorrectFeeAdmin = 0x2402, } impl DecodeError for TipRouterError { diff --git a/core/src/fees.rs b/core/src/fees.rs new file mode 100644 index 0000000..3f820e1 --- /dev/null +++ b/core/src/fees.rs @@ -0,0 +1,266 @@ +use bytemuck::{Pod, Zeroable}; +use shank::ShankType; +use solana_program::pubkey::Pubkey; + +use crate::{error::TipRouterError, MAX_FEE_BPS}; + +/// Fee account. Allows for fee updates to take place in a future epoch without requiring an update. +/// This is important so all operators calculate the same Merkle root regardless of when fee changes take place. +#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod)] +#[repr(C)] +pub struct Fees { + fee_1: Fee, + fee_2: Fee, +} + +impl Fees { + pub const fn new( + wallet: Pubkey, + dao_fee_share_bps: u64, + ncn_fee_share_bps: u64, + block_engine_fee_bps: u64, + current_epoch: u64, + ) -> Self { + let fee = Fee::new( + wallet, + dao_fee_share_bps, + ncn_fee_share_bps, + block_engine_fee_bps, + current_epoch, + ); + Self { + fee_1: fee, + fee_2: fee, + } + } + + const fn current_fee(&self, current_epoch: u64) -> &Fee { + // If either fee is not yet active, return the other one + if self.fee_1.activation_epoch > current_epoch { + return &self.fee_2; + } + if self.fee_2.activation_epoch > current_epoch { + return &self.fee_1; + } + + // Otherwise return the one with higher activation epoch + if self.fee_1.activation_epoch >= self.fee_2.activation_epoch { + &self.fee_1 + } else { + &self.fee_2 + } + } + + pub const fn block_engine_fee(&self, current_epoch: u64) -> u64 { + self.current_fee(current_epoch).block_engine_fee_bps + } + + /// Calculate fee as a portion of remaining BPS after block engine fee + /// new_fee = dao_fee_bps / ((10000 - block_engine_fee_bps) / 10000) + /// = dao_fee_bps * 10000 / (10000 - block_engine_fee_bps) + pub fn dao_fee(&self, current_epoch: u64) -> Result { + let fee = self.current_fee(current_epoch); + let remaining_bps = MAX_FEE_BPS + .checked_sub(fee.block_engine_fee_bps) + .ok_or(TipRouterError::ArithmeticOverflow)?; + fee.dao_share_bps + .checked_mul(MAX_FEE_BPS) + .and_then(|x| x.checked_div(remaining_bps)) + .ok_or(TipRouterError::ArithmeticOverflow) + } + + /// Calculate fee as a portion of remaining BPS after block engine fee + /// new_fee = ncn_fee_bps / ((10000 - block_engine_fee_bps) / 10000) + /// = ncn_fee_bps * 10000 / (10000 - block_engine_fee_bps) + pub fn ncn_fee(&self, current_epoch: u64) -> Result { + let fee = self.current_fee(current_epoch); + let remaining_bps = MAX_FEE_BPS + .checked_sub(fee.block_engine_fee_bps) + .ok_or(TipRouterError::ArithmeticOverflow)?; + fee.ncn_share_bps + .checked_mul(MAX_FEE_BPS) + .and_then(|x| x.checked_div(remaining_bps)) + .ok_or(TipRouterError::ArithmeticOverflow) + } + + pub const fn fee_wallet(&self, current_epoch: u64) -> Pubkey { + self.current_fee(current_epoch).wallet + } + + fn get_updatable_fee_mut(&mut self, current_epoch: u64) -> &mut Fee { + // If either fee is scheduled for next epoch, return that one + if self.fee_1.activation_epoch > current_epoch { + return &mut self.fee_1; + } + if self.fee_2.activation_epoch > current_epoch { + return &mut self.fee_2; + } + + // Otherwise return the one with lower activation epoch + if self.fee_1.activation_epoch <= self.fee_2.activation_epoch { + &mut self.fee_1 + } else { + &mut self.fee_2 + } + } + + pub fn set_new_fees( + &mut self, + new_dao_fee_bps: Option, + new_ncn_fee_bps: Option, + new_block_engine_fee_bps: Option, + new_wallet: Option, + current_epoch: u64, + ) -> Result<(), TipRouterError> { + let current_fees = *self.current_fee(current_epoch); + let new_fees = self.get_updatable_fee_mut(current_epoch); + *new_fees = current_fees; + + if let Some(new_dao_fee_bps) = new_dao_fee_bps { + if new_dao_fee_bps > MAX_FEE_BPS { + return Err(TipRouterError::FeeCapExceeded); + } + new_fees.dao_share_bps = new_dao_fee_bps; + } + if let Some(new_ncn_fee_bps) = new_ncn_fee_bps { + if new_ncn_fee_bps > MAX_FEE_BPS { + return Err(TipRouterError::FeeCapExceeded); + } + new_fees.ncn_share_bps = new_ncn_fee_bps; + } + if let Some(new_block_engine_fee_bps) = new_block_engine_fee_bps { + if new_block_engine_fee_bps > MAX_FEE_BPS { + return Err(TipRouterError::FeeCapExceeded); + } + new_fees.block_engine_fee_bps = new_block_engine_fee_bps; + } + if let Some(new_wallet) = new_wallet { + new_fees.wallet = new_wallet; + } + new_fees.activation_epoch = current_epoch + .checked_add(1) + .ok_or(TipRouterError::ArithmeticOverflow)?; + Ok(()) + } +} + +#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod)] +#[repr(C)] +pub struct Fee { + wallet: Pubkey, + dao_share_bps: u64, + ncn_share_bps: u64, + block_engine_fee_bps: u64, + activation_epoch: u64, +} + +impl Fee { + pub const fn new( + wallet: Pubkey, + dao_share_bps: u64, + ncn_share_bps: u64, + block_engine_fee_bps: u64, + epoch: u64, + ) -> Self { + Self { + wallet, + dao_share_bps, + ncn_share_bps, + block_engine_fee_bps, + activation_epoch: epoch, + } + } +} + +#[cfg(test)] +mod tests { + use solana_program::pubkey::Pubkey; + + use super::*; + + #[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.fee_1.wallet, new_wallet); + assert_eq!(fees.fee_1.activation_epoch, 11); + } + + #[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.clone(); + + 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); + } + + #[test] + fn test_update_fees_errors() { + let mut fees = Fees::new(Pubkey::new_unique(), 100, 200, 300, 5); + + assert!(matches!( + 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!(matches!( + fees.set_new_fees(None, None, None, None, u64::MAX), + Err(TipRouterError::ArithmeticOverflow) + )); + } + + #[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.activation_epoch = 10; + + assert_eq!(fees.current_fee(5).activation_epoch, 5); + assert_eq!(fees.current_fee(10).activation_epoch, 10); + + fees.fee_2.activation_epoch = 15; + + assert_eq!(fees.current_fee(12).activation_epoch, 10); + assert_eq!(fees.current_fee(15).activation_epoch, 15); + } + + #[test] + fn test_get_updatable_fee_mut() { + let mut fees = Fees::new(Pubkey::new_unique(), 100, 200, 300, 5); + + let fee = fees.get_updatable_fee_mut(10); + fee.dao_share_bps = 400; + fee.activation_epoch = 11; + + assert_eq!(fees.fee_1.dao_share_bps, 400); + assert_eq!(fees.fee_1.activation_epoch, 11); + + fees.fee_2.activation_epoch = 13; + + let fee = fees.get_updatable_fee_mut(12); + fee.dao_share_bps = 500; + fee.activation_epoch = 13; + + 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 4280e99..3ffa68d 100644 --- a/core/src/instruction.rs +++ b/core/src/instruction.rs @@ -1,11 +1,33 @@ use borsh::{BorshDeserialize, BorshSerialize}; use shank::ShankInstruction; +use solana_program::pubkey::Pubkey; + +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub enum ConfigAdminRole { + FeeAdmin, + TieBreakerAdmin, +} #[rustfmt::skip] #[derive(Debug, BorshSerialize, BorshDeserialize, ShankInstruction)] pub enum WeightTableInstruction { - /// Initializes global configuration + + /// Initialize the global configuration for this NCN + #[account(0, writable, name = "config")] + #[account(1, name = "ncn")] + #[account(2, signer, name = "ncn_admin")] + #[account(3, name = "fee_wallet")] + #[account(4, name = "tie_breaker_admin")] + #[account(5, name = "restaking_program_id")] + #[account(6, name = "system_program")] + InitializeConfig { + dao_fee_bps: u64, + ncn_fee_bps: u64, + block_engine_fee_bps: u64, + }, + + /// Initializes the weight table for a given NCN epoch #[account(0, name = "restaking_config")] #[account(1, name = "ncn")] #[account(2, writable, signer, name = "weight_table")] @@ -35,4 +57,25 @@ pub enum WeightTableInstruction { ncn_epoch: u64, }, + /// Updates the fee configuration + #[account(0, writable, name = "config")] + #[account(1, name = "ncn")] + #[account(2, signer, name = "ncn_admin")] + #[account(3, name = "restaking_program_id")] + SetConfigFees { + new_dao_fee_bps: Option, + new_ncn_fee_bps: Option, + new_block_engine_fee_bps: Option, + new_fee_wallet: Option, + }, + + /// Sets a new secondary admin for the NCN + #[account(0, writable, name = "config")] + #[account(1, name = "ncn")] + #[account(2, signer, name = "ncn_admin")] + #[account(3, name = "new_admin")] + #[account(4, name = "restaking_program_id")] + SetNewAdmin { + role: ConfigAdminRole, + }, } diff --git a/core/src/lib.rs b/core/src/lib.rs index a568233..18715e5 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -2,4 +2,8 @@ pub mod discriminators; pub mod error; pub mod instruction; // pub mod depreciated_weight; +pub mod fees; +pub mod ncn_config; pub mod weight_table; + +pub const MAX_FEE_BPS: u64 = 10_000; diff --git a/core/src/ncn_config.rs b/core/src/ncn_config.rs new file mode 100644 index 0000000..d68a0fa --- /dev/null +++ b/core/src/ncn_config.rs @@ -0,0 +1,113 @@ +// Global configuration for the tip router + +// Contains: +// Main NCN address - updatable? +// Admins +// - config admin - should this be here? or just use main NCN admin? +// - Weight table upload admin (hot wallet) (this exists in NCN, do we want it here too? since custom weight table) +// - Tie breaker admin (hot wallet) (depending on tie breaker process?) +// DAO fee share +// NCN fee share + +use bytemuck::{Pod, Zeroable}; +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}; + +#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod, AccountDeserialize, ShankAccount)] +#[repr(C)] +pub struct NcnConfig { + /// The Restaking program's NCN admin is the signer to create and update this account + pub ncn: Pubkey, + + pub tie_breaker_admin: Pubkey, + + pub fee_admin: Pubkey, + + pub fees: Fees, + + /// Bump seed for the PDA + pub bump: u8, + + /// Reserved space + reserved: [u8; 127], +} + +impl Discriminator for NcnConfig { + const DISCRIMINATOR: u8 = Discriminators::Config as u8; +} + +impl NcnConfig { + pub const fn new( + ncn: Pubkey, + tie_breaker_admin: Pubkey, + fee_admin: Pubkey, + fees: Fees, + ) -> Self { + Self { + ncn, + tie_breaker_admin, + fee_admin, + fees, + bump: 0, + reserved: [0; 127], + } + } + + pub fn seeds(ncn: &Pubkey) -> Vec> { + vec![b"config".to_vec(), ncn.to_bytes().to_vec()] + } + + pub fn find_program_address(program_id: &Pubkey, ncn: &Pubkey) -> (Pubkey, u8, Vec>) { + let seeds = vec![b"config".to_vec(), ncn.to_bytes().to_vec()]; + let (address, bump) = Pubkey::find_program_address( + &seeds.iter().map(|s| s.as_slice()).collect::>(), + program_id, + ); + (address, bump, seeds) + } + + /// Loads the NCN [`Config`] account + /// + /// # Arguments + /// * `program_id` - The program ID + /// * `ncn` - The NCN pubkey + /// * `account` - The account to load + /// * `expect_writable` - Whether the account should be writable + /// + /// # Returns + /// * `Result<(), ProgramError>` - The result of the operation + pub fn load( + program_id: &Pubkey, + ncn: &Pubkey, + account: &AccountInfo, + expect_writable: bool, + ) -> Result<(), ProgramError> { + if account.owner.ne(program_id) { + msg!("Config account has an invalid owner"); + return Err(ProgramError::InvalidAccountOwner); + } + if account.data_is_empty() { + msg!("Config account data is empty"); + return Err(ProgramError::InvalidAccountData); + } + if expect_writable && !account.is_writable { + msg!("Config account is not writable"); + return Err(ProgramError::InvalidAccountData); + } + if account.data.borrow()[0].ne(&Self::DISCRIMINATOR) { + msg!("Config account discriminator is invalid"); + return Err(ProgramError::InvalidAccountData); + } + if account + .key + .ne(&Self::find_program_address(program_id, ncn).0) + { + msg!("Config account is not at the correct PDA"); + return Err(ProgramError::InvalidAccountData); + } + Ok(()) + } +} diff --git a/idl/jito_mev_tip_distribution_ncn.json b/idl/jito_mev_tip_distribution_ncn.json deleted file mode 100644 index 3c9eabf..0000000 --- a/idl/jito_mev_tip_distribution_ncn.json +++ /dev/null @@ -1,243 +0,0 @@ -{ - "version": "0.0.1", - "name": "jito_tip_router", - "instructions": [ - { - "name": "InitializeWeightTable", - "accounts": [ - { - "name": "restakingConfig", - "isMut": false, - "isSigner": false - }, - { - "name": "ncn", - "isMut": false, - "isSigner": false - }, - { - "name": "weightTable", - "isMut": true, - "isSigner": true - }, - { - "name": "weightTableAdmin", - "isMut": true, - "isSigner": true - }, - { - "name": "restakingProgramId", - "isMut": false, - "isSigner": false - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false - } - ], - "args": [ - { - "name": "firstSlotOfNcnEpoch", - "type": { - "option": "u64" - } - } - ], - "discriminant": { - "type": "u8", - "value": 0 - } - }, - { - "name": "UpdateWeightTable", - "accounts": [ - { - "name": "ncn", - "isMut": false, - "isSigner": false - }, - { - "name": "weightTable", - "isMut": true, - "isSigner": false - }, - { - "name": "weightTableAdmin", - "isMut": false, - "isSigner": true - }, - { - "name": "restakingProgramId", - "isMut": false, - "isSigner": false - } - ], - "args": [ - { - "name": "ncnEpoch", - "type": "u64" - }, - { - "name": "weightNumerator", - "type": "u64" - }, - { - "name": "weightDenominator", - "type": "u64" - } - ], - "discriminant": { - "type": "u8", - "value": 1 - } - }, - { - "name": "FinalizeWeightTable", - "accounts": [ - { - "name": "ncn", - "isMut": false, - "isSigner": false - }, - { - "name": "weightTable", - "isMut": true, - "isSigner": false - }, - { - "name": "weightTableAdmin", - "isMut": false, - "isSigner": true - }, - { - "name": "restakingProgramId", - "isMut": false, - "isSigner": false - } - ], - "args": [ - { - "name": "ncnEpoch", - "type": "u64" - } - ], - "discriminant": { - "type": "u8", - "value": 2 - } - } - ], - "accounts": [ - { - "name": "WeightTable", - "type": { - "kind": "struct", - "fields": [ - { - "name": "ncn", - "type": "publicKey" - }, - { - "name": "ncnEpoch", - "type": { - "defined": "PodU64" - } - }, - { - "name": "slotCreated", - "type": { - "defined": "PodU64" - } - }, - { - "name": "slotFinalized", - "type": { - "defined": "PodU64" - } - }, - { - "name": "bump", - "type": "u8" - }, - { - "name": "reserved", - "type": { - "array": [ - "u8", - 128 - ] - } - }, - { - "name": "table", - "type": { - "array": [ - { - "defined": "WeightEntry" - }, - 32 - ] - } - } - ] - } - } - ], - "types": [ - { - "name": "WeightEntry", - "type": { - "kind": "struct", - "fields": [ - { - "name": "mint", - "type": "publicKey" - }, - { - "name": "weight", - "type": { - "defined": "PodU64" - } - } - ] - } - } - ], - "errors": [ - { - "code": 8192, - "name": "NoMoreTableSlots", - "msg": "No more table slots available" - }, - { - "code": 8448, - "name": "DenominatorIsZero", - "msg": "Zero in the denominator" - }, - { - "code": 8449, - "name": "ArithmeticOverflow", - "msg": "Overflow" - }, - { - "code": 8450, - "name": "ModuloOverflow", - "msg": "Modulo Overflow" - }, - { - "code": 8704, - "name": "IncorrectWeightTableAdmin", - "msg": "Incorrect weight table admin" - }, - { - "code": 8705, - "name": "CannotCreateFutureWeightTables", - "msg": "Cannnot create future weight tables" - } - ], - "metadata": { - "origin": "shank", - "address": "Fv9aHCgvPQSr4jg9W8eTS6Ys1SNmh2qjyATrbsjEMaSH" - } -} \ No newline at end of file diff --git a/idl/jito_tip_router.json b/idl/jito_tip_router.json index 3c9eabf..4078fd7 100644 --- a/idl/jito_tip_router.json +++ b/idl/jito_tip_router.json @@ -2,6 +2,64 @@ "version": "0.0.1", "name": "jito_tip_router", "instructions": [ + { + "name": "InitializeConfig", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "ncn", + "isMut": false, + "isSigner": false + }, + { + "name": "ncnAdmin", + "isMut": false, + "isSigner": true + }, + { + "name": "feeWallet", + "isMut": false, + "isSigner": false + }, + { + "name": "tieBreakerAdmin", + "isMut": false, + "isSigner": false + }, + { + "name": "restakingProgramId", + "isMut": false, + "isSigner": false + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "daoFeeBps", + "type": "u64" + }, + { + "name": "ncnFeeBps", + "type": "u64" + }, + { + "name": "blockEngineFeeBps", + "type": "u64" + } + ], + "discriminant": { + "type": "u8", + "value": 0 + } + }, { "name": "InitializeWeightTable", "accounts": [ @@ -46,7 +104,7 @@ ], "discriminant": { "type": "u8", - "value": 0 + "value": 1 } }, { @@ -89,7 +147,7 @@ ], "discriminant": { "type": "u8", - "value": 1 + "value": 2 } }, { @@ -124,11 +182,147 @@ ], "discriminant": { "type": "u8", - "value": 2 + "value": 3 + } + }, + { + "name": "SetConfigFees", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "ncn", + "isMut": false, + "isSigner": false + }, + { + "name": "ncnAdmin", + "isMut": false, + "isSigner": true + }, + { + "name": "restakingProgramId", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "newDaoFeeBps", + "type": { + "option": "u64" + } + }, + { + "name": "newNcnFeeBps", + "type": { + "option": "u64" + } + }, + { + "name": "newBlockEngineFeeBps", + "type": { + "option": "u64" + } + }, + { + "name": "newFeeWallet", + "type": { + "option": "publicKey" + } + } + ], + "discriminant": { + "type": "u8", + "value": 4 + } + }, + { + "name": "SetNewAdmin", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "ncn", + "isMut": false, + "isSigner": false + }, + { + "name": "ncnAdmin", + "isMut": false, + "isSigner": true + }, + { + "name": "newAdmin", + "isMut": false, + "isSigner": false + }, + { + "name": "restakingProgramId", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "role", + "type": { + "defined": "ConfigAdminRole" + } + } + ], + "discriminant": { + "type": "u8", + "value": 5 } } ], "accounts": [ + { + "name": "NcnConfig", + "type": { + "kind": "struct", + "fields": [ + { + "name": "ncn", + "type": "publicKey" + }, + { + "name": "tieBreakerAdmin", + "type": "publicKey" + }, + { + "name": "feeAdmin", + "type": "publicKey" + }, + { + "name": "fees", + "type": { + "defined": "Fees" + } + }, + { + "name": "bump", + "type": "u8" + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 127 + ] + } + } + ] + } + }, { "name": "WeightTable", "type": { @@ -185,6 +379,54 @@ } ], "types": [ + { + "name": "Fees", + "type": { + "kind": "struct", + "fields": [ + { + "name": "fee1", + "type": { + "defined": "Fee" + } + }, + { + "name": "fee2", + "type": { + "defined": "Fee" + } + } + ] + } + }, + { + "name": "Fee", + "type": { + "kind": "struct", + "fields": [ + { + "name": "wallet", + "type": "publicKey" + }, + { + "name": "daoShareBps", + "type": "u64" + }, + { + "name": "ncnShareBps", + "type": "u64" + }, + { + "name": "blockEngineFeeBps", + "type": "u64" + }, + { + "name": "activationEpoch", + "type": "u64" + } + ] + } + }, { "name": "WeightEntry", "type": { @@ -202,6 +444,20 @@ } ] } + }, + { + "name": "ConfigAdminRole", + "type": { + "kind": "enum", + "variants": [ + { + "name": "FeeAdmin" + }, + { + "name": "TieBreakerAdmin" + } + ] + } } ], "errors": [ @@ -234,6 +490,26 @@ "code": 8705, "name": "CannotCreateFutureWeightTables", "msg": "Cannnot create future weight tables" + }, + { + "code": 8960, + "name": "FeeCapExceeded", + "msg": "Fee cap exceeded" + }, + { + "code": 9216, + "name": "IncorrectNcnAdmin", + "msg": "Incorrect NCN Admin" + }, + { + "code": 9217, + "name": "IncorrectNcn", + "msg": "Incorrect NCN" + }, + { + "code": 9218, + "name": "IncorrectFeeAdmin", + "msg": "Incorrect fee admin" } ], "metadata": { diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 699b299..4417d23 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -16,6 +16,8 @@ jito-jsm-core = { workspace = true } jito-restaking-core = { workspace = true } jito-restaking-program = { workspace = true } jito-restaking-sdk = { workspace = true } +jito-tip-router-core = { workspace = true } +jito-tip-router-program = { workspace = true } jito-vault-core = { workspace = true } jito-vault-program = { workspace = true } jito-vault-sdk = { workspace = true } diff --git a/integration_tests/tests/fixtures/jito_restaking_program.so b/integration_tests/tests/fixtures/jito_restaking_program.so new file mode 100644 index 0000000..10e16e8 Binary files /dev/null and b/integration_tests/tests/fixtures/jito_restaking_program.so differ diff --git a/integration_tests/tests/fixtures/jito_vault_program.so b/integration_tests/tests/fixtures/jito_vault_program.so new file mode 100644 index 0000000..82ad982 Binary files /dev/null and b/integration_tests/tests/fixtures/jito_vault_program.so differ diff --git a/integration_tests/tests/fixtures/mod.rs b/integration_tests/tests/fixtures/mod.rs new file mode 100644 index 0000000..c15b19b --- /dev/null +++ b/integration_tests/tests/fixtures/mod.rs @@ -0,0 +1,41 @@ +use solana_program::{instruction::InstructionError, program_error::ProgramError}; +use solana_program_test::BanksClientError; +use solana_sdk::transaction::TransactionError; +use thiserror::Error; + +pub mod restaking_client; +pub mod test_builder; +pub mod tip_router_client; + +pub type TestResult = Result; + +#[derive(Error, Debug)] +pub enum TestError { + #[error(transparent)] + BanksClientError(#[from] BanksClientError), + #[error(transparent)] + ProgramError(#[from] ProgramError), +} + +impl TestError { + pub fn to_transaction_error(&self) -> Option { + match self { + TestError::BanksClientError(e) => match e { + BanksClientError::TransactionError(e) => Some(e.clone()), + BanksClientError::SimulationError { err, .. } => Some(err.clone()), + _ => None, + }, + TestError::ProgramError(_) => None, + } + } +} + +#[inline(always)] +#[track_caller] +pub fn assert_ix_error(test_error: Result, ix_error: InstructionError) { + assert!(test_error.is_err()); + assert_eq!( + test_error.err().unwrap().to_transaction_error().unwrap(), + TransactionError::InstructionError(0, ix_error) + ); +} diff --git a/integration_tests/tests/fixtures/restaking_client.rs b/integration_tests/tests/fixtures/restaking_client.rs new file mode 100644 index 0000000..9128bf8 --- /dev/null +++ b/integration_tests/tests/fixtures/restaking_client.rs @@ -0,0 +1,132 @@ +use jito_restaking_core::{config::Config, ncn::Ncn}; +use jito_restaking_sdk::sdk::{initialize_config, initialize_ncn}; +use solana_program_test::BanksClient; +use solana_sdk::{ + commitment_config::CommitmentLevel, native_token::sol_to_lamports, pubkey::Pubkey, + signature::Keypair, signer::Signer, system_instruction::transfer, transaction::Transaction, +}; + +use super::TestResult; + +#[derive(Debug)] +pub struct NcnRoot { + pub ncn_pubkey: Pubkey, + pub ncn_admin: Keypair, +} + +pub struct RestakingProgramClient { + banks_client: BanksClient, + payer: Keypair, +} + +impl RestakingProgramClient { + pub const fn new(banks_client: BanksClient, payer: Keypair) -> Self { + Self { + banks_client, + payer, + } + } + + pub async fn process_transaction(&mut self, tx: &Transaction) -> TestResult<()> { + self.banks_client + .process_transaction_with_preflight_and_commitment( + tx.clone(), + CommitmentLevel::Processed, + ) + .await?; + Ok(()) + } + + pub async fn airdrop(&mut self, to: &Pubkey, sol: f64) -> TestResult<()> { + let blockhash = self.banks_client.get_latest_blockhash().await?; + self.banks_client + .process_transaction_with_preflight_and_commitment( + Transaction::new_signed_with_payer( + &[transfer(&self.payer.pubkey(), to, sol_to_lamports(sol))], + Some(&self.payer.pubkey()), + &[&self.payer], + blockhash, + ), + CommitmentLevel::Processed, + ) + .await?; + Ok(()) + } + + pub async fn do_initialize_config(&mut self) -> TestResult { + let restaking_config_pubkey = Config::find_program_address(&jito_restaking_program::id()).0; + let restaking_config_admin = Keypair::new(); + + self.airdrop(&restaking_config_admin.pubkey(), 10.0).await?; + self.initialize_config(&restaking_config_pubkey, &restaking_config_admin) + .await?; + + Ok(restaking_config_admin) + } + + pub async fn initialize_config( + &mut self, + config: &Pubkey, + config_admin: &Keypair, + ) -> TestResult<()> { + let blockhash = self.banks_client.get_latest_blockhash().await?; + self.process_transaction(&Transaction::new_signed_with_payer( + &[initialize_config( + &jito_restaking_program::id(), + config, + &config_admin.pubkey(), + &jito_vault_program::id(), + )], + Some(&config_admin.pubkey()), + &[config_admin], + blockhash, + )) + .await + } + + pub async fn do_initialize_ncn(&mut self) -> TestResult { + let ncn_admin = Keypair::new(); + let ncn_base = Keypair::new(); + + self.airdrop(&ncn_admin.pubkey(), 1.0).await?; + + let ncn_pubkey = + Ncn::find_program_address(&jito_restaking_program::id(), &ncn_base.pubkey()).0; + self.initialize_ncn( + &Config::find_program_address(&jito_restaking_program::id()).0, + &ncn_pubkey, + &ncn_admin, + &ncn_base, + ) + .await?; + + Ok(NcnRoot { + ncn_pubkey, + ncn_admin, + }) + } + + pub async fn initialize_ncn( + &mut self, + config: &Pubkey, + ncn: &Pubkey, + ncn_admin: &Keypair, + ncn_base: &Keypair, + ) -> TestResult<()> { + let blockhash = self.banks_client.get_latest_blockhash().await?; + + self.process_transaction(&Transaction::new_signed_with_payer( + &[initialize_ncn( + &jito_restaking_program::id(), + config, + ncn, + &ncn_admin.pubkey(), + &ncn_base.pubkey(), + )], + Some(&ncn_admin.pubkey()), + &[&ncn_admin, &ncn_base], + blockhash, + )) + .await + } +} diff --git a/integration_tests/tests/fixtures/test_builder.rs b/integration_tests/tests/fixtures/test_builder.rs new file mode 100644 index 0000000..74545ef --- /dev/null +++ b/integration_tests/tests/fixtures/test_builder.rs @@ -0,0 +1,85 @@ +use std::fmt::{Debug, Formatter}; + +use solana_program::clock::Clock; +use solana_program_test::{processor, BanksClientError, ProgramTest, ProgramTestContext}; + +use super::{ + restaking_client::{NcnRoot, RestakingProgramClient}, + tip_router_client::TipRouterClient, + TestResult, +}; + +pub struct TestBuilder { + context: ProgramTestContext, +} + +impl Debug for TestBuilder { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "TestBuilder",) + } +} + +impl TestBuilder { + pub async fn new() -> Self { + let mut program_test = ProgramTest::new( + "jito_tip_router_program", + jito_tip_router_program::id(), + processor!(jito_tip_router_program::process_instruction), + ); + program_test.add_program( + "jito_vault_program", + jito_vault_program::id(), + processor!(jito_vault_program::process_instruction), + ); + program_test.add_program( + "jito_restaking_program", + jito_restaking_program::id(), + processor!(jito_restaking_program::process_instruction), + ); + program_test.prefer_bpf(true); + + Self { + context: program_test.start_with_context().await, + } + } + + pub async fn warp_slot_incremental( + &mut self, + incremental_slots: u64, + ) -> Result<(), BanksClientError> { + let clock: Clock = self.context.banks_client.get_sysvar().await?; + self.context + .warp_to_slot(clock.slot.checked_add(incremental_slots).unwrap()) + .map_err(|_| BanksClientError::ClientError("failed to warp slot"))?; + Ok(()) + } + + pub async fn clock(&mut self) -> Clock { + self.context.banks_client.get_sysvar().await.unwrap() + } + + pub fn tip_router_client(&self) -> TipRouterClient { + TipRouterClient::new( + self.context.banks_client.clone(), + self.context.payer.insecure_clone(), + ) + } + + pub fn restaking_program_client(&self) -> RestakingProgramClient { + RestakingProgramClient::new( + self.context.banks_client.clone(), + self.context.payer.insecure_clone(), + ) + } + + pub async fn setup_ncn(&mut self) -> TestResult { + let mut restaking_program_client = self.restaking_program_client(); + + restaking_program_client.do_initialize_config().await?; + let ncn_root = restaking_program_client.do_initialize_ncn().await?; + + Ok(ncn_root) + } + + // Extend this to setup operators, vaults and relationships as neede +} diff --git a/integration_tests/tests/fixtures/tip_router_client.rs b/integration_tests/tests/fixtures/tip_router_client.rs new file mode 100644 index 0000000..3a53ba8 --- /dev/null +++ b/integration_tests/tests/fixtures/tip_router_client.rs @@ -0,0 +1,216 @@ +use jito_bytemuck::AccountDeserialize; +use jito_tip_router_client::{ + instructions::{InitializeConfigBuilder, SetConfigFeesBuilder, SetNewAdminBuilder}, + types::ConfigAdminRole, +}; +use jito_tip_router_core::{error::TipRouterError, ncn_config::NcnConfig}; +use solana_program::{ + instruction::InstructionError, native_token::sol_to_lamports, pubkey::Pubkey, + system_instruction::transfer, +}; +use solana_program_test::BanksClient; +use solana_sdk::{ + commitment_config::CommitmentLevel, + signature::{Keypair, Signer}, + transaction::{Transaction, TransactionError}, +}; + +use super::restaking_client::NcnRoot; +use crate::fixtures::{TestError, TestResult}; + +pub struct TipRouterClient { + banks_client: BanksClient, + payer: Keypair, +} + +impl TipRouterClient { + pub const fn new(banks_client: BanksClient, payer: Keypair) -> Self { + Self { + banks_client, + payer, + } + } + + pub async fn process_transaction(&mut self, tx: &Transaction) -> TestResult<()> { + self.banks_client + .process_transaction_with_preflight_and_commitment( + tx.clone(), + CommitmentLevel::Processed, + ) + .await?; + Ok(()) + } + + pub async fn airdrop(&mut self, to: &Pubkey, sol: f64) -> TestResult<()> { + let blockhash = self.banks_client.get_latest_blockhash().await?; + self.banks_client + .process_transaction_with_preflight_and_commitment( + Transaction::new_signed_with_payer( + &[transfer(&self.payer.pubkey(), to, sol_to_lamports(sol))], + Some(&self.payer.pubkey()), + &[&self.payer], + blockhash, + ), + CommitmentLevel::Processed, + ) + .await?; + Ok(()) + } + + pub async fn get_config(&mut self, ncn_pubkey: Pubkey) -> TestResult { + let config_pda = + NcnConfig::find_program_address(&jito_tip_router_program::id(), &ncn_pubkey).0; + let config = self.banks_client.get_account(config_pda).await?.unwrap(); + Ok(*NcnConfig::try_from_slice_unchecked(config.data.as_slice()).unwrap()) + } + + pub async fn do_initialize_config( + &mut self, + ncn: Pubkey, + ncn_admin: &Keypair, + ) -> TestResult<()> { + self.airdrop(&self.payer.pubkey(), 1.0).await?; + + let ncn_admin_pubkey = ncn_admin.pubkey(); + self.initialize_config(ncn, ncn_admin, ncn_admin_pubkey, ncn_admin_pubkey, 0, 0, 0) + .await + } + + pub async fn initialize_config( + &mut self, + ncn: Pubkey, + ncn_admin: &Keypair, + fee_wallet: Pubkey, + tie_breaker_admin: Pubkey, + dao_fee_bps: u64, + ncn_fee_bps: u64, + block_engine_fee_bps: u64, + ) -> TestResult<()> { + let config_pda = NcnConfig::find_program_address(&jito_tip_router_program::id(), &ncn).0; + + let ix = InitializeConfigBuilder::new() + .config(config_pda) + .ncn(ncn) + .ncn_admin(ncn_admin.pubkey()) + .fee_wallet(fee_wallet) + .tie_breaker_admin(tie_breaker_admin) + .restaking_program_id(jito_restaking_program::id()) + .dao_fee_bps(dao_fee_bps) + .ncn_fee_bps(ncn_fee_bps) + .block_engine_fee_bps(block_engine_fee_bps) + .instruction(); + + let blockhash = self.banks_client.get_latest_blockhash().await?; + self.process_transaction(&Transaction::new_signed_with_payer( + &[ix], + Some(&ncn_admin.pubkey()), + &[&ncn_admin], + blockhash, + )) + .await + } + + pub async fn do_set_config_fees( + &mut self, + dao_fee_bps: u64, + ncn_fee_bps: u64, + block_engine_fee_bps: u64, + fee_wallet: Pubkey, + ncn_root: &NcnRoot, + ) -> TestResult<()> { + let config_pda = + NcnConfig::find_program_address(&jito_tip_router_program::id(), &ncn_root.ncn_pubkey).0; + self.airdrop(&ncn_root.ncn_admin.pubkey(), 1.0).await?; + self.set_config_fees( + config_pda, + dao_fee_bps, + ncn_fee_bps, + block_engine_fee_bps, + fee_wallet, + &ncn_root, + ) + .await + } + + pub async fn set_config_fees( + &mut self, + config_pda: Pubkey, + dao_fee_bps: u64, + ncn_fee_bps: u64, + block_engine_fee_bps: u64, + fee_wallet: Pubkey, + ncn_root: &NcnRoot, + ) -> TestResult<()> { + let ix = SetConfigFeesBuilder::new() + .config(config_pda) + .ncn(ncn_root.ncn_pubkey) + .ncn_admin(ncn_root.ncn_admin.pubkey()) + .restaking_program_id(jito_restaking_program::id()) + .new_dao_fee_bps(dao_fee_bps) + .new_ncn_fee_bps(ncn_fee_bps) + .new_block_engine_fee_bps(block_engine_fee_bps) + .new_fee_wallet(fee_wallet) + .instruction(); + + let blockhash = self.banks_client.get_latest_blockhash().await?; + self.process_transaction(&Transaction::new_signed_with_payer( + &[ix], + Some(&ncn_root.ncn_admin.pubkey()), + &[&ncn_root.ncn_admin], + blockhash, + )) + .await + } + + pub async fn do_set_new_admin( + &mut self, + role: ConfigAdminRole, + new_admin: Pubkey, + ncn_root: &NcnRoot, + ) -> TestResult<()> { + let config_pda = + NcnConfig::find_program_address(&jito_tip_router_program::id(), &ncn_root.ncn_pubkey).0; + self.airdrop(&ncn_root.ncn_admin.pubkey(), 1.0).await?; + self.set_new_admin(config_pda, role, new_admin, ncn_root) + .await + } + + pub async fn set_new_admin( + &mut self, + config_pda: Pubkey, + role: ConfigAdminRole, + new_admin: Pubkey, + ncn_root: &NcnRoot, + ) -> TestResult<()> { + let ix = SetNewAdminBuilder::new() + .config(config_pda) + .ncn(ncn_root.ncn_pubkey) + .ncn_admin(ncn_root.ncn_admin.pubkey()) + .new_admin(new_admin) + .restaking_program_id(jito_restaking_program::id()) + .role(role) + .instruction(); + + let blockhash = self.banks_client.get_latest_blockhash().await?; + self.process_transaction(&Transaction::new_signed_with_payer( + &[ix], + Some(&ncn_root.ncn_admin.pubkey()), + &[&ncn_root.ncn_admin], + blockhash, + )) + .await + } +} + +#[inline(always)] +#[track_caller] +pub fn assert_tip_router_error( + test_error: Result, + tip_router_error: TipRouterError, +) { + assert!(test_error.is_err()); + assert_eq!( + test_error.err().unwrap().to_transaction_error().unwrap(), + TransactionError::InstructionError(0, InstructionError::Custom(tip_router_error as u32)) + ); +} diff --git a/integration_tests/tests/fixtures/vault_client.rs b/integration_tests/tests/fixtures/vault_client.rs new file mode 100644 index 0000000..06a9639 --- /dev/null +++ b/integration_tests/tests/fixtures/vault_client.rs @@ -0,0 +1,35 @@ +use std::{fmt, fmt::Debug}; + +use solana_program::pubkey::Pubkey; +use solana_program_test::BanksClient; +use solana_sdk::signature::Keypair; + +pub struct VaultRoot { + pub vault_pubkey: Pubkey, + pub vault_admin: Keypair, +} + +impl Debug for VaultRoot { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "VaultRoot {{ vault_pubkey: {}, vault_admin: {:?} }}", + self.vault_pubkey, self.vault_admin + ) + } +} + +#[allow(dead_code)] +pub struct VaultProgramClient { + banks_client: BanksClient, + payer: Keypair, +} + +impl VaultProgramClient { + pub const fn new(banks_client: BanksClient, payer: Keypair) -> Self { + Self { + banks_client, + payer, + } + } +} diff --git a/integration_tests/tests/tests.rs b/integration_tests/tests/tests.rs new file mode 100644 index 0000000..e761317 --- /dev/null +++ b/integration_tests/tests/tests.rs @@ -0,0 +1,2 @@ +mod fixtures; +mod tip_router; diff --git a/integration_tests/tests/tip_router/initialize_ncn_config.rs b/integration_tests/tests/tip_router/initialize_ncn_config.rs new file mode 100644 index 0000000..b7d72c8 --- /dev/null +++ b/integration_tests/tests/tip_router/initialize_ncn_config.rs @@ -0,0 +1,80 @@ +#[cfg(test)] +mod tests { + use jito_tip_router_core::error::TipRouterError; + use solana_program::instruction::InstructionError; + use solana_sdk::signature::{Keypair, Signer}; + + use crate::fixtures::{ + assert_ix_error, restaking_client::NcnRoot, test_builder::TestBuilder, + tip_router_client::assert_tip_router_error, TestResult, + }; + + #[tokio::test] + async fn test_initialize_ncn_config_ok() -> TestResult<()> { + let mut fixture = TestBuilder::new().await; + let mut tip_router_client = fixture.tip_router_client(); + let ncn_root = fixture.setup_ncn().await?; + tip_router_client + .do_initialize_config(ncn_root.ncn_pubkey, &ncn_root.ncn_admin) + .await?; + Ok(()) + } + + #[tokio::test] + async fn test_initialize_ncn_config_double_init_fails() -> TestResult<()> { + let mut fixture = TestBuilder::new().await; + let mut tip_router_client = fixture.tip_router_client(); + let ncn_root = fixture.setup_ncn().await?; + tip_router_client + .do_initialize_config(ncn_root.ncn_pubkey, &ncn_root.ncn_admin) + .await?; + fixture.warp_slot_incremental(1).await?; + let transaction_error = tip_router_client + .do_initialize_config(ncn_root.ncn_pubkey, &ncn_root.ncn_admin) + .await; + assert_ix_error(transaction_error, InstructionError::InvalidAccountOwner); + Ok(()) + } + + #[tokio::test] + async fn test_initialize_ncn_config_invalid_ncn_fails() -> TestResult<()> { + let fixture = TestBuilder::new().await; + let mut tip_router_client = fixture.tip_router_client(); + let fake_ncn = Keypair::new(); + let fake_admin = Keypair::new(); + let fake_ncn_root = NcnRoot { + ncn_pubkey: fake_ncn.pubkey(), + ncn_admin: fake_admin, + }; + tip_router_client + .airdrop(&fake_ncn_root.ncn_admin.pubkey(), 1.0) + .await?; + let transaction_error = tip_router_client + .do_initialize_config(fake_ncn_root.ncn_pubkey, &fake_ncn_root.ncn_admin) + .await; + assert_ix_error(transaction_error, InstructionError::InvalidAccountOwner); + Ok(()) + } + + #[tokio::test] + async fn test_initialize_ncn_config_fees_exceed_max_fails() -> TestResult<()> { + let mut fixture = TestBuilder::new().await; + let mut tip_router_client = fixture.tip_router_client(); + let ncn_root = fixture.setup_ncn().await?; + + let ncn_admin_pubkey = ncn_root.ncn_admin.pubkey(); + let transaction_error = tip_router_client + .initialize_config( + ncn_root.ncn_pubkey, + &ncn_root.ncn_admin, + ncn_admin_pubkey, + ncn_admin_pubkey, + 10_001, + 0, + 0, + ) + .await; + assert_tip_router_error(transaction_error, TipRouterError::FeeCapExceeded); + Ok(()) + } +} diff --git a/integration_tests/tests/tip_router/mod.rs b/integration_tests/tests/tip_router/mod.rs new file mode 100644 index 0000000..04d688f --- /dev/null +++ b/integration_tests/tests/tip_router/mod.rs @@ -0,0 +1,3 @@ +mod initialize_ncn_config; +mod set_config_fees; +mod set_new_admin; diff --git a/integration_tests/tests/tip_router/set_config_fees.rs b/integration_tests/tests/tip_router/set_config_fees.rs new file mode 100644 index 0000000..dd79393 --- /dev/null +++ b/integration_tests/tests/tip_router/set_config_fees.rs @@ -0,0 +1,113 @@ +#[cfg(test)] +mod tests { + use jito_tip_router_core::error::TipRouterError; + use solana_sdk::{ + clock::DEFAULT_SLOTS_PER_EPOCH, + signature::{Keypair, Signer}, + }; + + use crate::fixtures::{ + test_builder::TestBuilder, tip_router_client::assert_tip_router_error, TestResult, + }; + + #[tokio::test] + async fn test_set_config_fees_ok() -> TestResult<()> { + let mut fixture = TestBuilder::new().await; + let mut tip_router_client = fixture.tip_router_client(); + let ncn_root = fixture.setup_ncn().await?; + + // Initialize config first - note that ncn_admin is now required as signer + tip_router_client + .do_initialize_config(ncn_root.ncn_pubkey, &ncn_root.ncn_admin) + .await?; + + // Change fees and fee wallet + let new_fee_wallet = Keypair::new(); + tip_router_client + .do_set_config_fees( + 100, // dao_fee_bps + 200, // ncn_fee_bps + 300, // block_engine_fee_bps + new_fee_wallet.pubkey(), + &ncn_root, + ) + .await?; + + Ok(()) + } + + #[tokio::test] + async fn test_set_config_fees_exceed_max_fails() -> TestResult<()> { + let mut fixture = TestBuilder::new().await; + let mut tip_router_client = fixture.tip_router_client(); + let ncn_root = fixture.setup_ncn().await?; + + // Initialize config first + tip_router_client + .do_initialize_config(ncn_root.ncn_pubkey, &ncn_root.ncn_admin) + .await?; + + // Try to set fees above max + let transaction_error = tip_router_client + .do_set_config_fees(10_001, 0, 0, ncn_root.ncn_admin.pubkey(), &ncn_root) + .await; + + assert_tip_router_error(transaction_error, TipRouterError::FeeCapExceeded); + Ok(()) + } + + #[tokio::test] + async fn test_set_config_fees_wrong_admin_fails() -> TestResult<()> { + let mut fixture = TestBuilder::new().await; + let mut tip_router_client = fixture.tip_router_client(); + let mut ncn_root = fixture.setup_ncn().await?; + + tip_router_client + .do_initialize_config(ncn_root.ncn_pubkey, &ncn_root.ncn_admin) + .await?; + + let wrong_admin = Keypair::new(); + ncn_root.ncn_admin = wrong_admin; + let transaction_error = tip_router_client + .do_set_config_fees(100, 200, 300, ncn_root.ncn_admin.pubkey(), &ncn_root) + .await; + + assert_tip_router_error(transaction_error, TipRouterError::IncorrectFeeAdmin); + Ok(()) + } + + #[tokio::test] + async fn test_set_config_fees_across_epoch() -> TestResult<()> { + let mut fixture = TestBuilder::new().await; + let mut tip_router_client = fixture.tip_router_client(); + let ncn_root = fixture.setup_ncn().await?; + + // Initialize config first + tip_router_client + .do_initialize_config(ncn_root.ncn_pubkey, &ncn_root.ncn_admin) + .await?; + + // Set new fees + let new_fee_wallet = Keypair::new(); + tip_router_client + .do_set_config_fees(100, 200, 0, new_fee_wallet.pubkey(), &ncn_root) + .await?; + + // Advance epoch + fixture + .warp_slot_incremental(2 * DEFAULT_SLOTS_PER_EPOCH) + .await?; + + let config = tip_router_client.get_config(ncn_root.ncn_pubkey).await?; + let clock = fixture.clock().await; + assert_eq!(config.fees.dao_fee(clock.epoch as u64).unwrap(), 100); + assert_eq!(config.fees.ncn_fee(clock.epoch as u64).unwrap(), 200); + assert_eq!(config.fees.block_engine_fee(clock.epoch as u64), 0); + assert_eq!( + config.fees.fee_wallet(clock.epoch as u64), + new_fee_wallet.pubkey() + ); + + Ok(()) + } +} diff --git a/integration_tests/tests/tip_router/set_new_admin.rs b/integration_tests/tests/tip_router/set_new_admin.rs new file mode 100644 index 0000000..14f19c0 --- /dev/null +++ b/integration_tests/tests/tip_router/set_new_admin.rs @@ -0,0 +1,85 @@ +mod tests { + use jito_tip_router_client::types::ConfigAdminRole; + use jito_tip_router_core::{error::TipRouterError, ncn_config::NcnConfig}; + use solana_program::pubkey::Pubkey; + use solana_sdk::{instruction::InstructionError, signature::Keypair}; + + use crate::fixtures::{ + assert_ix_error, restaking_client::NcnRoot, test_builder::TestBuilder, + tip_router_client::assert_tip_router_error, TestResult, + }; + + #[tokio::test] + async fn test_set_new_admin_success() -> TestResult<()> { + let mut fixture = TestBuilder::new().await; + let mut tip_router_client = fixture.tip_router_client(); + let ncn_root = fixture.setup_ncn().await?; + + tip_router_client + .do_initialize_config(ncn_root.ncn_pubkey, &ncn_root.ncn_admin) + .await?; + + let new_fee_admin = Pubkey::new_unique(); + tip_router_client + .do_set_new_admin(ConfigAdminRole::FeeAdmin, new_fee_admin, &ncn_root) + .await?; + + let config = tip_router_client.get_config(ncn_root.ncn_pubkey).await?; + assert_eq!(config.fee_admin, new_fee_admin); + + let new_tie_breaker = Pubkey::new_unique(); + tip_router_client + .do_set_new_admin(ConfigAdminRole::TieBreakerAdmin, new_tie_breaker, &ncn_root) + .await?; + + let config = tip_router_client.get_config(ncn_root.ncn_pubkey).await?; + assert_eq!(config.tie_breaker_admin, new_tie_breaker); + Ok(()) + } + + #[tokio::test] + async fn test_set_new_admin_incorrect_accounts() -> TestResult<()> { + let mut fixture = TestBuilder::new().await; + let mut tip_router_client = fixture.tip_router_client(); + let ncn_root = fixture.setup_ncn().await?; + + tip_router_client + .do_initialize_config(ncn_root.ncn_pubkey, &ncn_root.ncn_admin) + .await?; + + fixture.warp_slot_incremental(1).await?; + let mut restaking_program_client = fixture.restaking_program_client(); + let wrong_ncn_root = restaking_program_client.do_initialize_ncn().await?; + + let result = tip_router_client + .set_new_admin( + NcnConfig::find_program_address( + &jito_tip_router_program::id(), + &ncn_root.ncn_pubkey, + ) + .0, + ConfigAdminRole::FeeAdmin, + Pubkey::new_unique(), + &wrong_ncn_root, + ) + .await; + + assert_ix_error(result, InstructionError::InvalidAccountData); + + let wrong_ncn_root = NcnRoot { + ncn_pubkey: ncn_root.ncn_pubkey, + ncn_admin: Keypair::new(), + }; + + let result = tip_router_client + .do_set_new_admin( + ConfigAdminRole::FeeAdmin, + Pubkey::new_unique(), + &wrong_ncn_root, + ) + .await; + + assert_tip_router_error(result, TipRouterError::IncorrectNcnAdmin); + Ok(()) + } +} diff --git a/program/src/initialize_ncn_config.rs b/program/src/initialize_ncn_config.rs new file mode 100644 index 0000000..a1fe566 --- /dev/null +++ b/program/src/initialize_ncn_config.rs @@ -0,0 +1,88 @@ +use jito_bytemuck::{AccountDeserialize, Discriminator}; +use jito_jsm_core::{ + create_account, + loader::{load_signer, load_system_account, load_system_program}, +}; +use jito_restaking_core::ncn::Ncn; +use jito_tip_router_core::{error::TipRouterError, fees::Fees, ncn_config::NcnConfig, MAX_FEE_BPS}; +use solana_program::{ + account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, + program_error::ProgramError, pubkey::Pubkey, rent::Rent, sysvar::Sysvar, +}; + +pub fn process_initialize_ncn_config( + program_id: &Pubkey, + accounts: &[AccountInfo], + dao_fee_bps: u64, + ncn_fee_bps: u64, + block_engine_fee_bps: u64, +) -> ProgramResult { + let [config, ncn_account, fee_wallet, ncn_admin, tie_breaker_admin, restaking_program_id, system_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + load_system_account(config, true)?; + load_system_program(system_program)?; + load_signer(ncn_admin, false)?; + + Ncn::load(restaking_program_id.key, ncn_account, false)?; + + let ncn_data = ncn_account.data.borrow(); + let ncn = Ncn::try_from_slice_unchecked(&ncn_data)?; + if ncn.admin != *ncn_admin.key { + return Err(TipRouterError::IncorrectNcnAdmin.into()); + } + + let (config_pda, config_bump, mut config_seeds) = + NcnConfig::find_program_address(program_id, ncn_account.key); + config_seeds.push(vec![config_bump]); + + if config_pda != *config.key { + return Err(ProgramError::InvalidSeeds); + } + + if dao_fee_bps > MAX_FEE_BPS { + return Err(TipRouterError::FeeCapExceeded.into()); + } + if ncn_fee_bps > MAX_FEE_BPS { + return Err(TipRouterError::FeeCapExceeded.into()); + } + if block_engine_fee_bps > MAX_FEE_BPS { + return Err(TipRouterError::FeeCapExceeded.into()); + } + + create_account( + ncn_admin, + config, + system_program, + program_id, + &Rent::get()?, + 8_u64 + .checked_add(std::mem::size_of::() as u64) + .unwrap(), + &config_seeds, + )?; + + let epoch = Clock::get()?.epoch; + + let mut config_data = config.try_borrow_mut_data()?; + config_data[0] = NcnConfig::DISCRIMINATOR; + let config = NcnConfig::try_from_slice_unchecked_mut(&mut config_data)?; + *config = NcnConfig::new( + *ncn_account.key, + *tie_breaker_admin.key, + *ncn_admin.key, + Fees::new( + *fee_wallet.key, + dao_fee_bps, + ncn_fee_bps, + block_engine_fee_bps, + epoch, + ), + ); + config.bump = config_bump; + + Ok(()) +} diff --git a/program/src/lib.rs b/program/src/lib.rs index 39f150a..f91459d 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -1,10 +1,14 @@ mod finalize_weight_table; +mod initialize_ncn_config; mod initialize_weight_table; +mod set_config_fees; +mod set_new_admin; mod update_weight_table; use borsh::BorshDeserialize; use const_str_to_pubkey::str_to_pubkey; use jito_tip_router_core::instruction::WeightTableInstruction; +use set_new_admin::process_set_new_admin; use solana_program::{ account_info::AccountInfo, declare_id, entrypoint::ProgramResult, msg, program_error::ProgramError, pubkey::Pubkey, @@ -14,8 +18,9 @@ use solana_security_txt::security_txt; use crate::{ finalize_weight_table::process_finalize_weight_table, + initialize_ncn_config::process_initialize_ncn_config, initialize_weight_table::process_initialize_weight_table, - update_weight_table::process_update_weight_table, + set_config_fees::process_set_config_fees, update_weight_table::process_update_weight_table, }; declare_id!(str_to_pubkey(env!("TIP_ROUTER_PROGRAM_ID"))); @@ -50,6 +55,20 @@ pub fn process_instruction( // ------------------------------------------ // Initialization // ------------------------------------------ + WeightTableInstruction::InitializeConfig { + dao_fee_bps, + ncn_fee_bps, + block_engine_fee_bps, + } => { + msg!("Instruction: InitializeConfig"); + process_initialize_ncn_config( + program_id, + accounts, + dao_fee_bps, + ncn_fee_bps, + block_engine_fee_bps, + ) + } WeightTableInstruction::InitializeWeightTable { first_slot_of_ncn_epoch, } => { @@ -80,5 +99,25 @@ pub fn process_instruction( msg!("Instruction: FinalizeWeightTable"); process_finalize_weight_table(program_id, accounts, ncn_epoch) } + WeightTableInstruction::SetConfigFees { + new_dao_fee_bps, + new_ncn_fee_bps, + new_block_engine_fee_bps, + new_fee_wallet, + } => { + msg!("Instruction: SetConfigFees"); + process_set_config_fees( + program_id, + accounts, + new_dao_fee_bps, + new_ncn_fee_bps, + new_block_engine_fee_bps, + new_fee_wallet, + ) + } + WeightTableInstruction::SetNewAdmin { role } => { + msg!("Instruction: SetNewAdmin"); + process_set_new_admin(program_id, accounts, role) + } } } diff --git a/program/src/set_config_fees.rs b/program/src/set_config_fees.rs new file mode 100644 index 0000000..ad7b3be --- /dev/null +++ b/program/src/set_config_fees.rs @@ -0,0 +1,52 @@ +use jito_bytemuck::{AccountDeserialize, Discriminator}; +use jito_jsm_core::loader::load_signer; +use jito_restaking_core::ncn::Ncn; +use jito_tip_router_core::{error::TipRouterError, ncn_config::NcnConfig}; +use solana_program::{ + account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, + program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, +}; + +pub fn process_set_config_fees( + program_id: &Pubkey, + accounts: &[AccountInfo], + new_dao_fee_bps: Option, + new_ncn_fee_bps: Option, + new_block_engine_fee_bps: Option, + new_fee_wallet: Option, +) -> ProgramResult { + let [config, ncn_account, fee_admin, restaking_program_id] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + load_signer(fee_admin, true)?; + + NcnConfig::load(program_id, ncn_account.key, config, true)?; + Ncn::load(restaking_program_id.key, ncn_account, false)?; + + let mut config_data = config.try_borrow_mut_data()?; + if config_data[0] != NcnConfig::DISCRIMINATOR { + return Err(ProgramError::InvalidAccountData); + } + let config = NcnConfig::try_from_slice_unchecked_mut(&mut config_data)?; + + // Verify NCN and Admin + if config.ncn != *ncn_account.key { + return Err(TipRouterError::IncorrectNcn.into()); + } + + if config.fee_admin != *fee_admin.key { + return Err(TipRouterError::IncorrectFeeAdmin.into()); + } + + let epoch = Clock::get()?.epoch; + config.fees.set_new_fees( + new_dao_fee_bps, + new_ncn_fee_bps, + new_block_engine_fee_bps, + new_fee_wallet, + epoch, + )?; + + Ok(()) +} diff --git a/program/src/set_new_admin.rs b/program/src/set_new_admin.rs new file mode 100644 index 0000000..1e1f09f --- /dev/null +++ b/program/src/set_new_admin.rs @@ -0,0 +1,56 @@ +use jito_bytemuck::{AccountDeserialize, Discriminator}; +use jito_jsm_core::loader::load_signer; +use jito_restaking_core::ncn::Ncn; +use jito_tip_router_core::{ + error::TipRouterError, instruction::ConfigAdminRole, ncn_config::NcnConfig, +}; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, + pubkey::Pubkey, +}; + +pub fn process_set_new_admin( + program_id: &Pubkey, + accounts: &[AccountInfo], + role: ConfigAdminRole, +) -> ProgramResult { + let [config, ncn_account, ncn_admin, new_admin, restaking_program_id] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + load_signer(ncn_admin, true)?; + + NcnConfig::load(program_id, ncn_account.key, config, true)?; + Ncn::load(restaking_program_id.key, ncn_account, false)?; + + let mut config_data = config.try_borrow_mut_data()?; + if config_data[0] != NcnConfig::DISCRIMINATOR { + return Err(ProgramError::InvalidAccountData); + } + let config = NcnConfig::try_from_slice_unchecked_mut(&mut config_data)?; + + // Verify NCN and Admin + if config.ncn != *ncn_account.key { + return Err(TipRouterError::IncorrectNcn.into()); + } + + let ncn_data = ncn_account.data.borrow(); + let ncn = Ncn::try_from_slice_unchecked(&ncn_data)?; + + if ncn.admin != *ncn_admin.key { + return Err(TipRouterError::IncorrectNcnAdmin.into()); + } + + match role { + ConfigAdminRole::FeeAdmin => { + config.fee_admin = *new_admin.key; + msg!("Fee admin set to {:?}", new_admin.key); + } + ConfigAdminRole::TieBreakerAdmin => { + config.tie_breaker_admin = *new_admin.key; + msg!("Tie breaker admin set to {:?}", new_admin.key); + } + } + + Ok(()) +}