From 01bee5add42a9538fc3102a71cb8cb2c98aca3d3 Mon Sep 17 00:00:00 2001 From: Evan Batsell Date: Tue, 12 Nov 2024 17:19:37 -0500 Subject: [PATCH] Move tracked_mints to its own account --- clients/js/jito_tip_router/accounts/index.ts | 1 + .../js/jito_tip_router/accounts/ncnConfig.ts | 8 - .../jito_tip_router/accounts/trackedMints.ts | 135 ++++++ .../jito_tip_router/errors/jitoTipRouter.ts | 8 + .../js/jito_tip_router/instructions/index.ts | 1 + .../instructions/initializeTrackedMints.ts | 229 +++++++++ .../instructions/registerMint.ts | 28 +- .../jito_tip_router/programs/jitoTipRouter.ts | 11 +- clients/js/jito_tip_router/types/mintEntry.ts | 36 +- .../src/generated/accounts/mod.rs | 3 +- .../src/generated/accounts/ncn_config.rs | 4 +- .../src/generated/accounts/tracked_mints.rs | 69 +++ .../src/generated/errors/jito_tip_router.rs | 6 + .../instructions/initialize_tracked_mints.rs | 437 ++++++++++++++++++ .../src/generated/instructions/mod.rs | 5 +- .../generated/instructions/register_mint.rs | 41 +- .../src/generated/types/mint_entry.rs | 7 +- core/src/discriminators.rs | 1 + core/src/error.rs | 4 + core/src/instruction.rs | 10 +- core/src/lib.rs | 1 + core/src/ncn_config.rs | 82 +--- core/src/tracked_mints.rs | 224 +++++++++ idl/jito_tip_router.json | 113 ++++- program/src/initialize_tracked_mints.rs | 53 +++ program/src/initialize_weight_table.rs | 14 +- program/src/lib.rs | 6 + program/src/register_mint.rs | 12 +- 28 files changed, 1400 insertions(+), 149 deletions(-) create mode 100644 clients/js/jito_tip_router/accounts/trackedMints.ts create mode 100644 clients/js/jito_tip_router/instructions/initializeTrackedMints.ts create mode 100644 clients/rust/jito_tip_router/src/generated/accounts/tracked_mints.rs create mode 100644 clients/rust/jito_tip_router/src/generated/instructions/initialize_tracked_mints.rs create mode 100644 core/src/tracked_mints.rs create mode 100644 program/src/initialize_tracked_mints.rs diff --git a/clients/js/jito_tip_router/accounts/index.ts b/clients/js/jito_tip_router/accounts/index.ts index 033882ce..4c535665 100644 --- a/clients/js/jito_tip_router/accounts/index.ts +++ b/clients/js/jito_tip_router/accounts/index.ts @@ -7,4 +7,5 @@ */ export * from './ncnConfig'; +export * from './trackedMints'; export * from './weightTable'; diff --git a/clients/js/jito_tip_router/accounts/ncnConfig.ts b/clients/js/jito_tip_router/accounts/ncnConfig.ts index 54af994e..6299e668 100644 --- a/clients/js/jito_tip_router/accounts/ncnConfig.ts +++ b/clients/js/jito_tip_router/accounts/ncnConfig.ts @@ -37,12 +37,8 @@ import { import { getFeesDecoder, getFeesEncoder, - getMintEntryDecoder, - getMintEntryEncoder, type Fees, type FeesArgs, - type MintEntry, - type MintEntryArgs, } from '../types'; export type NcnConfig = { @@ -51,7 +47,6 @@ export type NcnConfig = { tieBreakerAdmin: Address; feeAdmin: Address; fees: Fees; - mintList: Array; bump: number; reserved: Array; }; @@ -62,7 +57,6 @@ export type NcnConfigArgs = { tieBreakerAdmin: Address; feeAdmin: Address; fees: FeesArgs; - mintList: Array; bump: number; reserved: Array; }; @@ -74,7 +68,6 @@ export function getNcnConfigEncoder(): Encoder { ['tieBreakerAdmin', getAddressEncoder()], ['feeAdmin', getAddressEncoder()], ['fees', getFeesEncoder()], - ['mintList', getArrayEncoder(getMintEntryEncoder(), { size: 64 })], ['bump', getU8Encoder()], ['reserved', getArrayEncoder(getU8Encoder(), { size: 127 })], ]); @@ -87,7 +80,6 @@ export function getNcnConfigDecoder(): Decoder { ['tieBreakerAdmin', getAddressDecoder()], ['feeAdmin', getAddressDecoder()], ['fees', getFeesDecoder()], - ['mintList', getArrayDecoder(getMintEntryDecoder(), { size: 64 })], ['bump', getU8Decoder()], ['reserved', getArrayDecoder(getU8Decoder(), { size: 127 })], ]); diff --git a/clients/js/jito_tip_router/accounts/trackedMints.ts b/clients/js/jito_tip_router/accounts/trackedMints.ts new file mode 100644 index 00000000..a275f531 --- /dev/null +++ b/clients/js/jito_tip_router/accounts/trackedMints.ts @@ -0,0 +1,135 @@ +/** + * 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 { + getMintEntryDecoder, + getMintEntryEncoder, + type MintEntry, + type MintEntryArgs, +} from '../types'; + +export type TrackedMints = { + discriminator: bigint; + ncn: Address; + bump: number; + reserved: Array; + stMintList: Array; +}; + +export type TrackedMintsArgs = { + discriminator: number | bigint; + ncn: Address; + bump: number; + reserved: Array; + stMintList: Array; +}; + +export function getTrackedMintsEncoder(): Encoder { + return getStructEncoder([ + ['discriminator', getU64Encoder()], + ['ncn', getAddressEncoder()], + ['bump', getU8Encoder()], + ['reserved', getArrayEncoder(getU8Encoder(), { size: 7 })], + ['stMintList', getArrayEncoder(getMintEntryEncoder(), { size: 64 })], + ]); +} + +export function getTrackedMintsDecoder(): Decoder { + return getStructDecoder([ + ['discriminator', getU64Decoder()], + ['ncn', getAddressDecoder()], + ['bump', getU8Decoder()], + ['reserved', getArrayDecoder(getU8Decoder(), { size: 7 })], + ['stMintList', getArrayDecoder(getMintEntryDecoder(), { size: 64 })], + ]); +} + +export function getTrackedMintsCodec(): Codec { + return combineCodec(getTrackedMintsEncoder(), getTrackedMintsDecoder()); +} + +export function decodeTrackedMints( + encodedAccount: EncodedAccount +): Account; +export function decodeTrackedMints( + encodedAccount: MaybeEncodedAccount +): MaybeAccount; +export function decodeTrackedMints( + encodedAccount: EncodedAccount | MaybeEncodedAccount +): Account | MaybeAccount { + return decodeAccount( + encodedAccount as MaybeEncodedAccount, + getTrackedMintsDecoder() + ); +} + +export async function fetchTrackedMints( + rpc: Parameters[0], + address: Address, + config?: FetchAccountConfig +): Promise> { + const maybeAccount = await fetchMaybeTrackedMints(rpc, address, config); + assertAccountExists(maybeAccount); + return maybeAccount; +} + +export async function fetchMaybeTrackedMints( + rpc: Parameters[0], + address: Address, + config?: FetchAccountConfig +): Promise> { + const maybeAccount = await fetchEncodedAccount(rpc, address, config); + return decodeTrackedMints(maybeAccount); +} + +export async function fetchAllTrackedMints( + rpc: Parameters[0], + addresses: Array
, + config?: FetchAccountsConfig +): Promise[]> { + const maybeAccounts = await fetchAllMaybeTrackedMints(rpc, addresses, config); + assertAccountsExist(maybeAccounts); + return maybeAccounts; +} + +export async function fetchAllMaybeTrackedMints( + rpc: Parameters[0], + addresses: Array
, + config?: FetchAccountsConfig +): Promise[]> { + const maybeAccounts = await fetchEncodedAccounts(rpc, addresses, config); + return maybeAccounts.map((maybeAccount) => decodeTrackedMints(maybeAccount)); +} diff --git a/clients/js/jito_tip_router/errors/jitoTipRouter.ts b/clients/js/jito_tip_router/errors/jitoTipRouter.ts index 214b57d8..a4469937 100644 --- a/clients/js/jito_tip_router/errors/jitoTipRouter.ts +++ b/clients/js/jito_tip_router/errors/jitoTipRouter.ts @@ -46,6 +46,10 @@ export const JITO_TIP_ROUTER_ERROR__INVALID_MINT_FOR_WEIGHT_TABLE = 0x2208; // 8 export const JITO_TIP_ROUTER_ERROR__CONFIG_MINTS_NOT_UPDATED = 0x2209; // 8713 /** ConfigMintListFull: NCN config vaults are at capacity */ export const JITO_TIP_ROUTER_ERROR__CONFIG_MINT_LIST_FULL = 0x220a; // 8714 +/** TrackedMintListFull: Tracked mints are at capacity */ +export const JITO_TIP_ROUTER_ERROR__TRACKED_MINT_LIST_FULL = 0x220b; // 8715 +/** VaultIndexAlreadyInUse: Vault index already in use by a different mint */ +export const JITO_TIP_ROUTER_ERROR__VAULT_INDEX_ALREADY_IN_USE = 0x220c; // 8716 /** FeeCapExceeded: Fee cap exceeded */ export const JITO_TIP_ROUTER_ERROR__FEE_CAP_EXCEEDED = 0x2300; // 8960 /** IncorrectNcnAdmin: Incorrect NCN Admin */ @@ -73,6 +77,8 @@ export type JitoTipRouterError = | typeof JITO_TIP_ROUTER_ERROR__NEW_PRECISE_NUMBER_ERROR | typeof JITO_TIP_ROUTER_ERROR__NO_MINTS_IN_TABLE | typeof JITO_TIP_ROUTER_ERROR__TOO_MANY_MINTS_FOR_TABLE + | typeof JITO_TIP_ROUTER_ERROR__TRACKED_MINT_LIST_FULL + | typeof JITO_TIP_ROUTER_ERROR__VAULT_INDEX_ALREADY_IN_USE | typeof JITO_TIP_ROUTER_ERROR__WEIGHT_MINTS_DO_NOT_MATCH_LENGTH | typeof JITO_TIP_ROUTER_ERROR__WEIGHT_MINTS_DO_NOT_MATCH_MINT_HASH | typeof JITO_TIP_ROUTER_ERROR__WEIGHT_TABLE_ALREADY_INITIALIZED; @@ -97,6 +103,8 @@ if (process.env.NODE_ENV !== 'production') { [JITO_TIP_ROUTER_ERROR__NEW_PRECISE_NUMBER_ERROR]: `New precise number error`, [JITO_TIP_ROUTER_ERROR__NO_MINTS_IN_TABLE]: `There are no mints in the table`, [JITO_TIP_ROUTER_ERROR__TOO_MANY_MINTS_FOR_TABLE]: `Too many mints for table`, + [JITO_TIP_ROUTER_ERROR__TRACKED_MINT_LIST_FULL]: `Tracked mints are at capacity`, + [JITO_TIP_ROUTER_ERROR__VAULT_INDEX_ALREADY_IN_USE]: `Vault index already in use by a different mint`, [JITO_TIP_ROUTER_ERROR__WEIGHT_MINTS_DO_NOT_MATCH_LENGTH]: `Weight mints do not match - length`, [JITO_TIP_ROUTER_ERROR__WEIGHT_MINTS_DO_NOT_MATCH_MINT_HASH]: `Weight mints do not match - mint hash`, [JITO_TIP_ROUTER_ERROR__WEIGHT_TABLE_ALREADY_INITIALIZED]: `Weight table already initialized`, diff --git a/clients/js/jito_tip_router/instructions/index.ts b/clients/js/jito_tip_router/instructions/index.ts index 30486715..b46aa8b9 100644 --- a/clients/js/jito_tip_router/instructions/index.ts +++ b/clients/js/jito_tip_router/instructions/index.ts @@ -8,6 +8,7 @@ export * from './adminUpdateWeightTable'; export * from './initializeNCNConfig'; +export * from './initializeTrackedMints'; export * from './initializeWeightTable'; export * from './registerMint'; export * from './setConfigFees'; diff --git a/clients/js/jito_tip_router/instructions/initializeTrackedMints.ts b/clients/js/jito_tip_router/instructions/initializeTrackedMints.ts new file mode 100644 index 00000000..a113a590 --- /dev/null +++ b/clients/js/jito_tip_router/instructions/initializeTrackedMints.ts @@ -0,0 +1,229 @@ +/** + * 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 TransactionSigner, + type WritableAccount, + type WritableSignerAccount, +} from '@solana/web3.js'; +import { JITO_TIP_ROUTER_PROGRAM_ADDRESS } from '../programs'; +import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; + +export const INITIALIZE_TRACKED_MINTS_DISCRIMINATOR = 6; + +export function getInitializeTrackedMintsDiscriminatorBytes() { + return getU8Encoder().encode(INITIALIZE_TRACKED_MINTS_DISCRIMINATOR); +} + +export type InitializeTrackedMintsInstruction< + TProgram extends string = typeof JITO_TIP_ROUTER_PROGRAM_ADDRESS, + TAccountNcnConfig extends string | IAccountMeta = string, + TAccountTrackedMints extends string | IAccountMeta = string, + TAccountNcn extends string | IAccountMeta = string, + TAccountPayer extends string | IAccountMeta = string, + TAccountSystemProgram extends + | string + | IAccountMeta = '11111111111111111111111111111111', + TRemainingAccounts extends readonly IAccountMeta[] = [], +> = IInstruction & + IInstructionWithData & + IInstructionWithAccounts< + [ + TAccountNcnConfig extends string + ? ReadonlyAccount + : TAccountNcnConfig, + TAccountTrackedMints extends string + ? WritableAccount + : TAccountTrackedMints, + TAccountNcn extends string ? ReadonlyAccount : TAccountNcn, + TAccountPayer extends string + ? WritableSignerAccount & + IAccountSignerMeta + : TAccountPayer, + TAccountSystemProgram extends string + ? ReadonlyAccount + : TAccountSystemProgram, + ...TRemainingAccounts, + ] + >; + +export type InitializeTrackedMintsInstructionData = { discriminator: number }; + +export type InitializeTrackedMintsInstructionDataArgs = {}; + +export function getInitializeTrackedMintsInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([['discriminator', getU8Encoder()]]), + (value) => ({ + ...value, + discriminator: INITIALIZE_TRACKED_MINTS_DISCRIMINATOR, + }) + ); +} + +export function getInitializeTrackedMintsInstructionDataDecoder(): Decoder { + return getStructDecoder([['discriminator', getU8Decoder()]]); +} + +export function getInitializeTrackedMintsInstructionDataCodec(): Codec< + InitializeTrackedMintsInstructionDataArgs, + InitializeTrackedMintsInstructionData +> { + return combineCodec( + getInitializeTrackedMintsInstructionDataEncoder(), + getInitializeTrackedMintsInstructionDataDecoder() + ); +} + +export type InitializeTrackedMintsInput< + TAccountNcnConfig extends string = string, + TAccountTrackedMints extends string = string, + TAccountNcn extends string = string, + TAccountPayer extends string = string, + TAccountSystemProgram extends string = string, +> = { + ncnConfig: Address; + trackedMints: Address; + ncn: Address; + payer: TransactionSigner; + systemProgram?: Address; +}; + +export function getInitializeTrackedMintsInstruction< + TAccountNcnConfig extends string, + TAccountTrackedMints extends string, + TAccountNcn extends string, + TAccountPayer extends string, + TAccountSystemProgram extends string, + TProgramAddress extends Address = typeof JITO_TIP_ROUTER_PROGRAM_ADDRESS, +>( + input: InitializeTrackedMintsInput< + TAccountNcnConfig, + TAccountTrackedMints, + TAccountNcn, + TAccountPayer, + TAccountSystemProgram + >, + config?: { programAddress?: TProgramAddress } +): InitializeTrackedMintsInstruction< + TProgramAddress, + TAccountNcnConfig, + TAccountTrackedMints, + TAccountNcn, + TAccountPayer, + TAccountSystemProgram +> { + // Program address. + const programAddress = + config?.programAddress ?? JITO_TIP_ROUTER_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + ncnConfig: { value: input.ncnConfig ?? null, isWritable: false }, + trackedMints: { value: input.trackedMints ?? null, isWritable: true }, + ncn: { value: input.ncn ?? null, isWritable: false }, + payer: { value: input.payer ?? null, isWritable: true }, + systemProgram: { value: input.systemProgram ?? null, isWritable: false }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + // Resolve default values. + if (!accounts.systemProgram.value) { + accounts.systemProgram.value = + '11111111111111111111111111111111' as Address<'11111111111111111111111111111111'>; + } + + const getAccountMeta = getAccountMetaFactory(programAddress, 'programId'); + const instruction = { + accounts: [ + getAccountMeta(accounts.ncnConfig), + getAccountMeta(accounts.trackedMints), + getAccountMeta(accounts.ncn), + getAccountMeta(accounts.payer), + getAccountMeta(accounts.systemProgram), + ], + programAddress, + data: getInitializeTrackedMintsInstructionDataEncoder().encode({}), + } as InitializeTrackedMintsInstruction< + TProgramAddress, + TAccountNcnConfig, + TAccountTrackedMints, + TAccountNcn, + TAccountPayer, + TAccountSystemProgram + >; + + return instruction; +} + +export type ParsedInitializeTrackedMintsInstruction< + TProgram extends string = typeof JITO_TIP_ROUTER_PROGRAM_ADDRESS, + TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], +> = { + programAddress: Address; + accounts: { + ncnConfig: TAccountMetas[0]; + trackedMints: TAccountMetas[1]; + ncn: TAccountMetas[2]; + payer: TAccountMetas[3]; + systemProgram: TAccountMetas[4]; + }; + data: InitializeTrackedMintsInstructionData; +}; + +export function parseInitializeTrackedMintsInstruction< + TProgram extends string, + TAccountMetas extends readonly IAccountMeta[], +>( + instruction: IInstruction & + IInstructionWithAccounts & + IInstructionWithData +): ParsedInitializeTrackedMintsInstruction { + 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: { + ncnConfig: getNextAccount(), + trackedMints: getNextAccount(), + ncn: getNextAccount(), + payer: getNextAccount(), + systemProgram: getNextAccount(), + }, + data: getInitializeTrackedMintsInstructionDataDecoder().decode( + instruction.data + ), + }; +} diff --git a/clients/js/jito_tip_router/instructions/registerMint.ts b/clients/js/jito_tip_router/instructions/registerMint.ts index a7cb3e51..91641547 100644 --- a/clients/js/jito_tip_router/instructions/registerMint.ts +++ b/clients/js/jito_tip_router/instructions/registerMint.ts @@ -36,7 +36,7 @@ export function getRegisterMintDiscriminatorBytes() { export type RegisterMintInstruction< TProgram extends string = typeof JITO_TIP_ROUTER_PROGRAM_ADDRESS, TAccountRestakingConfig extends string | IAccountMeta = string, - TAccountNcnConfig extends string | IAccountMeta = string, + TAccountTrackedMints extends string | IAccountMeta = string, TAccountNcn extends string | IAccountMeta = string, TAccountVault extends string | IAccountMeta = string, TAccountVaultNcnTicket extends string | IAccountMeta = string, @@ -51,9 +51,9 @@ export type RegisterMintInstruction< TAccountRestakingConfig extends string ? ReadonlyAccount : TAccountRestakingConfig, - TAccountNcnConfig extends string - ? WritableAccount - : TAccountNcnConfig, + TAccountTrackedMints extends string + ? WritableAccount + : TAccountTrackedMints, TAccountNcn extends string ? ReadonlyAccount : TAccountNcn, TAccountVault extends string ? ReadonlyAccount @@ -101,7 +101,7 @@ export function getRegisterMintInstructionDataCodec(): Codec< export type RegisterMintInput< TAccountRestakingConfig extends string = string, - TAccountNcnConfig extends string = string, + TAccountTrackedMints extends string = string, TAccountNcn extends string = string, TAccountVault extends string = string, TAccountVaultNcnTicket extends string = string, @@ -110,7 +110,7 @@ export type RegisterMintInput< TAccountVaultProgramId extends string = string, > = { restakingConfig: Address; - ncnConfig: Address; + trackedMints: Address; ncn: Address; vault: Address; vaultNcnTicket: Address; @@ -121,7 +121,7 @@ export type RegisterMintInput< export function getRegisterMintInstruction< TAccountRestakingConfig extends string, - TAccountNcnConfig extends string, + TAccountTrackedMints extends string, TAccountNcn extends string, TAccountVault extends string, TAccountVaultNcnTicket extends string, @@ -132,7 +132,7 @@ export function getRegisterMintInstruction< >( input: RegisterMintInput< TAccountRestakingConfig, - TAccountNcnConfig, + TAccountTrackedMints, TAccountNcn, TAccountVault, TAccountVaultNcnTicket, @@ -144,7 +144,7 @@ export function getRegisterMintInstruction< ): RegisterMintInstruction< TProgramAddress, TAccountRestakingConfig, - TAccountNcnConfig, + TAccountTrackedMints, TAccountNcn, TAccountVault, TAccountVaultNcnTicket, @@ -162,7 +162,7 @@ export function getRegisterMintInstruction< value: input.restakingConfig ?? null, isWritable: false, }, - ncnConfig: { value: input.ncnConfig ?? null, isWritable: true }, + trackedMints: { value: input.trackedMints ?? null, isWritable: true }, ncn: { value: input.ncn ?? null, isWritable: false }, vault: { value: input.vault ?? null, isWritable: false }, vaultNcnTicket: { value: input.vaultNcnTicket ?? null, isWritable: false }, @@ -182,7 +182,7 @@ export function getRegisterMintInstruction< const instruction = { accounts: [ getAccountMeta(accounts.restakingConfig), - getAccountMeta(accounts.ncnConfig), + getAccountMeta(accounts.trackedMints), getAccountMeta(accounts.ncn), getAccountMeta(accounts.vault), getAccountMeta(accounts.vaultNcnTicket), @@ -195,7 +195,7 @@ export function getRegisterMintInstruction< } as RegisterMintInstruction< TProgramAddress, TAccountRestakingConfig, - TAccountNcnConfig, + TAccountTrackedMints, TAccountNcn, TAccountVault, TAccountVaultNcnTicket, @@ -214,7 +214,7 @@ export type ParsedRegisterMintInstruction< programAddress: Address; accounts: { restakingConfig: TAccountMetas[0]; - ncnConfig: TAccountMetas[1]; + trackedMints: TAccountMetas[1]; ncn: TAccountMetas[2]; vault: TAccountMetas[3]; vaultNcnTicket: TAccountMetas[4]; @@ -247,7 +247,7 @@ export function parseRegisterMintInstruction< programAddress: instruction.programAddress, accounts: { restakingConfig: getNextAccount(), - ncnConfig: getNextAccount(), + trackedMints: getNextAccount(), ncn: getNextAccount(), vault: getNextAccount(), vaultNcnTicket: getNextAccount(), diff --git a/clients/js/jito_tip_router/programs/jitoTipRouter.ts b/clients/js/jito_tip_router/programs/jitoTipRouter.ts index 4a2d3d89..5d846c65 100644 --- a/clients/js/jito_tip_router/programs/jitoTipRouter.ts +++ b/clients/js/jito_tip_router/programs/jitoTipRouter.ts @@ -15,6 +15,7 @@ import { import { type ParsedAdminUpdateWeightTableInstruction, type ParsedInitializeNCNConfigInstruction, + type ParsedInitializeTrackedMintsInstruction, type ParsedInitializeWeightTableInstruction, type ParsedRegisterMintInstruction, type ParsedSetConfigFeesInstruction, @@ -26,6 +27,7 @@ export const JITO_TIP_ROUTER_PROGRAM_ADDRESS = export enum JitoTipRouterAccount { NcnConfig, + TrackedMints, WeightTable, } @@ -36,6 +38,7 @@ export enum JitoTipRouterInstruction { InitializeWeightTable, AdminUpdateWeightTable, RegisterMint, + InitializeTrackedMints, } export function identifyJitoTipRouterInstruction( @@ -60,6 +63,9 @@ export function identifyJitoTipRouterInstruction( if (containsBytes(data, getU8Encoder().encode(5), 0)) { return JitoTipRouterInstruction.RegisterMint; } + if (containsBytes(data, getU8Encoder().encode(6), 0)) { + return JitoTipRouterInstruction.InitializeTrackedMints; + } throw new Error( 'The provided instruction could not be identified as a jitoTipRouter instruction.' ); @@ -85,4 +91,7 @@ export type ParsedJitoTipRouterInstruction< } & ParsedAdminUpdateWeightTableInstruction) | ({ instructionType: JitoTipRouterInstruction.RegisterMint; - } & ParsedRegisterMintInstruction); + } & ParsedRegisterMintInstruction) + | ({ + instructionType: JitoTipRouterInstruction.InitializeTrackedMints; + } & ParsedInitializeTrackedMintsInstruction); diff --git a/clients/js/jito_tip_router/types/mintEntry.ts b/clients/js/jito_tip_router/types/mintEntry.ts index 0934db01..9f271770 100644 --- a/clients/js/jito_tip_router/types/mintEntry.ts +++ b/clients/js/jito_tip_router/types/mintEntry.ts @@ -10,31 +10,59 @@ import { combineCodec, getAddressDecoder, getAddressEncoder, + getArrayDecoder, + getArrayEncoder, getStructDecoder, getStructEncoder, + getU128Decoder, + getU128Encoder, getU64Decoder, getU64Encoder, + getU8Decoder, + getU8Encoder, type Address, type Codec, type Decoder, type Encoder, } from '@solana/web3.js'; -export type MintEntry = { mint: Address; vaultIndex: bigint }; +export type MintEntry = { + stMint: Address; + vaultIndex: bigint; + weight: bigint; + slotSet: bigint; + slotUpdated: bigint; + reserved: Array; +}; -export type MintEntryArgs = { mint: Address; vaultIndex: number | bigint }; +export type MintEntryArgs = { + stMint: Address; + vaultIndex: number | bigint; + weight: number | bigint; + slotSet: number | bigint; + slotUpdated: number | bigint; + reserved: Array; +}; export function getMintEntryEncoder(): Encoder { return getStructEncoder([ - ['mint', getAddressEncoder()], + ['stMint', getAddressEncoder()], ['vaultIndex', getU64Encoder()], + ['weight', getU128Encoder()], + ['slotSet', getU64Encoder()], + ['slotUpdated', getU64Encoder()], + ['reserved', getArrayEncoder(getU8Encoder(), { size: 128 })], ]); } export function getMintEntryDecoder(): Decoder { return getStructDecoder([ - ['mint', getAddressDecoder()], + ['stMint', getAddressDecoder()], ['vaultIndex', getU64Decoder()], + ['weight', getU128Decoder()], + ['slotSet', getU64Decoder()], + ['slotUpdated', getU64Decoder()], + ['reserved', getArrayDecoder(getU8Decoder(), { size: 128 })], ]); } diff --git a/clients/rust/jito_tip_router/src/generated/accounts/mod.rs b/clients/rust/jito_tip_router/src/generated/accounts/mod.rs index 6feb3df1..e34e1397 100644 --- a/clients/rust/jito_tip_router/src/generated/accounts/mod.rs +++ b/clients/rust/jito_tip_router/src/generated/accounts/mod.rs @@ -5,6 +5,7 @@ //! pub(crate) mod r#ncn_config; +pub(crate) mod r#tracked_mints; pub(crate) mod r#weight_table; -pub use self::{r#ncn_config::*, r#weight_table::*}; +pub use self::{r#ncn_config::*, r#tracked_mints::*, r#weight_table::*}; diff --git a/clients/rust/jito_tip_router/src/generated/accounts/ncn_config.rs b/clients/rust/jito_tip_router/src/generated/accounts/ncn_config.rs index d1884b28..eaca31b0 100644 --- a/clients/rust/jito_tip_router/src/generated/accounts/ncn_config.rs +++ b/clients/rust/jito_tip_router/src/generated/accounts/ncn_config.rs @@ -7,7 +7,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::pubkey::Pubkey; -use crate::generated::types::{Fees, MintEntry}; +use crate::generated::types::Fees; #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -29,8 +29,6 @@ pub struct NcnConfig { )] pub fee_admin: Pubkey, pub fees: Fees, - #[cfg_attr(feature = "serde", serde(with = "serde_with::As::"))] - pub mint_list: [MintEntry; 64], pub bump: u8, #[cfg_attr(feature = "serde", serde(with = "serde_with::As::"))] pub reserved: [u8; 127], diff --git a/clients/rust/jito_tip_router/src/generated/accounts/tracked_mints.rs b/clients/rust/jito_tip_router/src/generated/accounts/tracked_mints.rs new file mode 100644 index 00000000..332743cf --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/accounts/tracked_mints.rs @@ -0,0 +1,69 @@ +//! 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::MintEntry; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TrackedMints { + pub discriminator: u64, + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub ncn: Pubkey, + pub bump: u8, + pub reserved: [u8; 7], + #[cfg_attr(feature = "serde", serde(with = "serde_with::As::"))] + pub st_mint_list: [MintEntry; 64], +} + +impl TrackedMints { + #[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 TrackedMints { + 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 TrackedMints { + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { + Ok(Self::deserialize(buf)?) + } +} + +#[cfg(feature = "anchor")] +impl anchor_lang::AccountSerialize for TrackedMints {} + +#[cfg(feature = "anchor")] +impl anchor_lang::Owner for TrackedMints { + fn owner() -> Pubkey { + crate::JITO_TIP_ROUTER_ID + } +} + +#[cfg(feature = "anchor-idl-build")] +impl anchor_lang::IdlBuild for TrackedMints {} + +#[cfg(feature = "anchor-idl-build")] +impl anchor_lang::Discriminator for TrackedMints { + 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 f8d40e1a..3a3792c2 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 @@ -57,6 +57,12 @@ pub enum JitoTipRouterError { /// 8714 - NCN config vaults are at capacity #[error("NCN config vaults are at capacity")] ConfigMintListFull = 0x220A, + /// 8715 - Tracked mints are at capacity + #[error("Tracked mints are at capacity")] + TrackedMintListFull = 0x220B, + /// 8716 - Vault index already in use by a different mint + #[error("Vault index already in use by a different mint")] + VaultIndexAlreadyInUse = 0x220C, /// 8960 - Fee cap exceeded #[error("Fee cap exceeded")] FeeCapExceeded = 0x2300, diff --git a/clients/rust/jito_tip_router/src/generated/instructions/initialize_tracked_mints.rs b/clients/rust/jito_tip_router/src/generated/instructions/initialize_tracked_mints.rs new file mode 100644 index 00000000..b41c70a1 --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/instructions/initialize_tracked_mints.rs @@ -0,0 +1,437 @@ +//! 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 InitializeTrackedMints { + pub ncn_config: solana_program::pubkey::Pubkey, + + pub tracked_mints: solana_program::pubkey::Pubkey, + + pub ncn: solana_program::pubkey::Pubkey, + + pub payer: solana_program::pubkey::Pubkey, + + pub system_program: solana_program::pubkey::Pubkey, +} + +impl InitializeTrackedMints { + pub fn instruction(&self) -> solana_program::instruction::Instruction { + self.instruction_with_remaining_accounts(&[]) + } + #[allow(clippy::vec_init_then_push)] + pub fn instruction_with_remaining_accounts( + &self, + 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_readonly( + self.ncn_config, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.tracked_mints, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.ncn, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.payer, true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.system_program, + false, + )); + accounts.extend_from_slice(remaining_accounts); + let data = InitializeTrackedMintsInstructionData::new() + .try_to_vec() + .unwrap(); + + solana_program::instruction::Instruction { + program_id: crate::JITO_TIP_ROUTER_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +pub struct InitializeTrackedMintsInstructionData { + discriminator: u8, +} + +impl InitializeTrackedMintsInstructionData { + pub fn new() -> Self { + Self { discriminator: 6 } + } +} + +impl Default for InitializeTrackedMintsInstructionData { + fn default() -> Self { + Self::new() + } +} + +/// Instruction builder for `InitializeTrackedMints`. +/// +/// ### Accounts: +/// +/// 0. `[]` ncn_config +/// 1. `[writable]` tracked_mints +/// 2. `[]` ncn +/// 3. `[writable, signer]` payer +/// 4. `[optional]` system_program (default to `11111111111111111111111111111111`) +#[derive(Clone, Debug, Default)] +pub struct InitializeTrackedMintsBuilder { + ncn_config: Option, + tracked_mints: Option, + ncn: Option, + payer: Option, + system_program: Option, + __remaining_accounts: Vec, +} + +impl InitializeTrackedMintsBuilder { + pub fn new() -> Self { + Self::default() + } + #[inline(always)] + pub fn ncn_config(&mut self, ncn_config: solana_program::pubkey::Pubkey) -> &mut Self { + self.ncn_config = Some(ncn_config); + self + } + #[inline(always)] + pub fn tracked_mints(&mut self, tracked_mints: solana_program::pubkey::Pubkey) -> &mut Self { + self.tracked_mints = Some(tracked_mints); + self + } + #[inline(always)] + pub fn ncn(&mut self, ncn: solana_program::pubkey::Pubkey) -> &mut Self { + self.ncn = Some(ncn); + self + } + #[inline(always)] + pub fn payer(&mut self, payer: solana_program::pubkey::Pubkey) -> &mut Self { + self.payer = Some(payer); + 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 + } + /// 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 = InitializeTrackedMints { + ncn_config: self.ncn_config.expect("ncn_config is not set"), + tracked_mints: self.tracked_mints.expect("tracked_mints is not set"), + ncn: self.ncn.expect("ncn is not set"), + payer: self.payer.expect("payer is not set"), + system_program: self + .system_program + .unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")), + }; + + accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) + } +} + +/// `initialize_tracked_mints` CPI accounts. +pub struct InitializeTrackedMintsCpiAccounts<'a, 'b> { + pub ncn_config: &'b solana_program::account_info::AccountInfo<'a>, + + pub tracked_mints: &'b solana_program::account_info::AccountInfo<'a>, + + pub ncn: &'b solana_program::account_info::AccountInfo<'a>, + + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `initialize_tracked_mints` CPI instruction. +pub struct InitializeTrackedMintsCpi<'a, 'b> { + /// The program to invoke. + pub __program: &'b solana_program::account_info::AccountInfo<'a>, + + pub ncn_config: &'b solana_program::account_info::AccountInfo<'a>, + + pub tracked_mints: &'b solana_program::account_info::AccountInfo<'a>, + + pub ncn: &'b solana_program::account_info::AccountInfo<'a>, + + pub payer: &'b solana_program::account_info::AccountInfo<'a>, + + pub system_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +impl<'a, 'b> InitializeTrackedMintsCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: InitializeTrackedMintsCpiAccounts<'a, 'b>, + ) -> Self { + Self { + __program: program, + ncn_config: accounts.ncn_config, + tracked_mints: accounts.tracked_mints, + ncn: accounts.ncn, + payer: accounts.payer, + system_program: accounts.system_program, + } + } + #[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_readonly( + *self.ncn_config.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.tracked_mints.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.ncn.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.payer.key, + true, + )); + 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 data = InitializeTrackedMintsInstructionData::new() + .try_to_vec() + .unwrap(); + + 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.ncn_config.clone()); + account_infos.push(self.tracked_mints.clone()); + account_infos.push(self.ncn.clone()); + account_infos.push(self.payer.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 `InitializeTrackedMints` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[]` ncn_config +/// 1. `[writable]` tracked_mints +/// 2. `[]` ncn +/// 3. `[writable, signer]` payer +/// 4. `[]` system_program +#[derive(Clone, Debug)] +pub struct InitializeTrackedMintsCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> InitializeTrackedMintsCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(InitializeTrackedMintsCpiBuilderInstruction { + __program: program, + ncn_config: None, + tracked_mints: None, + ncn: None, + payer: None, + system_program: None, + __remaining_accounts: Vec::new(), + }); + Self { instruction } + } + #[inline(always)] + pub fn ncn_config( + &mut self, + ncn_config: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.ncn_config = Some(ncn_config); + self + } + #[inline(always)] + pub fn tracked_mints( + &mut self, + tracked_mints: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.tracked_mints = Some(tracked_mints); + self + } + #[inline(always)] + pub fn ncn(&mut self, ncn: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.ncn = Some(ncn); + self + } + #[inline(always)] + pub fn payer(&mut self, payer: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.payer = Some(payer); + self + } + #[inline(always)] + pub fn system_program( + &mut self, + system_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.system_program = Some(system_program); + 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 instruction = InitializeTrackedMintsCpi { + __program: self.instruction.__program, + + ncn_config: self.instruction.ncn_config.expect("ncn_config is not set"), + + tracked_mints: self + .instruction + .tracked_mints + .expect("tracked_mints is not set"), + + ncn: self.instruction.ncn.expect("ncn is not set"), + + payer: self.instruction.payer.expect("payer is not set"), + + system_program: self + .instruction + .system_program + .expect("system_program is not set"), + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +#[derive(Clone, Debug)] +struct InitializeTrackedMintsCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + ncn_config: Option<&'b solana_program::account_info::AccountInfo<'a>>, + tracked_mints: Option<&'b solana_program::account_info::AccountInfo<'a>>, + ncn: Option<&'b solana_program::account_info::AccountInfo<'a>>, + payer: Option<&'b solana_program::account_info::AccountInfo<'a>>, + system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>, + /// 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/mod.rs b/clients/rust/jito_tip_router/src/generated/instructions/mod.rs index 812f5ead..9608d457 100644 --- a/clients/rust/jito_tip_router/src/generated/instructions/mod.rs +++ b/clients/rust/jito_tip_router/src/generated/instructions/mod.rs @@ -6,12 +6,13 @@ pub(crate) mod r#admin_update_weight_table; pub(crate) mod r#initialize_n_c_n_config; +pub(crate) mod r#initialize_tracked_mints; pub(crate) mod r#initialize_weight_table; pub(crate) mod r#register_mint; pub(crate) mod r#set_config_fees; pub(crate) mod r#set_new_admin; pub use self::{ - r#admin_update_weight_table::*, r#initialize_n_c_n_config::*, r#initialize_weight_table::*, - r#register_mint::*, r#set_config_fees::*, r#set_new_admin::*, + r#admin_update_weight_table::*, r#initialize_n_c_n_config::*, r#initialize_tracked_mints::*, + r#initialize_weight_table::*, r#register_mint::*, r#set_config_fees::*, r#set_new_admin::*, }; diff --git a/clients/rust/jito_tip_router/src/generated/instructions/register_mint.rs b/clients/rust/jito_tip_router/src/generated/instructions/register_mint.rs index b3847c7b..667a722e 100644 --- a/clients/rust/jito_tip_router/src/generated/instructions/register_mint.rs +++ b/clients/rust/jito_tip_router/src/generated/instructions/register_mint.rs @@ -10,7 +10,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; pub struct RegisterMint { pub restaking_config: solana_program::pubkey::Pubkey, - pub ncn_config: solana_program::pubkey::Pubkey, + pub tracked_mints: solana_program::pubkey::Pubkey, pub ncn: solana_program::pubkey::Pubkey, @@ -40,7 +40,7 @@ impl RegisterMint { false, )); accounts.push(solana_program::instruction::AccountMeta::new( - self.ncn_config, + self.tracked_mints, false, )); accounts.push(solana_program::instruction::AccountMeta::new_readonly( @@ -98,7 +98,7 @@ impl Default for RegisterMintInstructionData { /// ### Accounts: /// /// 0. `[]` restaking_config -/// 1. `[writable]` ncn_config +/// 1. `[writable]` tracked_mints /// 2. `[]` ncn /// 3. `[]` vault /// 4. `[]` vault_ncn_ticket @@ -108,7 +108,7 @@ impl Default for RegisterMintInstructionData { #[derive(Clone, Debug, Default)] pub struct RegisterMintBuilder { restaking_config: Option, - ncn_config: Option, + tracked_mints: Option, ncn: Option, vault: Option, vault_ncn_ticket: Option, @@ -131,8 +131,8 @@ impl RegisterMintBuilder { self } #[inline(always)] - pub fn ncn_config(&mut self, ncn_config: solana_program::pubkey::Pubkey) -> &mut Self { - self.ncn_config = Some(ncn_config); + pub fn tracked_mints(&mut self, tracked_mints: solana_program::pubkey::Pubkey) -> &mut Self { + self.tracked_mints = Some(tracked_mints); self } #[inline(always)] @@ -199,7 +199,7 @@ impl RegisterMintBuilder { pub fn instruction(&self) -> solana_program::instruction::Instruction { let accounts = RegisterMint { restaking_config: self.restaking_config.expect("restaking_config is not set"), - ncn_config: self.ncn_config.expect("ncn_config is not set"), + tracked_mints: self.tracked_mints.expect("tracked_mints is not set"), ncn: self.ncn.expect("ncn is not set"), vault: self.vault.expect("vault is not set"), vault_ncn_ticket: self.vault_ncn_ticket.expect("vault_ncn_ticket is not set"), @@ -218,7 +218,7 @@ impl RegisterMintBuilder { pub struct RegisterMintCpiAccounts<'a, 'b> { pub restaking_config: &'b solana_program::account_info::AccountInfo<'a>, - pub ncn_config: &'b solana_program::account_info::AccountInfo<'a>, + pub tracked_mints: &'b solana_program::account_info::AccountInfo<'a>, pub ncn: &'b solana_program::account_info::AccountInfo<'a>, @@ -240,7 +240,7 @@ pub struct RegisterMintCpi<'a, 'b> { pub restaking_config: &'b solana_program::account_info::AccountInfo<'a>, - pub ncn_config: &'b solana_program::account_info::AccountInfo<'a>, + pub tracked_mints: &'b solana_program::account_info::AccountInfo<'a>, pub ncn: &'b solana_program::account_info::AccountInfo<'a>, @@ -263,7 +263,7 @@ impl<'a, 'b> RegisterMintCpi<'a, 'b> { Self { __program: program, restaking_config: accounts.restaking_config, - ncn_config: accounts.ncn_config, + tracked_mints: accounts.tracked_mints, ncn: accounts.ncn, vault: accounts.vault, vault_ncn_ticket: accounts.vault_ncn_ticket, @@ -311,7 +311,7 @@ impl<'a, 'b> RegisterMintCpi<'a, 'b> { false, )); accounts.push(solana_program::instruction::AccountMeta::new( - *self.ncn_config.key, + *self.tracked_mints.key, false, )); accounts.push(solana_program::instruction::AccountMeta::new_readonly( @@ -355,7 +355,7 @@ impl<'a, 'b> RegisterMintCpi<'a, 'b> { let mut account_infos = Vec::with_capacity(8 + 1 + remaining_accounts.len()); account_infos.push(self.__program.clone()); account_infos.push(self.restaking_config.clone()); - account_infos.push(self.ncn_config.clone()); + account_infos.push(self.tracked_mints.clone()); account_infos.push(self.ncn.clone()); account_infos.push(self.vault.clone()); account_infos.push(self.vault_ncn_ticket.clone()); @@ -379,7 +379,7 @@ impl<'a, 'b> RegisterMintCpi<'a, 'b> { /// ### Accounts: /// /// 0. `[]` restaking_config -/// 1. `[writable]` ncn_config +/// 1. `[writable]` tracked_mints /// 2. `[]` ncn /// 3. `[]` vault /// 4. `[]` vault_ncn_ticket @@ -396,7 +396,7 @@ impl<'a, 'b> RegisterMintCpiBuilder<'a, 'b> { let instruction = Box::new(RegisterMintCpiBuilderInstruction { __program: program, restaking_config: None, - ncn_config: None, + tracked_mints: None, ncn: None, vault: None, vault_ncn_ticket: None, @@ -416,11 +416,11 @@ impl<'a, 'b> RegisterMintCpiBuilder<'a, 'b> { self } #[inline(always)] - pub fn ncn_config( + pub fn tracked_mints( &mut self, - ncn_config: &'b solana_program::account_info::AccountInfo<'a>, + tracked_mints: &'b solana_program::account_info::AccountInfo<'a>, ) -> &mut Self { - self.instruction.ncn_config = Some(ncn_config); + self.instruction.tracked_mints = Some(tracked_mints); self } #[inline(always)] @@ -514,7 +514,10 @@ impl<'a, 'b> RegisterMintCpiBuilder<'a, 'b> { .restaking_config .expect("restaking_config is not set"), - ncn_config: self.instruction.ncn_config.expect("ncn_config is not set"), + tracked_mints: self + .instruction + .tracked_mints + .expect("tracked_mints is not set"), ncn: self.instruction.ncn.expect("ncn is not set"), @@ -551,7 +554,7 @@ impl<'a, 'b> RegisterMintCpiBuilder<'a, 'b> { struct RegisterMintCpiBuilderInstruction<'a, 'b> { __program: &'b solana_program::account_info::AccountInfo<'a>, restaking_config: Option<&'b solana_program::account_info::AccountInfo<'a>>, - ncn_config: Option<&'b solana_program::account_info::AccountInfo<'a>>, + tracked_mints: Option<&'b solana_program::account_info::AccountInfo<'a>>, ncn: Option<&'b solana_program::account_info::AccountInfo<'a>>, vault: Option<&'b solana_program::account_info::AccountInfo<'a>>, vault_ncn_ticket: Option<&'b solana_program::account_info::AccountInfo<'a>>, diff --git a/clients/rust/jito_tip_router/src/generated/types/mint_entry.rs b/clients/rust/jito_tip_router/src/generated/types/mint_entry.rs index 4b789100..2667883b 100644 --- a/clients/rust/jito_tip_router/src/generated/types/mint_entry.rs +++ b/clients/rust/jito_tip_router/src/generated/types/mint_entry.rs @@ -14,6 +14,11 @@ pub struct MintEntry { feature = "serde", serde(with = "serde_with::As::") )] - pub mint: Pubkey, + pub st_mint: Pubkey, pub vault_index: u64, + pub weight: u128, + pub slot_set: u64, + pub slot_updated: u64, + #[cfg_attr(feature = "serde", serde(with = "serde_with::As::"))] + pub reserved: [u8; 128], } diff --git a/core/src/discriminators.rs b/core/src/discriminators.rs index 18f0d317..2196c1af 100644 --- a/core/src/discriminators.rs +++ b/core/src/discriminators.rs @@ -2,4 +2,5 @@ pub enum Discriminators { Config = 1, WeightTable = 2, + TrackedMints = 3, } diff --git a/core/src/error.rs b/core/src/error.rs index bf430c1e..39958c24 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -36,6 +36,10 @@ pub enum TipRouterError { ConfigMintsNotUpdated, #[error("NCN config vaults are at capacity")] ConfigMintListFull, + #[error("Tracked mints are at capacity")] + TrackedMintListFull, + #[error("Vault index already in use by a different mint")] + VaultIndexAlreadyInUse, #[error("Fee cap exceeded")] FeeCapExceeded = 0x2300, #[error("Incorrect NCN Admin")] diff --git a/core/src/instruction.rs b/core/src/instruction.rs index a29ba963..76d2f65c 100644 --- a/core/src/instruction.rs +++ b/core/src/instruction.rs @@ -76,7 +76,7 @@ pub enum WeightTableInstruction { /// Registers a mint with the NCN config #[account(0, name = "restaking_config")] - #[account(1, writable, name = "ncn_config")] + #[account(1, writable, name = "tracked_mints")] #[account(2, name = "ncn")] #[account(3, name = "vault")] #[account(4, name = "vault_ncn_ticket")] @@ -84,4 +84,12 @@ pub enum WeightTableInstruction { #[account(6, name = "restaking_program_id")] #[account(7, name = "vault_program_id")] RegisterMint, + + /// Initializes the tracked mints account for an NCN + #[account(0, name = "ncn_config")] + #[account(1, writable, name = "tracked_mints")] + #[account(2, name = "ncn")] + #[account(3, writable, signer, name = "payer")] + #[account(4, name = "system_program")] + InitializeTrackedMints, } diff --git a/core/src/lib.rs b/core/src/lib.rs index a75de933..817bbadc 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -3,6 +3,7 @@ pub mod error; pub mod fees; pub mod instruction; pub mod ncn_config; +pub mod tracked_mints; pub mod weight_entry; pub mod weight_table; diff --git a/core/src/ncn_config.rs b/core/src/ncn_config.rs index 9a091364..63bd08d0 100644 --- a/core/src/ncn_config.rs +++ b/core/src/ncn_config.rs @@ -1,38 +1,10 @@ -use std::collections::HashSet; - use bytemuck::{Pod, Zeroable}; -use jito_bytemuck::{types::PodU64, AccountDeserialize, Discriminator}; +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)] -#[repr(C)] -pub struct MintEntry { - mint: Pubkey, - vault_index: PodU64, -} - -impl MintEntry { - pub fn new(mint: Pubkey, vault_index: u64) -> Self { - Self { - mint, - vault_index: PodU64::from(vault_index), - } - } - - pub fn vault_index(&self) -> u64 { - self.vault_index.into() - } -} - -impl Default for MintEntry { - fn default() -> Self { - Self::new(Pubkey::default(), 0) - } -} - #[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod, AccountDeserialize, ShankAccount)] #[repr(C)] pub struct NcnConfig { @@ -45,9 +17,6 @@ pub struct NcnConfig { pub fees: Fees, - /// List of mints associated with this NCN - mint_list: [MintEntry; 64], // TODO is this the right size - /// Bump seed for the PDA pub bump: u8, // /// Reserved space @@ -59,13 +28,17 @@ impl Discriminator for NcnConfig { } impl NcnConfig { - pub fn new(ncn: Pubkey, tie_breaker_admin: Pubkey, fee_admin: Pubkey, fees: Fees) -> Self { + pub const fn new( + ncn: Pubkey, + tie_breaker_admin: Pubkey, + fee_admin: Pubkey, + fees: Fees, + ) -> Self { Self { ncn, tie_breaker_admin, fee_admin, fees, - mint_list: [MintEntry::default(); 64], bump: 0, reserved: [0; 127], } @@ -76,7 +49,7 @@ impl NcnConfig { } 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 seeds = Self::seeds(ncn); let (address, bump) = Pubkey::find_program_address( &seeds.iter().map(|s| s.as_slice()).collect::>(), program_id, @@ -84,45 +57,6 @@ impl NcnConfig { (address, bump, seeds) } - pub fn add_mint(&mut self, mint: Pubkey, vault_index: u64) -> Result<(), ProgramError> { - // Check if (mint, vault_index) is already in the list - if self - .mint_list - .iter() - .any(|m| m.mint == mint && m.vault_index() == vault_index) - { - return Ok(()); - } - - // Insert at the first empty slot - let mint_entry = self - .mint_list - .iter_mut() - .find(|m| m.mint == MintEntry::default().mint) - .ok_or(ProgramError::InvalidAccountData)?; // TODO list already full error - - *mint_entry = MintEntry::new(mint, vault_index); - Ok(()) - } - - pub fn mint_count(&self) -> usize { - self.mint_list - .iter() - .filter(|m| m.mint != Pubkey::default()) - .count() - } - - pub fn get_unique_mints(&self) -> Vec { - let mut unique_mints: HashSet = HashSet::new(); - self.mint_list - .iter() - .filter(|m| m.mint != Pubkey::default()) - .for_each(|m| { - unique_mints.insert(m.mint); - }); - unique_mints.into_iter().collect() - } - /// Loads the NCN [`Config`] account /// /// # Arguments diff --git a/core/src/tracked_mints.rs b/core/src/tracked_mints.rs new file mode 100644 index 00000000..c9f3bb2c --- /dev/null +++ b/core/src/tracked_mints.rs @@ -0,0 +1,224 @@ +use std::collections::HashSet; + +use bytemuck::{Pod, Zeroable}; +use jito_bytemuck::{types::PodU64, AccountDeserialize, Discriminator}; +use shank::{ShankAccount, ShankType}; +use solana_program::{account_info::AccountInfo, msg, program_error::ProgramError, pubkey::Pubkey}; + +use crate::{discriminators::Discriminators, error::TipRouterError}; + +#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod)] +#[repr(C)] +pub struct MintEntry { + st_mint: Pubkey, + vault_index: PodU64, +} + +impl MintEntry { + pub fn new(mint: Pubkey, vault_index: u64) -> Self { + Self { + st_mint: mint, + vault_index: PodU64::from(vault_index), + } + } + + pub fn vault_index(&self) -> u64 { + self.vault_index.into() + } +} + +impl Default for MintEntry { + fn default() -> Self { + Self::new(Pubkey::default(), u64::MAX) + } +} + +#[derive(Debug, Clone, Copy, Zeroable, ShankType, Pod, AccountDeserialize, ShankAccount)] +#[repr(C)] +pub struct TrackedMints { + pub ncn: Pubkey, + pub bump: u8, + pub reserved: [u8; 7], + pub st_mint_list: [MintEntry; 64], +} + +impl Discriminator for TrackedMints { + const DISCRIMINATOR: u8 = Discriminators::TrackedMints as u8; +} + +impl TrackedMints { + pub fn new(ncn: Pubkey, bump: u8) -> Self { + Self { + ncn, + bump, + reserved: [0; 7], + st_mint_list: [MintEntry::default(); 64], + } + } + + pub fn seeds(ncn: &Pubkey) -> Vec> { + Vec::from_iter( + [b"tracked_mints".to_vec(), ncn.to_bytes().to_vec()] + .iter() + .cloned(), + ) + } + + pub fn find_program_address(program_id: &Pubkey, ncn: &Pubkey) -> (Pubkey, u8, Vec>) { + let seeds = Self::seeds(ncn); + let seeds_iter: Vec<_> = seeds.iter().map(|s| s.as_slice()).collect(); + let (address, bump) = Pubkey::find_program_address(&seeds_iter, program_id); + (address, bump, seeds) + } + + pub fn add_mint(&mut self, mint: Pubkey, vault_index: u64) -> Result<(), ProgramError> { + // Check if (mint, vault_index) is already in the list + if self + .st_mint_list + .iter() + .any(|m| m.st_mint == mint && m.vault_index() == vault_index) + { + return Ok(()); + } + + // Check if vault_index is already in use by a different mint + if self + .st_mint_list + .iter() + .any(|m| m.vault_index() == vault_index) + { + return Err(TipRouterError::VaultIndexAlreadyInUse.into()); + } + + // Insert at the first empty slot + let mint_entry = self + .st_mint_list + .iter_mut() + .find(|m| m.st_mint == MintEntry::default().st_mint) + .ok_or(TipRouterError::TrackedMintListFull)?; + + *mint_entry = MintEntry::new(mint, vault_index); + Ok(()) + } + + pub fn mint_count(&self) -> usize { + self.st_mint_list + .iter() + .filter(|m| m.st_mint != Pubkey::default()) + .count() + } + + pub fn get_unique_mints(&self) -> Vec { + let mut unique_mints: HashSet = HashSet::new(); + self.st_mint_list + .iter() + .filter(|m| m.st_mint != Pubkey::default()) + .for_each(|m| { + unique_mints.insert(m.st_mint); + }); + unique_mints.into_iter().collect() + } + + pub fn load( + program_id: &Pubkey, + ncn: &Pubkey, + account: &AccountInfo, + expect_writable: bool, + ) -> Result<(), ProgramError> { + if account.owner.ne(program_id) { + msg!("Tracked Mints account has an invalid owner"); + return Err(ProgramError::InvalidAccountOwner); + } + + if account.data_is_empty() { + msg!("Tracked Mints account data is empty"); + return Err(ProgramError::InvalidAccountData); + } + + if expect_writable && !account.is_writable { + msg!("Tracked Mints account is not writable"); + return Err(ProgramError::InvalidAccountData); + } + + if account.data.borrow()[0].ne(&Self::DISCRIMINATOR) { + msg!("Tracked Mints account discriminator is invalid"); + return Err(ProgramError::InvalidAccountData); + } + + if account + .key + .ne(&Self::find_program_address(program_id, ncn).0) + { + msg!("Tracked Mints account is not at the correct PDA"); + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add_mint() { + let mut tracked_mints = TrackedMints::new(Pubkey::default(), 0); + let mint = Pubkey::new_unique(); + + assert_eq!(tracked_mints.mint_count(), 0); + tracked_mints.add_mint(mint, 0).unwrap(); + assert_eq!(tracked_mints.mint_count(), 1); + + // Adding same mint with different vault index should succeed + tracked_mints.add_mint(mint, 1).unwrap(); + assert_eq!(tracked_mints.mint_count(), 2); + + // Adding same mint with same vault index should succeed but do nothing + tracked_mints.add_mint(mint, 1).unwrap(); + assert_eq!(tracked_mints.mint_count(), 2); + + // Adding different mint with same vault index should fail + let mint2 = Pubkey::new_unique(); + assert!(tracked_mints.add_mint(mint2, 1).is_err()); + + // Adding to a full list should fail + for i in tracked_mints.mint_count()..tracked_mints.st_mint_list.len() { + tracked_mints + .add_mint(Pubkey::new_unique(), i as u64) + .unwrap(); + } + assert!(tracked_mints.add_mint(Pubkey::new_unique(), 0).is_err()); + } + + #[test] + fn test_mint_count() { + let mut tracked_mints = TrackedMints::new(Pubkey::default(), 0); + assert_eq!(tracked_mints.mint_count(), 0); + + for i in 0..3 { + tracked_mints.add_mint(Pubkey::new_unique(), i).unwrap(); + } + assert_eq!(tracked_mints.mint_count(), 3); + } + + #[test] + fn test_get_unique_mints() { + let mut tracked_mints = TrackedMints::new(Pubkey::default(), 0); + + let mint1 = Pubkey::new_unique(); + let mint2 = Pubkey::new_unique(); + tracked_mints.add_mint(mint1, 0).unwrap(); + tracked_mints.add_mint(mint2, 1).unwrap(); + tracked_mints.add_mint(mint1, 2).unwrap(); + + let unique_mints = tracked_mints.get_unique_mints(); + assert_eq!(unique_mints.len(), 2); + assert!(unique_mints.contains(&mint1)); + assert!(unique_mints.contains(&mint2)); + + // Default pubkeys should not be included + let empty_tracked_mints = TrackedMints::new(Pubkey::default(), 0); + assert_eq!(empty_tracked_mints.get_unique_mints().len(), 0); + } +} diff --git a/idl/jito_tip_router.json b/idl/jito_tip_router.json index ebca9e67..d2d68967 100644 --- a/idl/jito_tip_router.json +++ b/idl/jito_tip_router.json @@ -272,7 +272,7 @@ "isSigner": false }, { - "name": "ncnConfig", + "name": "trackedMints", "isMut": true, "isSigner": false }, @@ -312,6 +312,41 @@ "type": "u8", "value": 5 } + }, + { + "name": "InitializeTrackedMints", + "accounts": [ + { + "name": "ncnConfig", + "isMut": false, + "isSigner": false + }, + { + "name": "trackedMints", + "isMut": true, + "isSigner": false + }, + { + "name": "ncn", + "isMut": false, + "isSigner": false + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [], + "discriminant": { + "type": "u8", + "value": 6 + } } ], "accounts": [ @@ -339,15 +374,29 @@ } }, { - "name": "mintList", + "name": "bump", + "type": "u8" + }, + { + "name": "reserved", "type": { "array": [ - { - "defined": "MintEntry" - }, - 64 + "u8", + 127 ] } + } + ] + } + }, + { + "name": "TrackedMints", + "type": { + "kind": "struct", + "fields": [ + { + "name": "ncn", + "type": "publicKey" }, { "name": "bump", @@ -358,7 +407,18 @@ "type": { "array": [ "u8", - 127 + 7 + ] + } + }, + { + "name": "stMintList", + "type": { + "array": [ + { + "defined": "MintEntry" + }, + 64 ] } } @@ -477,7 +537,7 @@ "kind": "struct", "fields": [ { - "name": "mint", + "name": "stMint", "type": "publicKey" }, { @@ -485,6 +545,33 @@ "type": { "defined": "PodU64" } + }, + { + "name": "weight", + "type": { + "defined": "PodU128" + } + }, + { + "name": "slotSet", + "type": { + "defined": "PodU64" + } + }, + { + "name": "slotUpdated", + "type": { + "defined": "PodU64" + } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 128 + ] + } } ] } @@ -624,6 +711,16 @@ "name": "ConfigMintListFull", "msg": "NCN config vaults are at capacity" }, + { + "code": 8715, + "name": "TrackedMintListFull", + "msg": "Tracked mints are at capacity" + }, + { + "code": 8716, + "name": "VaultIndexAlreadyInUse", + "msg": "Vault index already in use by a different mint" + }, { "code": 8960, "name": "FeeCapExceeded", diff --git a/program/src/initialize_tracked_mints.rs b/program/src/initialize_tracked_mints.rs new file mode 100644 index 00000000..84bcd3b7 --- /dev/null +++ b/program/src/initialize_tracked_mints.rs @@ -0,0 +1,53 @@ +use jito_bytemuck::{AccountDeserialize, Discriminator}; +use jito_jsm_core::{ + create_account, + loader::{load_system_account, load_system_program}, +}; +use jito_tip_router_core::{ncn_config::NcnConfig, tracked_mints::TrackedMints}; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, rent::Rent, sysvar::Sysvar, +}; + +pub fn process_initialize_tracked_mints( + program_id: &Pubkey, + accounts: &[AccountInfo], +) -> ProgramResult { + let [ncn_config, tracked_mints, ncn_account, payer, system_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + // Verify accounts + load_system_account(tracked_mints, true)?; + load_system_program(system_program)?; + + NcnConfig::load(program_id, ncn_account.key, ncn_config, false)?; + + let (tracked_mints_pda, tracked_mints_bump, mut tracked_mints_seeds) = + TrackedMints::find_program_address(program_id, ncn_account.key); + tracked_mints_seeds.push(vec![tracked_mints_bump]); + + if tracked_mints_pda != *tracked_mints.key { + return Err(ProgramError::InvalidSeeds); + } + + create_account( + payer, + tracked_mints, + system_program, + program_id, + &Rent::get()?, + 8_u64 + .checked_add(std::mem::size_of::() as u64) + .unwrap(), + &tracked_mints_seeds, + )?; + + let mut tracked_mints_data = tracked_mints.try_borrow_mut_data()?; + tracked_mints_data[0] = TrackedMints::DISCRIMINATOR; + let tracked_mints_account = + TrackedMints::try_from_slice_unchecked_mut(&mut tracked_mints_data)?; + *tracked_mints_account = TrackedMints::new(*ncn_account.key, tracked_mints_bump); + + Ok(()) +} diff --git a/program/src/initialize_weight_table.rs b/program/src/initialize_weight_table.rs index d3a28fba..f893b63e 100644 --- a/program/src/initialize_weight_table.rs +++ b/program/src/initialize_weight_table.rs @@ -7,7 +7,7 @@ use jito_jsm_core::{ }; use jito_restaking_core::{config::Config, ncn::Ncn}; use jito_tip_router_core::{ - error::TipRouterError, ncn_config::NcnConfig, weight_table::WeightTable, + error::TipRouterError, tracked_mints::TrackedMints, weight_table::WeightTable, }; use solana_program::{ account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, msg, @@ -21,13 +21,13 @@ pub fn process_initialize_weight_table( accounts: &[AccountInfo], first_slot_of_ncn_epoch: Option, ) -> ProgramResult { - let [restaking_config, ncn_config, ncn, weight_table, payer, restaking_program_id, system_program] = + let [restaking_config, tracked_mints, ncn, weight_table, payer, restaking_program_id, system_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; - NcnConfig::load(program_id, ncn.key, ncn_config, false)?; + TrackedMints::load(program_id, ncn.key, tracked_mints, false)?; Config::load(restaking_program_id.key, restaking_config, false)?; Ncn::load(restaking_program_id.key, ncn, false)?; @@ -43,8 +43,8 @@ pub fn process_initialize_weight_table( ncn.vault_count() }; - let ncn_config_data: std::cell::Ref<'_, &mut [u8]> = ncn_config.data.borrow(); - let ncn_config = NcnConfig::try_from_slice_unchecked(&ncn_config_data)?; + let tracked_mints_data: std::cell::Ref<'_, &mut [u8]> = tracked_mints.data.borrow(); + let tracked_mints = TrackedMints::try_from_slice_unchecked(&tracked_mints_data)?; load_system_account(weight_table, true)?; load_system_program(system_program)?; @@ -79,7 +79,7 @@ pub fn process_initialize_weight_table( return Err(ProgramError::InvalidAccountData); } - if vault_count as usize != ncn_config.mint_count() { + if vault_count as usize != tracked_mints.mint_count() { msg!("Vault count does not match supported mint count"); return Err(ProgramError::InvalidAccountData); } @@ -106,7 +106,7 @@ pub fn process_initialize_weight_table( *weight_table_account = WeightTable::new(*ncn.key, ncn_epoch, current_slot, weight_table_bump); - weight_table_account.initalize_weight_table(&ncn_config.get_unique_mints())?; + weight_table_account.initalize_weight_table(&tracked_mints.get_unique_mints())?; Ok(()) } diff --git a/program/src/lib.rs b/program/src/lib.rs index 34a6155b..68c4a4de 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -1,5 +1,6 @@ mod admin_update_weight_table; mod initialize_ncn_config; +mod initialize_tracked_mints; mod initialize_weight_table; mod register_mint; mod set_config_fees; @@ -19,6 +20,7 @@ use solana_security_txt::security_txt; use crate::{ admin_update_weight_table::process_admin_update_weight_table, initialize_ncn_config::process_initialize_ncn_config, + initialize_tracked_mints::process_initialize_tracked_mints, initialize_weight_table::process_initialize_weight_table, register_mint::process_register_mint, set_config_fees::process_set_config_fees, }; @@ -106,5 +108,9 @@ pub fn process_instruction( msg!("Instruction: RegisterMint"); process_register_mint(program_id, accounts) } + WeightTableInstruction::InitializeTrackedMints => { + msg!("Instruction: InitializeTrackedMints"); + process_initialize_tracked_mints(program_id, accounts) + } } } diff --git a/program/src/register_mint.rs b/program/src/register_mint.rs index ef6c2ddc..1818f5c0 100644 --- a/program/src/register_mint.rs +++ b/program/src/register_mint.rs @@ -1,6 +1,6 @@ use jito_bytemuck::AccountDeserialize; use jito_restaking_core::{config::Config, ncn::Ncn, ncn_vault_ticket::NcnVaultTicket}; -use jito_tip_router_core::ncn_config::NcnConfig; +use jito_tip_router_core::tracked_mints::TrackedMints; use jito_vault_core::{vault::Vault, vault_ncn_ticket::VaultNcnTicket}; use solana_program::{ account_info::AccountInfo, @@ -12,13 +12,13 @@ use solana_program::{ }; pub fn process_register_mint(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { - let [restaking_config, ncn_config, ncn, vault, vault_ncn_ticket, ncn_vault_ticket, restaking_program_id, vault_program_id] = + let [restaking_config, tracked_mints, ncn, vault, vault_ncn_ticket, ncn_vault_ticket, restaking_program_id, vault_program_id] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; - NcnConfig::load(program_id, ncn.key, ncn_config, true)?; + TrackedMints::load(program_id, ncn.key, tracked_mints, true)?; Ncn::load(restaking_program_id.key, ncn, false)?; Vault::load(vault_program_id.key, vault, false)?; VaultNcnTicket::load(vault_program_id.key, vault_ncn_ticket, vault, ncn, false)?; @@ -61,9 +61,9 @@ pub fn process_register_mint(program_id: &Pubkey, accounts: &[AccountInfo]) -> P let vault_data = vault.data.borrow(); let vault = Vault::try_from_slice_unchecked(&vault_data)?; - let mut ncn_config_data = ncn_config.try_borrow_mut_data()?; - let ncn_config = NcnConfig::try_from_slice_unchecked_mut(&mut ncn_config_data)?; - ncn_config.add_mint(vault.supported_mint, vault.vault_index())?; + let mut tracked_mints_data = tracked_mints.try_borrow_mut_data()?; + let tracked_mints = TrackedMints::try_from_slice_unchecked_mut(&mut tracked_mints_data)?; + tracked_mints.add_mint(vault.supported_mint, vault.vault_index())?; Ok(()) }