From 60b388421be855466c7bed191edbea8f449c8a88 Mon Sep 17 00:00:00 2001 From: Evan B Date: Fri, 25 Oct 2024 18:45:09 -0400 Subject: [PATCH] Set admin for config, in restaking and vault (#152) Co-authored-by: Coach Chuck <169060940+coachchucksol@users.noreply.github.com> --- .../restaking_client/errors/jitoRestaking.ts | 4 + .../js/restaking_client/instructions/index.ts | 1 + .../instructions/setConfigAdmin.ts | 183 +++++++++ .../programs/jitoRestaking.ts | 10 +- clients/js/vault_client/errors/jitoVault.ts | 16 +- clients/js/vault_client/instructions/index.ts | 1 + .../instructions/setConfigAdmin.ts | 183 +++++++++ clients/js/vault_client/programs/jitoVault.ts | 10 +- .../src/generated/errors/jito_restaking.rs | 3 + .../src/generated/instructions/mod.rs | 5 +- .../instructions/set_config_admin.rs | 358 ++++++++++++++++++ .../src/generated/errors/jito_vault.rs | 12 +- .../src/generated/instructions/mod.rs | 9 +- .../instructions/set_config_admin.rs | 358 ++++++++++++++++++ idl/jito_restaking.json | 30 ++ idl/jito_vault.json | 33 +- .../tests/fixtures/restaking_client.rs | 27 +- .../tests/fixtures/vault_client.rs | 24 ++ integration_tests/tests/restaking/mod.rs | 1 + .../tests/restaking/set_config_admin.rs | 78 ++++ integration_tests/tests/vault/mod.rs | 1 + .../tests/vault/set_config_admin.rs | 73 ++++ integration_tests/tests/vault/set_fees.rs | 2 +- .../tests/vault/set_program_fee_wallet.rs | 2 +- restaking_core/src/config.rs | 4 + restaking_program/src/lib.rs | 7 +- restaking_program/src/set_config_admin.rs | 27 ++ restaking_sdk/src/error.rs | 3 + restaking_sdk/src/instruction.rs | 6 + restaking_sdk/src/sdk.rs | 18 + vault_core/src/config.rs | 5 + vault_program/src/lib.rs | 9 +- vault_program/src/set_config_admin.rs | 27 ++ vault_program/src/set_program_fee.rs | 2 +- vault_program/src/set_program_fee_wallet.rs | 2 +- vault_sdk/src/error.rs | 8 +- vault_sdk/src/instruction.rs | 5 + vault_sdk/src/sdk.rs | 18 + 38 files changed, 1527 insertions(+), 38 deletions(-) create mode 100644 clients/js/restaking_client/instructions/setConfigAdmin.ts create mode 100644 clients/js/vault_client/instructions/setConfigAdmin.ts create mode 100644 clients/rust/restaking_client/src/generated/instructions/set_config_admin.rs create mode 100644 clients/rust/vault_client/src/generated/instructions/set_config_admin.rs create mode 100644 integration_tests/tests/restaking/set_config_admin.rs create mode 100644 integration_tests/tests/vault/set_config_admin.rs create mode 100644 restaking_program/src/set_config_admin.rs create mode 100644 vault_program/src/set_config_admin.rs diff --git a/clients/js/restaking_client/errors/jitoRestaking.ts b/clients/js/restaking_client/errors/jitoRestaking.ts index 44743be2..d0da58aa 100644 --- a/clients/js/restaking_client/errors/jitoRestaking.ts +++ b/clients/js/restaking_client/errors/jitoRestaking.ts @@ -64,6 +64,8 @@ export const JITO_RESTAKING_ERROR__VAULT_OVERFLOW = 0x7db; // 2011 export const JITO_RESTAKING_ERROR__SLASHER_OVERFLOW = 0x7dc; // 2012 /** InvalidEpochLength: InvalidEpochLength */ export const JITO_RESTAKING_ERROR__INVALID_EPOCH_LENGTH = 0x7dd; // 2013 +/** ConfigAdminInvalid: ConfigAdminInvalid */ +export const JITO_RESTAKING_ERROR__CONFIG_ADMIN_INVALID = 0x7de; // 2014 /** ArithmeticOverflow: ArithmeticOverflow */ export const JITO_RESTAKING_ERROR__ARITHMETIC_OVERFLOW = 0xbb8; // 3000 /** ArithmeticUnderflow: ArithmeticUnderflow */ @@ -74,6 +76,7 @@ export const JITO_RESTAKING_ERROR__DIVISION_BY_ZERO = 0xbba; // 3002 export type JitoRestakingError = | typeof JITO_RESTAKING_ERROR__ARITHMETIC_OVERFLOW | typeof JITO_RESTAKING_ERROR__ARITHMETIC_UNDERFLOW + | typeof JITO_RESTAKING_ERROR__CONFIG_ADMIN_INVALID | typeof JITO_RESTAKING_ERROR__DIVISION_BY_ZERO | typeof JITO_RESTAKING_ERROR__INVALID_EPOCH_LENGTH | typeof JITO_RESTAKING_ERROR__NCN_ADMIN_INVALID @@ -106,6 +109,7 @@ if (process.env.NODE_ENV !== 'production') { jitoRestakingErrorMessages = { [JITO_RESTAKING_ERROR__ARITHMETIC_OVERFLOW]: `ArithmeticOverflow`, [JITO_RESTAKING_ERROR__ARITHMETIC_UNDERFLOW]: `ArithmeticUnderflow`, + [JITO_RESTAKING_ERROR__CONFIG_ADMIN_INVALID]: `ConfigAdminInvalid`, [JITO_RESTAKING_ERROR__DIVISION_BY_ZERO]: `DivisionByZero`, [JITO_RESTAKING_ERROR__INVALID_EPOCH_LENGTH]: `InvalidEpochLength`, [JITO_RESTAKING_ERROR__NCN_ADMIN_INVALID]: `NcnAdminInvalid`, diff --git a/clients/js/restaking_client/instructions/index.ts b/clients/js/restaking_client/instructions/index.ts index 76b63211..1edfe481 100644 --- a/clients/js/restaking_client/instructions/index.ts +++ b/clients/js/restaking_client/instructions/index.ts @@ -27,6 +27,7 @@ export * from './operatorSetAdmin'; export * from './operatorSetFee'; export * from './operatorSetSecondaryAdmin'; export * from './operatorWarmupNcn'; +export * from './setConfigAdmin'; export * from './warmupNcnVaultSlasherTicket'; export * from './warmupNcnVaultTicket'; export * from './warmupOperatorVaultTicket'; diff --git a/clients/js/restaking_client/instructions/setConfigAdmin.ts b/clients/js/restaking_client/instructions/setConfigAdmin.ts new file mode 100644 index 00000000..64b1a60c --- /dev/null +++ b/clients/js/restaking_client/instructions/setConfigAdmin.ts @@ -0,0 +1,183 @@ +/** + * 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 ReadonlySignerAccount, + type TransactionSigner, + type WritableAccount, +} from '@solana/web3.js'; +import { JITO_RESTAKING_PROGRAM_ADDRESS } from '../programs'; +import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; + +export const SET_CONFIG_ADMIN_DISCRIMINATOR = 24; + +export function getSetConfigAdminDiscriminatorBytes() { + return getU8Encoder().encode(SET_CONFIG_ADMIN_DISCRIMINATOR); +} + +export type SetConfigAdminInstruction< + TProgram extends string = typeof JITO_RESTAKING_PROGRAM_ADDRESS, + TAccountConfig extends string | IAccountMeta = string, + TAccountOldAdmin extends string | IAccountMeta = string, + TAccountNewAdmin extends string | IAccountMeta = string, + TRemainingAccounts extends readonly IAccountMeta[] = [], +> = IInstruction & + IInstructionWithData & + IInstructionWithAccounts< + [ + TAccountConfig extends string + ? WritableAccount + : TAccountConfig, + TAccountOldAdmin extends string + ? ReadonlySignerAccount & + IAccountSignerMeta + : TAccountOldAdmin, + TAccountNewAdmin extends string + ? ReadonlySignerAccount & + IAccountSignerMeta + : TAccountNewAdmin, + ...TRemainingAccounts, + ] + >; + +export type SetConfigAdminInstructionData = { discriminator: number }; + +export type SetConfigAdminInstructionDataArgs = {}; + +export function getSetConfigAdminInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([['discriminator', getU8Encoder()]]), + (value) => ({ ...value, discriminator: SET_CONFIG_ADMIN_DISCRIMINATOR }) + ); +} + +export function getSetConfigAdminInstructionDataDecoder(): Decoder { + return getStructDecoder([['discriminator', getU8Decoder()]]); +} + +export function getSetConfigAdminInstructionDataCodec(): Codec< + SetConfigAdminInstructionDataArgs, + SetConfigAdminInstructionData +> { + return combineCodec( + getSetConfigAdminInstructionDataEncoder(), + getSetConfigAdminInstructionDataDecoder() + ); +} + +export type SetConfigAdminInput< + TAccountConfig extends string = string, + TAccountOldAdmin extends string = string, + TAccountNewAdmin extends string = string, +> = { + config: Address; + oldAdmin: TransactionSigner; + newAdmin: TransactionSigner; +}; + +export function getSetConfigAdminInstruction< + TAccountConfig extends string, + TAccountOldAdmin extends string, + TAccountNewAdmin extends string, +>( + input: SetConfigAdminInput +): SetConfigAdminInstruction< + typeof JITO_RESTAKING_PROGRAM_ADDRESS, + TAccountConfig, + TAccountOldAdmin, + TAccountNewAdmin +> { + // Program address. + const programAddress = JITO_RESTAKING_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + config: { value: input.config ?? null, isWritable: true }, + oldAdmin: { value: input.oldAdmin ?? null, isWritable: false }, + newAdmin: { value: input.newAdmin ?? null, isWritable: false }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + const getAccountMeta = getAccountMetaFactory(programAddress, 'programId'); + const instruction = { + accounts: [ + getAccountMeta(accounts.config), + getAccountMeta(accounts.oldAdmin), + getAccountMeta(accounts.newAdmin), + ], + programAddress, + data: getSetConfigAdminInstructionDataEncoder().encode({}), + } as SetConfigAdminInstruction< + typeof JITO_RESTAKING_PROGRAM_ADDRESS, + TAccountConfig, + TAccountOldAdmin, + TAccountNewAdmin + >; + + return instruction; +} + +export type ParsedSetConfigAdminInstruction< + TProgram extends string = typeof JITO_RESTAKING_PROGRAM_ADDRESS, + TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], +> = { + programAddress: Address; + accounts: { + config: TAccountMetas[0]; + oldAdmin: TAccountMetas[1]; + newAdmin: TAccountMetas[2]; + }; + data: SetConfigAdminInstructionData; +}; + +export function parseSetConfigAdminInstruction< + TProgram extends string, + TAccountMetas extends readonly IAccountMeta[], +>( + instruction: IInstruction & + IInstructionWithAccounts & + IInstructionWithData +): ParsedSetConfigAdminInstruction { + if (instruction.accounts.length < 3) { + // 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(), + oldAdmin: getNextAccount(), + newAdmin: getNextAccount(), + }, + data: getSetConfigAdminInstructionDataDecoder().decode(instruction.data), + }; +} diff --git a/clients/js/restaking_client/programs/jitoRestaking.ts b/clients/js/restaking_client/programs/jitoRestaking.ts index 09a4dc81..df085025 100644 --- a/clients/js/restaking_client/programs/jitoRestaking.ts +++ b/clients/js/restaking_client/programs/jitoRestaking.ts @@ -34,6 +34,7 @@ import { type ParsedOperatorSetFeeInstruction, type ParsedOperatorSetSecondaryAdminInstruction, type ParsedOperatorWarmupNcnInstruction, + type ParsedSetConfigAdminInstruction, type ParsedWarmupNcnVaultSlasherTicketInstruction, type ParsedWarmupNcnVaultTicketInstruction, type ParsedWarmupOperatorVaultTicketInstruction, @@ -77,6 +78,7 @@ export enum JitoRestakingInstruction { OperatorSetFee, NcnDelegateTokenAccount, OperatorDelegateTokenAccount, + SetConfigAdmin, } export function identifyJitoRestakingInstruction( @@ -155,6 +157,9 @@ export function identifyJitoRestakingInstruction( if (containsBytes(data, getU8Encoder().encode(23), 0)) { return JitoRestakingInstruction.OperatorDelegateTokenAccount; } + if (containsBytes(data, getU8Encoder().encode(24), 0)) { + return JitoRestakingInstruction.SetConfigAdmin; + } throw new Error( 'The provided instruction could not be identified as a jitoRestaking instruction.' ); @@ -234,4 +239,7 @@ export type ParsedJitoRestakingInstruction< } & ParsedNcnDelegateTokenAccountInstruction) | ({ instructionType: JitoRestakingInstruction.OperatorDelegateTokenAccount; - } & ParsedOperatorDelegateTokenAccountInstruction); + } & ParsedOperatorDelegateTokenAccountInstruction) + | ({ + instructionType: JitoRestakingInstruction.SetConfigAdmin; + } & ParsedSetConfigAdminInstruction); diff --git a/clients/js/vault_client/errors/jitoVault.ts b/clients/js/vault_client/errors/jitoVault.ts index 38457ae5..0eb12ee9 100644 --- a/clients/js/vault_client/errors/jitoVault.ts +++ b/clients/js/vault_client/errors/jitoVault.ts @@ -40,10 +40,10 @@ export const JITO_VAULT_ERROR__VAULT_SLASHER_ADMIN_INVALID = 0x3f2; // 1010 export const JITO_VAULT_ERROR__VAULT_NCN_ADMIN_INVALID = 0x3f3; // 1011 /** VaultFeeAdminInvalid: VaultFeeAdminInvalid */ export const JITO_VAULT_ERROR__VAULT_FEE_ADMIN_INVALID = 0x3f4; // 1012 -/** VaultConfigAdminInvalid: VaultConfigAdminInvalid */ -export const JITO_VAULT_ERROR__VAULT_CONFIG_ADMIN_INVALID = 0x3f5; // 1013 -/** VaultConfigFeeAdminInvalid: VaultConfigFeeAdminInvalid */ -export const JITO_VAULT_ERROR__VAULT_CONFIG_FEE_ADMIN_INVALID = 0x3f6; // 1014 +/** ConfigAdminInvalid: ConfigAdminInvalid */ +export const JITO_VAULT_ERROR__CONFIG_ADMIN_INVALID = 0x3f5; // 1013 +/** ConfigFeeAdminInvalid: ConfigFeeAdminInvalid */ +export const JITO_VAULT_ERROR__CONFIG_FEE_ADMIN_INVALID = 0x3f6; // 1014 /** VaultFeeCapExceeded: VaultFeeCapExceeded */ export const JITO_VAULT_ERROR__VAULT_FEE_CAP_EXCEEDED = 0x3f7; // 1015 /** VaultFeeChangeTooSoon: VaultFeeChangeTooSoon */ @@ -140,6 +140,8 @@ export const JITO_VAULT_ERROR__DIVISION_BY_ZERO = 0xbba; // 3002 export type JitoVaultError = | typeof JITO_VAULT_ERROR__ARITHMETIC_OVERFLOW | typeof JITO_VAULT_ERROR__ARITHMETIC_UNDERFLOW + | typeof JITO_VAULT_ERROR__CONFIG_ADMIN_INVALID + | typeof JITO_VAULT_ERROR__CONFIG_FEE_ADMIN_INVALID | typeof JITO_VAULT_ERROR__DIVISION_BY_ZERO | typeof JITO_VAULT_ERROR__INVALID_DEPOSITOR | typeof JITO_VAULT_ERROR__INVALID_DEPOSIT_TOKEN_ACCOUNT @@ -158,8 +160,6 @@ export type JitoVaultError = | typeof JITO_VAULT_ERROR__VAULT_BURN_ZERO | typeof JITO_VAULT_ERROR__VAULT_CAPACITY_ADMIN_INVALID | typeof JITO_VAULT_ERROR__VAULT_CAPACITY_EXCEEDED - | typeof JITO_VAULT_ERROR__VAULT_CONFIG_ADMIN_INVALID - | typeof JITO_VAULT_ERROR__VAULT_CONFIG_FEE_ADMIN_INVALID | typeof JITO_VAULT_ERROR__VAULT_COOLDOWN_ZERO | typeof JITO_VAULT_ERROR__VAULT_DELEGATE_ASSET_ADMIN_INVALID | typeof JITO_VAULT_ERROR__VAULT_DELEGATION_ADMIN_INVALID @@ -205,6 +205,8 @@ if (process.env.NODE_ENV !== 'production') { jitoVaultErrorMessages = { [JITO_VAULT_ERROR__ARITHMETIC_OVERFLOW]: `ArithmeticOverflow`, [JITO_VAULT_ERROR__ARITHMETIC_UNDERFLOW]: `ArithmeticUnderflow`, + [JITO_VAULT_ERROR__CONFIG_ADMIN_INVALID]: `ConfigAdminInvalid`, + [JITO_VAULT_ERROR__CONFIG_FEE_ADMIN_INVALID]: `ConfigFeeAdminInvalid`, [JITO_VAULT_ERROR__DIVISION_BY_ZERO]: `DivisionByZero`, [JITO_VAULT_ERROR__INVALID_DEPOSITOR]: `InvalidDepositor`, [JITO_VAULT_ERROR__INVALID_DEPOSIT_TOKEN_ACCOUNT]: `InvalidDepositTokenAccount`, @@ -223,8 +225,6 @@ if (process.env.NODE_ENV !== 'production') { [JITO_VAULT_ERROR__VAULT_BURN_ZERO]: `VaultBurnZero`, [JITO_VAULT_ERROR__VAULT_CAPACITY_ADMIN_INVALID]: `VaultCapacityAdminInvalid`, [JITO_VAULT_ERROR__VAULT_CAPACITY_EXCEEDED]: `VaultCapacityExceeded`, - [JITO_VAULT_ERROR__VAULT_CONFIG_ADMIN_INVALID]: `VaultConfigAdminInvalid`, - [JITO_VAULT_ERROR__VAULT_CONFIG_FEE_ADMIN_INVALID]: `VaultConfigFeeAdminInvalid`, [JITO_VAULT_ERROR__VAULT_COOLDOWN_ZERO]: `VaultCooldownZero`, [JITO_VAULT_ERROR__VAULT_DELEGATE_ASSET_ADMIN_INVALID]: `VaultDelegateAssetAdminInvalid`, [JITO_VAULT_ERROR__VAULT_DELEGATION_ADMIN_INVALID]: `VaultDelegationAdminInvalid`, diff --git a/clients/js/vault_client/instructions/index.ts b/clients/js/vault_client/instructions/index.ts index 361e9333..7bc28cbe 100644 --- a/clients/js/vault_client/instructions/index.ts +++ b/clients/js/vault_client/instructions/index.ts @@ -27,6 +27,7 @@ export * from './initializeVaultUpdateStateTracker'; export * from './initializeVaultWithMint'; export * from './mintTo'; export * from './setAdmin'; +export * from './setConfigAdmin'; export * from './setDepositCapacity'; export * from './setFees'; export * from './setIsPaused'; diff --git a/clients/js/vault_client/instructions/setConfigAdmin.ts b/clients/js/vault_client/instructions/setConfigAdmin.ts new file mode 100644 index 00000000..1b45d5c1 --- /dev/null +++ b/clients/js/vault_client/instructions/setConfigAdmin.ts @@ -0,0 +1,183 @@ +/** + * 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 ReadonlySignerAccount, + type TransactionSigner, + type WritableAccount, +} from '@solana/web3.js'; +import { JITO_VAULT_PROGRAM_ADDRESS } from '../programs'; +import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; + +export const SET_CONFIG_ADMIN_DISCRIMINATOR = 31; + +export function getSetConfigAdminDiscriminatorBytes() { + return getU8Encoder().encode(SET_CONFIG_ADMIN_DISCRIMINATOR); +} + +export type SetConfigAdminInstruction< + TProgram extends string = typeof JITO_VAULT_PROGRAM_ADDRESS, + TAccountConfig extends string | IAccountMeta = string, + TAccountOldAdmin extends string | IAccountMeta = string, + TAccountNewAdmin extends string | IAccountMeta = string, + TRemainingAccounts extends readonly IAccountMeta[] = [], +> = IInstruction & + IInstructionWithData & + IInstructionWithAccounts< + [ + TAccountConfig extends string + ? WritableAccount + : TAccountConfig, + TAccountOldAdmin extends string + ? ReadonlySignerAccount & + IAccountSignerMeta + : TAccountOldAdmin, + TAccountNewAdmin extends string + ? ReadonlySignerAccount & + IAccountSignerMeta + : TAccountNewAdmin, + ...TRemainingAccounts, + ] + >; + +export type SetConfigAdminInstructionData = { discriminator: number }; + +export type SetConfigAdminInstructionDataArgs = {}; + +export function getSetConfigAdminInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([['discriminator', getU8Encoder()]]), + (value) => ({ ...value, discriminator: SET_CONFIG_ADMIN_DISCRIMINATOR }) + ); +} + +export function getSetConfigAdminInstructionDataDecoder(): Decoder { + return getStructDecoder([['discriminator', getU8Decoder()]]); +} + +export function getSetConfigAdminInstructionDataCodec(): Codec< + SetConfigAdminInstructionDataArgs, + SetConfigAdminInstructionData +> { + return combineCodec( + getSetConfigAdminInstructionDataEncoder(), + getSetConfigAdminInstructionDataDecoder() + ); +} + +export type SetConfigAdminInput< + TAccountConfig extends string = string, + TAccountOldAdmin extends string = string, + TAccountNewAdmin extends string = string, +> = { + config: Address; + oldAdmin: TransactionSigner; + newAdmin: TransactionSigner; +}; + +export function getSetConfigAdminInstruction< + TAccountConfig extends string, + TAccountOldAdmin extends string, + TAccountNewAdmin extends string, +>( + input: SetConfigAdminInput +): SetConfigAdminInstruction< + typeof JITO_VAULT_PROGRAM_ADDRESS, + TAccountConfig, + TAccountOldAdmin, + TAccountNewAdmin +> { + // Program address. + const programAddress = JITO_VAULT_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + config: { value: input.config ?? null, isWritable: true }, + oldAdmin: { value: input.oldAdmin ?? null, isWritable: false }, + newAdmin: { value: input.newAdmin ?? null, isWritable: false }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + const getAccountMeta = getAccountMetaFactory(programAddress, 'programId'); + const instruction = { + accounts: [ + getAccountMeta(accounts.config), + getAccountMeta(accounts.oldAdmin), + getAccountMeta(accounts.newAdmin), + ], + programAddress, + data: getSetConfigAdminInstructionDataEncoder().encode({}), + } as SetConfigAdminInstruction< + typeof JITO_VAULT_PROGRAM_ADDRESS, + TAccountConfig, + TAccountOldAdmin, + TAccountNewAdmin + >; + + return instruction; +} + +export type ParsedSetConfigAdminInstruction< + TProgram extends string = typeof JITO_VAULT_PROGRAM_ADDRESS, + TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], +> = { + programAddress: Address; + accounts: { + config: TAccountMetas[0]; + oldAdmin: TAccountMetas[1]; + newAdmin: TAccountMetas[2]; + }; + data: SetConfigAdminInstructionData; +}; + +export function parseSetConfigAdminInstruction< + TProgram extends string, + TAccountMetas extends readonly IAccountMeta[], +>( + instruction: IInstruction & + IInstructionWithAccounts & + IInstructionWithData +): ParsedSetConfigAdminInstruction { + if (instruction.accounts.length < 3) { + // 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(), + oldAdmin: getNextAccount(), + newAdmin: getNextAccount(), + }, + data: getSetConfigAdminInstructionDataDecoder().decode(instruction.data), + }; +} diff --git a/clients/js/vault_client/programs/jitoVault.ts b/clients/js/vault_client/programs/jitoVault.ts index 23bdc239..83cd3290 100644 --- a/clients/js/vault_client/programs/jitoVault.ts +++ b/clients/js/vault_client/programs/jitoVault.ts @@ -34,6 +34,7 @@ import { type ParsedInitializeVaultWithMintInstruction, type ParsedMintToInstruction, type ParsedSetAdminInstruction, + type ParsedSetConfigAdminInstruction, type ParsedSetDepositCapacityInstruction, type ParsedSetFeesInstruction, type ParsedSetIsPausedInstruction, @@ -92,6 +93,7 @@ export enum JitoVaultInstruction { CloseVaultUpdateStateTracker, CreateTokenMetadata, UpdateTokenMetadata, + SetConfigAdmin, } export function identifyJitoVaultInstruction( @@ -191,6 +193,9 @@ export function identifyJitoVaultInstruction( if (containsBytes(data, getU8Encoder().encode(30), 0)) { return JitoVaultInstruction.UpdateTokenMetadata; } + if (containsBytes(data, getU8Encoder().encode(31), 0)) { + return JitoVaultInstruction.SetConfigAdmin; + } throw new Error( 'The provided instruction could not be identified as a jitoVault instruction.' ); @@ -291,4 +296,7 @@ export type ParsedJitoVaultInstruction< } & ParsedCreateTokenMetadataInstruction) | ({ instructionType: JitoVaultInstruction.UpdateTokenMetadata; - } & ParsedUpdateTokenMetadataInstruction); + } & ParsedUpdateTokenMetadataInstruction) + | ({ + instructionType: JitoVaultInstruction.SetConfigAdmin; + } & ParsedSetConfigAdminInstruction); diff --git a/clients/rust/restaking_client/src/generated/errors/jito_restaking.rs b/clients/rust/restaking_client/src/generated/errors/jito_restaking.rs index 4f6d861b..5eb594c2 100644 --- a/clients/rust/restaking_client/src/generated/errors/jito_restaking.rs +++ b/clients/rust/restaking_client/src/generated/errors/jito_restaking.rs @@ -84,6 +84,9 @@ pub enum JitoRestakingError { /// 2013 - InvalidEpochLength #[error("InvalidEpochLength")] InvalidEpochLength = 0x7DD, + /// 2014 - ConfigAdminInvalid + #[error("ConfigAdminInvalid")] + ConfigAdminInvalid = 0x7DE, /// 3000 - ArithmeticOverflow #[error("ArithmeticOverflow")] ArithmeticOverflow = 0xBB8, diff --git a/clients/rust/restaking_client/src/generated/instructions/mod.rs b/clients/rust/restaking_client/src/generated/instructions/mod.rs index c4e13c4e..7de78c05 100644 --- a/clients/rust/restaking_client/src/generated/instructions/mod.rs +++ b/clients/rust/restaking_client/src/generated/instructions/mod.rs @@ -25,6 +25,7 @@ pub(crate) mod r#operator_set_admin; pub(crate) mod r#operator_set_fee; pub(crate) mod r#operator_set_secondary_admin; pub(crate) mod r#operator_warmup_ncn; +pub(crate) mod r#set_config_admin; pub(crate) mod r#warmup_ncn_vault_slasher_ticket; pub(crate) mod r#warmup_ncn_vault_ticket; pub(crate) mod r#warmup_operator_vault_ticket; @@ -38,6 +39,6 @@ pub use self::{ r#ncn_delegate_token_account::*, r#ncn_set_admin::*, r#ncn_set_secondary_admin::*, r#ncn_warmup_operator::*, r#operator_cooldown_ncn::*, r#operator_delegate_token_account::*, r#operator_set_admin::*, r#operator_set_fee::*, r#operator_set_secondary_admin::*, - r#operator_warmup_ncn::*, r#warmup_ncn_vault_slasher_ticket::*, r#warmup_ncn_vault_ticket::*, - r#warmup_operator_vault_ticket::*, + r#operator_warmup_ncn::*, r#set_config_admin::*, r#warmup_ncn_vault_slasher_ticket::*, + r#warmup_ncn_vault_ticket::*, r#warmup_operator_vault_ticket::*, }; diff --git a/clients/rust/restaking_client/src/generated/instructions/set_config_admin.rs b/clients/rust/restaking_client/src/generated/instructions/set_config_admin.rs new file mode 100644 index 00000000..6936a806 --- /dev/null +++ b/clients/rust/restaking_client/src/generated/instructions/set_config_admin.rs @@ -0,0 +1,358 @@ +//! 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 SetConfigAdmin { + pub config: solana_program::pubkey::Pubkey, + + pub old_admin: solana_program::pubkey::Pubkey, + + pub new_admin: solana_program::pubkey::Pubkey, +} + +impl SetConfigAdmin { + 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(3 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.config, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.old_admin, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.new_admin, + true, + )); + accounts.extend_from_slice(remaining_accounts); + let data = SetConfigAdminInstructionData::new().try_to_vec().unwrap(); + + solana_program::instruction::Instruction { + program_id: crate::JITO_RESTAKING_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +pub struct SetConfigAdminInstructionData { + discriminator: u8, +} + +impl SetConfigAdminInstructionData { + pub fn new() -> Self { + Self { discriminator: 24 } + } +} + +impl Default for SetConfigAdminInstructionData { + fn default() -> Self { + Self::new() + } +} + +/// Instruction builder for `SetConfigAdmin`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` config +/// 1. `[signer]` old_admin +/// 2. `[signer]` new_admin +#[derive(Clone, Debug, Default)] +pub struct SetConfigAdminBuilder { + config: Option, + old_admin: Option, + new_admin: Option, + __remaining_accounts: Vec, +} + +impl SetConfigAdminBuilder { + 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 old_admin(&mut self, old_admin: solana_program::pubkey::Pubkey) -> &mut Self { + self.old_admin = Some(old_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 + } + /// Add an aditional 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 = SetConfigAdmin { + config: self.config.expect("config is not set"), + old_admin: self.old_admin.expect("old_admin is not set"), + new_admin: self.new_admin.expect("new_admin is not set"), + }; + + accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) + } +} + +/// `set_config_admin` CPI accounts. +pub struct SetConfigAdminCpiAccounts<'a, 'b> { + pub config: &'b solana_program::account_info::AccountInfo<'a>, + + pub old_admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub new_admin: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `set_config_admin` CPI instruction. +pub struct SetConfigAdminCpi<'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 old_admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub new_admin: &'b solana_program::account_info::AccountInfo<'a>, +} + +impl<'a, 'b> SetConfigAdminCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: SetConfigAdminCpiAccounts<'a, 'b>, + ) -> Self { + Self { + __program: program, + config: accounts.config, + old_admin: accounts.old_admin, + new_admin: accounts.new_admin, + } + } + #[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(3 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.config.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.old_admin.key, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.new_admin.key, + true, + )); + 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 = SetConfigAdminInstructionData::new().try_to_vec().unwrap(); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::JITO_RESTAKING_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(3 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.config.clone()); + account_infos.push(self.old_admin.clone()); + account_infos.push(self.new_admin.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 `SetConfigAdmin` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` config +/// 1. `[signer]` old_admin +/// 2. `[signer]` new_admin +#[derive(Clone, Debug)] +pub struct SetConfigAdminCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> SetConfigAdminCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(SetConfigAdminCpiBuilderInstruction { + __program: program, + config: None, + old_admin: None, + new_admin: 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 old_admin( + &mut self, + old_admin: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.old_admin = Some(old_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 + } + /// 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 = SetConfigAdminCpi { + __program: self.instruction.__program, + + config: self.instruction.config.expect("config is not set"), + + old_admin: self.instruction.old_admin.expect("old_admin is not set"), + + new_admin: self.instruction.new_admin.expect("new_admin is not set"), + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +#[derive(Clone, Debug)] +struct SetConfigAdminCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + config: Option<&'b solana_program::account_info::AccountInfo<'a>>, + old_admin: Option<&'b solana_program::account_info::AccountInfo<'a>>, + new_admin: 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/vault_client/src/generated/errors/jito_vault.rs b/clients/rust/vault_client/src/generated/errors/jito_vault.rs index 843e1f03..50fd5817 100644 --- a/clients/rust/vault_client/src/generated/errors/jito_vault.rs +++ b/clients/rust/vault_client/src/generated/errors/jito_vault.rs @@ -48,12 +48,12 @@ pub enum JitoVaultError { /// 1012 - VaultFeeAdminInvalid #[error("VaultFeeAdminInvalid")] VaultFeeAdminInvalid = 0x3F4, - /// 1013 - VaultConfigAdminInvalid - #[error("VaultConfigAdminInvalid")] - VaultConfigAdminInvalid = 0x3F5, - /// 1014 - VaultConfigFeeAdminInvalid - #[error("VaultConfigFeeAdminInvalid")] - VaultConfigFeeAdminInvalid = 0x3F6, + /// 1013 - ConfigAdminInvalid + #[error("ConfigAdminInvalid")] + ConfigAdminInvalid = 0x3F5, + /// 1014 - ConfigFeeAdminInvalid + #[error("ConfigFeeAdminInvalid")] + ConfigFeeAdminInvalid = 0x3F6, /// 1015 - VaultFeeCapExceeded #[error("VaultFeeCapExceeded")] VaultFeeCapExceeded = 0x3F7, diff --git a/clients/rust/vault_client/src/generated/instructions/mod.rs b/clients/rust/vault_client/src/generated/instructions/mod.rs index 40f9b294..5e426226 100644 --- a/clients/rust/vault_client/src/generated/instructions/mod.rs +++ b/clients/rust/vault_client/src/generated/instructions/mod.rs @@ -25,6 +25,7 @@ pub(crate) mod r#initialize_vault_update_state_tracker; pub(crate) mod r#initialize_vault_with_mint; pub(crate) mod r#mint_to; pub(crate) mod r#set_admin; +pub(crate) mod r#set_config_admin; pub(crate) mod r#set_deposit_capacity; pub(crate) mod r#set_fees; pub(crate) mod r#set_is_paused; @@ -45,8 +46,8 @@ pub use self::{ r#initialize_vault_ncn_slasher_operator_ticket::*, r#initialize_vault_ncn_slasher_ticket::*, r#initialize_vault_ncn_ticket::*, r#initialize_vault_operator_delegation::*, r#initialize_vault_update_state_tracker::*, r#initialize_vault_with_mint::*, r#mint_to::*, - r#set_admin::*, r#set_deposit_capacity::*, r#set_fees::*, r#set_is_paused::*, - r#set_program_fee::*, r#set_program_fee_wallet::*, r#set_secondary_admin::*, - r#update_token_metadata::*, r#update_vault_balance::*, r#warmup_vault_ncn_slasher_ticket::*, - r#warmup_vault_ncn_ticket::*, + r#set_admin::*, r#set_config_admin::*, r#set_deposit_capacity::*, r#set_fees::*, + r#set_is_paused::*, r#set_program_fee::*, r#set_program_fee_wallet::*, + r#set_secondary_admin::*, r#update_token_metadata::*, r#update_vault_balance::*, + r#warmup_vault_ncn_slasher_ticket::*, r#warmup_vault_ncn_ticket::*, }; diff --git a/clients/rust/vault_client/src/generated/instructions/set_config_admin.rs b/clients/rust/vault_client/src/generated/instructions/set_config_admin.rs new file mode 100644 index 00000000..8e111284 --- /dev/null +++ b/clients/rust/vault_client/src/generated/instructions/set_config_admin.rs @@ -0,0 +1,358 @@ +//! 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 SetConfigAdmin { + pub config: solana_program::pubkey::Pubkey, + + pub old_admin: solana_program::pubkey::Pubkey, + + pub new_admin: solana_program::pubkey::Pubkey, +} + +impl SetConfigAdmin { + 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(3 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + self.config, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.old_admin, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.new_admin, + true, + )); + accounts.extend_from_slice(remaining_accounts); + let data = SetConfigAdminInstructionData::new().try_to_vec().unwrap(); + + solana_program::instruction::Instruction { + program_id: crate::JITO_VAULT_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +pub struct SetConfigAdminInstructionData { + discriminator: u8, +} + +impl SetConfigAdminInstructionData { + pub fn new() -> Self { + Self { discriminator: 31 } + } +} + +impl Default for SetConfigAdminInstructionData { + fn default() -> Self { + Self::new() + } +} + +/// Instruction builder for `SetConfigAdmin`. +/// +/// ### Accounts: +/// +/// 0. `[writable]` config +/// 1. `[signer]` old_admin +/// 2. `[signer]` new_admin +#[derive(Clone, Debug, Default)] +pub struct SetConfigAdminBuilder { + config: Option, + old_admin: Option, + new_admin: Option, + __remaining_accounts: Vec, +} + +impl SetConfigAdminBuilder { + 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 old_admin(&mut self, old_admin: solana_program::pubkey::Pubkey) -> &mut Self { + self.old_admin = Some(old_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 + } + /// Add an aditional 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 = SetConfigAdmin { + config: self.config.expect("config is not set"), + old_admin: self.old_admin.expect("old_admin is not set"), + new_admin: self.new_admin.expect("new_admin is not set"), + }; + + accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) + } +} + +/// `set_config_admin` CPI accounts. +pub struct SetConfigAdminCpiAccounts<'a, 'b> { + pub config: &'b solana_program::account_info::AccountInfo<'a>, + + pub old_admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub new_admin: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `set_config_admin` CPI instruction. +pub struct SetConfigAdminCpi<'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 old_admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub new_admin: &'b solana_program::account_info::AccountInfo<'a>, +} + +impl<'a, 'b> SetConfigAdminCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: SetConfigAdminCpiAccounts<'a, 'b>, + ) -> Self { + Self { + __program: program, + config: accounts.config, + old_admin: accounts.old_admin, + new_admin: accounts.new_admin, + } + } + #[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(3 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.config.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.old_admin.key, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.new_admin.key, + true, + )); + 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 = SetConfigAdminInstructionData::new().try_to_vec().unwrap(); + + let instruction = solana_program::instruction::Instruction { + program_id: crate::JITO_VAULT_ID, + accounts, + data, + }; + let mut account_infos = Vec::with_capacity(3 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.config.clone()); + account_infos.push(self.old_admin.clone()); + account_infos.push(self.new_admin.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 `SetConfigAdmin` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[writable]` config +/// 1. `[signer]` old_admin +/// 2. `[signer]` new_admin +#[derive(Clone, Debug)] +pub struct SetConfigAdminCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> SetConfigAdminCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(SetConfigAdminCpiBuilderInstruction { + __program: program, + config: None, + old_admin: None, + new_admin: 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 old_admin( + &mut self, + old_admin: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.old_admin = Some(old_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 + } + /// 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 = SetConfigAdminCpi { + __program: self.instruction.__program, + + config: self.instruction.config.expect("config is not set"), + + old_admin: self.instruction.old_admin.expect("old_admin is not set"), + + new_admin: self.instruction.new_admin.expect("new_admin is not set"), + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +#[derive(Clone, Debug)] +struct SetConfigAdminCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + config: Option<&'b solana_program::account_info::AccountInfo<'a>>, + old_admin: Option<&'b solana_program::account_info::AccountInfo<'a>>, + new_admin: 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/idl/jito_restaking.json b/idl/jito_restaking.json index 9701c419..4033d94f 100644 --- a/idl/jito_restaking.json +++ b/idl/jito_restaking.json @@ -890,6 +890,31 @@ "type": "u8", "value": 23 } + }, + { + "name": "SetConfigAdmin", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "oldAdmin", + "isMut": false, + "isSigner": true + }, + { + "name": "newAdmin", + "isMut": false, + "isSigner": true + } + ], + "args": [], + "discriminant": { + "type": "u8", + "value": 24 + } } ], "accounts": [ @@ -1484,6 +1509,11 @@ "name": "InvalidEpochLength", "msg": "InvalidEpochLength" }, + { + "code": 2014, + "name": "ConfigAdminInvalid", + "msg": "ConfigAdminInvalid" + }, { "code": 3000, "name": "ArithmeticOverflow", diff --git a/idl/jito_vault.json b/idl/jito_vault.json index 38056b7b..74bcb442 100644 --- a/idl/jito_vault.json +++ b/idl/jito_vault.json @@ -1337,6 +1337,31 @@ "type": "u8", "value": 30 } + }, + { + "name": "SetConfigAdmin", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "oldAdmin", + "isMut": false, + "isSigner": true + }, + { + "name": "newAdmin", + "isMut": false, + "isSigner": true + } + ], + "args": [], + "discriminant": { + "type": "u8", + "value": 31 + } } ], "accounts": [ @@ -2170,13 +2195,13 @@ }, { "code": 1013, - "name": "VaultConfigAdminInvalid", - "msg": "VaultConfigAdminInvalid" + "name": "ConfigAdminInvalid", + "msg": "ConfigAdminInvalid" }, { "code": 1014, - "name": "VaultConfigFeeAdminInvalid", - "msg": "VaultConfigFeeAdminInvalid" + "name": "ConfigFeeAdminInvalid", + "msg": "ConfigFeeAdminInvalid" }, { "code": 1015, diff --git a/integration_tests/tests/fixtures/restaking_client.rs b/integration_tests/tests/fixtures/restaking_client.rs index 539589a4..4baf8568 100644 --- a/integration_tests/tests/fixtures/restaking_client.rs +++ b/integration_tests/tests/fixtures/restaking_client.rs @@ -13,7 +13,8 @@ use jito_restaking_sdk::{ initialize_ncn_vault_ticket, initialize_operator, initialize_operator_vault_ticket, ncn_cooldown_operator, ncn_set_admin, ncn_warmup_operator, operator_cooldown_ncn, operator_set_admin, operator_set_fee, operator_set_secondary_admin, operator_warmup_ncn, - warmup_ncn_vault_slasher_ticket, warmup_ncn_vault_ticket, warmup_operator_vault_ticket, + set_config_admin, warmup_ncn_vault_slasher_ticket, warmup_ncn_vault_ticket, + warmup_operator_vault_ticket, }, }; use solana_program::{ @@ -138,6 +139,7 @@ impl RestakingProgramClient { )?) } + #[allow(dead_code)] pub async fn get_operator_ncn_ticket( &mut self, operator: &Pubkey, @@ -367,6 +369,7 @@ impl RestakingProgramClient { .await } + #[allow(dead_code)] pub async fn do_cooldown_ncn_vault_ticket( &mut self, ncn_root: &NcnRoot, @@ -388,6 +391,7 @@ impl RestakingProgramClient { .await } + #[allow(dead_code)] pub async fn cooldown_ncn_vault_ticket( &mut self, config: &Pubkey, @@ -1057,6 +1061,27 @@ impl RestakingProgramClient { .await?; Ok(()) } + + pub async fn set_config_admin( + &mut self, + config: &Pubkey, + old_admin: &Keypair, + new_admin: &Keypair, + ) -> Result<(), TestError> { + let blockhash = self.banks_client.get_latest_blockhash().await?; + self.process_transaction(&Transaction::new_signed_with_payer( + &[set_config_admin( + &jito_restaking_program::id(), + config, + &old_admin.pubkey(), + &new_admin.pubkey(), + )], + Some(&old_admin.pubkey()), + &[old_admin, new_admin], + blockhash, + )) + .await + } } #[track_caller] diff --git a/integration_tests/tests/fixtures/vault_client.rs b/integration_tests/tests/fixtures/vault_client.rs index 385a3f0c..ca019daf 100644 --- a/integration_tests/tests/fixtures/vault_client.rs +++ b/integration_tests/tests/fixtures/vault_client.rs @@ -172,6 +172,7 @@ impl VaultProgramClient { )?) } + #[allow(dead_code)] pub async fn get_vault_ncn_slasher_operator_ticket( &mut self, vault: &Pubkey, @@ -438,6 +439,7 @@ impl VaultProgramClient { .await } + #[allow(dead_code)] pub async fn setup_vault_ncn_slasher_operator_ticket( &mut self, vault_root: &VaultRoot, @@ -1339,6 +1341,7 @@ impl VaultProgramClient { .await } + #[allow(dead_code)] pub async fn initialize_vault_ncn_slasher_operator_ticket( &mut self, config: &Pubkey, @@ -1689,6 +1692,27 @@ impl VaultProgramClient { )) .await } + + pub async fn set_config_admin( + &mut self, + config: &Pubkey, + old_admin: &Keypair, + new_admin: &Keypair, + ) -> Result<(), TestError> { + let blockhash = self.banks_client.get_latest_blockhash().await?; + self._process_transaction(&Transaction::new_signed_with_payer( + &[jito_vault_sdk::sdk::set_config_admin( + &jito_vault_program::id(), + config, + &old_admin.pubkey(), + &new_admin.pubkey(), + )], + Some(&old_admin.pubkey()), + &[old_admin, new_admin], + blockhash, + )) + .await + } } #[inline(always)] diff --git a/integration_tests/tests/restaking/mod.rs b/integration_tests/tests/restaking/mod.rs index f869e21a..94f5a363 100644 --- a/integration_tests/tests/restaking/mod.rs +++ b/integration_tests/tests/restaking/mod.rs @@ -15,3 +15,4 @@ mod operator_set_admin; mod operator_set_fee; mod operator_set_secondary_admin; mod operator_warmup_ncn; +mod set_config_admin; diff --git a/integration_tests/tests/restaking/set_config_admin.rs b/integration_tests/tests/restaking/set_config_admin.rs new file mode 100644 index 00000000..8fdd8e28 --- /dev/null +++ b/integration_tests/tests/restaking/set_config_admin.rs @@ -0,0 +1,78 @@ +#[cfg(test)] +mod tests { + use jito_restaking_core::config::Config; + use jito_restaking_sdk::error::RestakingError; + use solana_program::instruction::InstructionError; + use solana_sdk::signature::{Keypair, Signer}; + + use crate::fixtures::{assert_ix_error, fixture::TestBuilder}; + + #[tokio::test] + async fn test_set_config_admin_ok() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Set new admin + let new_admin = Keypair::new(); + + restaking_program_client + .set_config_admin(&config, &config_admin, &new_admin) + .await + .unwrap(); + + // Verify new admin + let updated_config = restaking_program_client.get_config(&config).await.unwrap(); + assert_eq!(updated_config.admin, new_admin.pubkey()); + } + + #[tokio::test] + async fn test_set_config_admin_invalid_old_admin() { + let mut fixture = TestBuilder::new().await; + let mut restaking_program_client = fixture.restaking_program_client(); + + // Initialize config + let config_admin = Keypair::new(); + let config = Config::find_program_address(&jito_restaking_program::id()).0; + + fixture + .transfer(&config_admin.pubkey(), 10.0) + .await + .unwrap(); + + restaking_program_client + .initialize_config(&config, &config_admin) + .await + .unwrap(); + + // Attempt to set new admin with invalid old admin + let invalid_admin = Keypair::new(); + let new_admin = Keypair::new(); + fixture + .transfer(&invalid_admin.pubkey(), 10.0) + .await + .unwrap(); + + let transaction_error = restaking_program_client + .set_config_admin(&config, &invalid_admin, &new_admin) + .await; + + assert_ix_error( + transaction_error, + InstructionError::Custom(RestakingError::ConfigAdminInvalid as u32), + ); + } +} diff --git a/integration_tests/tests/vault/mod.rs b/integration_tests/tests/vault/mod.rs index 8c8031a0..8f9ccd70 100644 --- a/integration_tests/tests/vault/mod.rs +++ b/integration_tests/tests/vault/mod.rs @@ -15,6 +15,7 @@ mod initialize_vault_update_state_tracker; mod reward_fee; mod set_admin; mod set_capacity; +mod set_config_admin; mod set_fees; mod set_is_paused; mod set_program_fee_wallet; diff --git a/integration_tests/tests/vault/set_config_admin.rs b/integration_tests/tests/vault/set_config_admin.rs new file mode 100644 index 00000000..30b4db2a --- /dev/null +++ b/integration_tests/tests/vault/set_config_admin.rs @@ -0,0 +1,73 @@ +#[cfg(test)] +mod tests { + use jito_vault_core::config::Config; + use jito_vault_sdk::error::VaultError; + use solana_program::instruction::InstructionError; + use solana_sdk::signature::{Keypair, Signer}; + + use crate::fixtures::{assert_ix_error, fixture::TestBuilder}; + + #[tokio::test] + async fn test_set_config_admin_ok() { + let fixture = TestBuilder::new().await; + let mut vault_program_client = fixture.vault_program_client(); + + // Initialize config + let old_admin = vault_program_client.do_initialize_config().await.unwrap(); + + // Create new admin + let new_admin = Keypair::new(); + + // Set new admin + vault_program_client + .set_config_admin( + &Config::find_program_address(&jito_vault_program::id()).0, + &old_admin, + &new_admin, + ) + .await + .unwrap(); + + // Verify new admin + let config = vault_program_client + .get_config(&Config::find_program_address(&jito_vault_program::id()).0) + .await + .unwrap(); + + assert_eq!(config.admin, new_admin.pubkey()); + assert_eq!(config.fee_admin, new_admin.pubkey()); + } + + #[tokio::test] + async fn test_set_config_admin_invalid_old_admin() { + let fixture = TestBuilder::new().await; + let mut vault_program_client = fixture.vault_program_client(); + + // Initialize config + let _old_admin = vault_program_client.do_initialize_config().await.unwrap(); + + // Create invalid old admin and new admin + let invalid_old_admin = Keypair::new(); + let new_admin = Keypair::new(); + + vault_program_client + .airdrop(&invalid_old_admin.pubkey(), 1.0) + .await + .unwrap(); + + // Attempt to set new admin with invalid old admin + let result = vault_program_client + .set_config_admin( + &Config::find_program_address(&jito_vault_program::id()).0, + &invalid_old_admin, + &new_admin, + ) + .await; + + assert!(result.is_err()); + assert_ix_error( + result, + InstructionError::Custom(VaultError::ConfigAdminInvalid as u32), + ); + } +} diff --git a/integration_tests/tests/vault/set_fees.rs b/integration_tests/tests/vault/set_fees.rs index 3fbc2297..0d09c0bb 100644 --- a/integration_tests/tests/vault/set_fees.rs +++ b/integration_tests/tests/vault/set_fees.rs @@ -1073,7 +1073,7 @@ mod tests { .await .unwrap(); let result = vault_program_client.set_program_fee(&non_admin, 200).await; - assert_vault_error(result, VaultError::VaultConfigAdminInvalid); + assert_vault_error(result, VaultError::ConfigAdminInvalid); // Try to set fee above MAX_FEE_BPS let result = vault_program_client diff --git a/integration_tests/tests/vault/set_program_fee_wallet.rs b/integration_tests/tests/vault/set_program_fee_wallet.rs index a0fa9639..a0fc2859 100644 --- a/integration_tests/tests/vault/set_program_fee_wallet.rs +++ b/integration_tests/tests/vault/set_program_fee_wallet.rs @@ -34,7 +34,7 @@ mod tests { let result = vault_program_client .set_program_fee_wallet(&non_admin, &Keypair::new().pubkey()) .await; - assert_vault_error(result, VaultError::VaultConfigFeeAdminInvalid); + assert_vault_error(result, VaultError::ConfigFeeAdminInvalid); // Try to set fee wallet to the same address (should succeed) vault_program_client diff --git a/restaking_core/src/config.rs b/restaking_core/src/config.rs index 78557e49..d94cebd1 100644 --- a/restaking_core/src/config.rs +++ b/restaking_core/src/config.rs @@ -140,6 +140,10 @@ impl Config { } Ok(()) } + + pub fn set_admin(&mut self, new_admin: Pubkey) { + self.admin = new_admin; + } } #[cfg(test)] diff --git a/restaking_program/src/lib.rs b/restaking_program/src/lib.rs index c3e7baa4..0db93267 100644 --- a/restaking_program/src/lib.rs +++ b/restaking_program/src/lib.rs @@ -19,6 +19,7 @@ mod operator_set_admin; mod operator_set_fee; mod operator_set_secondary_admin; mod operator_warmup_ncn; +mod set_config_admin; mod warmup_ncn_vault_slasher_ticket; mod warmup_ncn_vault_ticket; mod warmup_operator_vault_ticket; @@ -52,7 +53,7 @@ use crate::{ operator_delegate_token_account::process_operator_delegate_token_account, operator_set_admin::process_set_node_operator_admin, operator_set_secondary_admin::process_set_operator_secondary_admin, - operator_warmup_ncn::process_operator_warmup_ncn, + operator_warmup_ncn::process_operator_warmup_ncn, set_config_admin::process_set_config_admin, warmup_ncn_vault_slasher_ticket::process_warmup_ncn_vault_slasher_ticket, warmup_ncn_vault_ticket::process_warmup_ncn_vault_ticket, warmup_operator_vault_ticket::process_warmup_operator_vault_ticket, @@ -187,5 +188,9 @@ pub fn process_instruction( msg!("Instruction: OperatorDelegateTokenAccount"); process_operator_delegate_token_account(program_id, accounts) } + RestakingInstruction::SetConfigAdmin => { + msg!("Instruction: SetConfigAdmin"); + process_set_config_admin(program_id, accounts) + } } } diff --git a/restaking_program/src/set_config_admin.rs b/restaking_program/src/set_config_admin.rs new file mode 100644 index 00000000..5c23ea16 --- /dev/null +++ b/restaking_program/src/set_config_admin.rs @@ -0,0 +1,27 @@ +use jito_bytemuck::AccountDeserialize; +use jito_jsm_core::loader::load_signer; +use jito_restaking_core::config::Config; +use jito_restaking_sdk::error::RestakingError; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, +}; + +/// Processes the set config admin instruction: [`crate::RestakingInstruction::SetConfigAdmin`] +pub fn process_set_config_admin(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { + let [config, old_admin, new_admin] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + Config::load(program_id, config, true)?; + let mut config_data = config.data.borrow_mut(); + let config = Config::try_from_slice_unchecked_mut(&mut config_data)?; + load_signer(old_admin, false)?; + load_signer(new_admin, false)?; + + if config.admin != *old_admin.key { + return Err(RestakingError::ConfigAdminInvalid.into()); + } + config.set_admin(*new_admin.key); + + Ok(()) +} diff --git a/restaking_sdk/src/error.rs b/restaking_sdk/src/error.rs index 01a7c771..9e820e62 100644 --- a/restaking_sdk/src/error.rs +++ b/restaking_sdk/src/error.rs @@ -55,6 +55,9 @@ pub enum RestakingError { #[error("InvalidEpochLength")] InvalidEpochLength, + #[error("ConfigAdminInvalid")] + ConfigAdminInvalid, + #[error("ArithmeticOverflow")] ArithmeticOverflow = 3000, #[error("ArithmeticUnderflow")] diff --git a/restaking_sdk/src/instruction.rs b/restaking_sdk/src/instruction.rs index 31493a40..208993d6 100644 --- a/restaking_sdk/src/instruction.rs +++ b/restaking_sdk/src/instruction.rs @@ -191,6 +191,12 @@ pub enum RestakingInstruction { #[account(4, name = "delegate")] #[account(5, name = "token_program")] OperatorDelegateTokenAccount, + + /// Changes the admin for the config + #[account(0, writable, name = "config")] + #[account(1, signer, name = "old_admin")] + #[account(2, signer, name = "new_admin")] + SetConfigAdmin, } #[derive(Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq)] diff --git a/restaking_sdk/src/sdk.rs b/restaking_sdk/src/sdk.rs index 0cb68ae8..7cd0e2fd 100644 --- a/restaking_sdk/src/sdk.rs +++ b/restaking_sdk/src/sdk.rs @@ -608,3 +608,21 @@ pub fn warmup_ncn_vault_slasher_ticket( .unwrap(), } } + +pub fn set_config_admin( + program_id: &Pubkey, + config: &Pubkey, + old_admin: &Pubkey, + new_admin: &Pubkey, +) -> Instruction { + let accounts = vec![ + AccountMeta::new(*config, false), + AccountMeta::new_readonly(*old_admin, true), + AccountMeta::new_readonly(*new_admin, true), + ]; + Instruction { + program_id: *program_id, + accounts, + data: RestakingInstruction::SetConfigAdmin.try_to_vec().unwrap(), + } +} diff --git a/vault_core/src/config.rs b/vault_core/src/config.rs index b9f889dc..ed6463c3 100644 --- a/vault_core/src/config.rs +++ b/vault_core/src/config.rs @@ -222,6 +222,11 @@ impl Config { } Ok(()) } + + pub fn set_admin(&mut self, new_admin: Pubkey) { + self.admin = new_admin; + self.fee_admin = new_admin; + } } #[cfg(test)] diff --git a/vault_program/src/lib.rs b/vault_program/src/lib.rs index 5b6d4032..fb0dbb8f 100644 --- a/vault_program/src/lib.rs +++ b/vault_program/src/lib.rs @@ -20,6 +20,7 @@ mod initialize_vault_with_mint; mod mint_to; mod set_admin; mod set_capacity; +mod set_config_admin; mod set_fees; mod set_is_paused; mod set_program_fee; @@ -60,8 +61,8 @@ use crate::{ initialize_vault_update_state_tracker::process_initialize_vault_update_state_tracker, initialize_vault_with_mint::process_initialize_vault_with_mint, mint_to::process_mint, set_admin::process_set_admin, set_capacity::process_set_deposit_capacity, - set_fees::process_set_fees, set_is_paused::process_set_is_paused, - set_program_fee_wallet::process_set_program_fee_wallet, + set_config_admin::process_set_config_admin, set_fees::process_set_fees, + set_is_paused::process_set_is_paused, set_program_fee_wallet::process_set_program_fee_wallet, set_secondary_admin::process_set_secondary_admin, update_token_metadata::process_update_token_metadata, update_vault_balance::process_update_vault_balance, @@ -274,5 +275,9 @@ pub fn process_instruction( msg!("Instruction: UpdateTokenMetadata"); process_update_token_metadata(program_id, accounts, name, symbol, uri) } + VaultInstruction::SetConfigAdmin => { + msg!("Instruction: SetConfigAdmin"); + process_set_config_admin(program_id, accounts) + } } } diff --git a/vault_program/src/set_config_admin.rs b/vault_program/src/set_config_admin.rs new file mode 100644 index 00000000..98734922 --- /dev/null +++ b/vault_program/src/set_config_admin.rs @@ -0,0 +1,27 @@ +use jito_bytemuck::AccountDeserialize; +use jito_jsm_core::loader::load_signer; +use jito_vault_core::config::Config; +use jito_vault_sdk::error::VaultError; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, +}; + +/// Processes the set config admin instruction: [`crate::VaultInstruction::SetConfigAdmin`] +pub fn process_set_config_admin(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { + let [config, old_admin, new_admin] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + Config::load(program_id, config, true)?; + let mut config_data = config.data.borrow_mut(); + let config = Config::try_from_slice_unchecked_mut(&mut config_data)?; + load_signer(old_admin, false)?; + load_signer(new_admin, false)?; + + if config.admin != *old_admin.key { + return Err(VaultError::ConfigAdminInvalid.into()); + } + config.set_admin(*new_admin.key); + + Ok(()) +} diff --git a/vault_program/src/set_program_fee.rs b/vault_program/src/set_program_fee.rs index 9cc76ed8..4915271a 100644 --- a/vault_program/src/set_program_fee.rs +++ b/vault_program/src/set_program_fee.rs @@ -28,7 +28,7 @@ pub fn process_set_program_fee( if config_admin.key != &config.admin { msg!("Config admin does not match"); - return Err(VaultError::VaultConfigAdminInvalid.into()); + return Err(VaultError::ConfigAdminInvalid.into()); } config.set_program_fee_bps(new_fee_bps)?; diff --git a/vault_program/src/set_program_fee_wallet.rs b/vault_program/src/set_program_fee_wallet.rs index f79fc684..ec10da7a 100644 --- a/vault_program/src/set_program_fee_wallet.rs +++ b/vault_program/src/set_program_fee_wallet.rs @@ -26,7 +26,7 @@ pub fn process_set_program_fee_wallet( if config_fee_admin.key != &config.fee_admin { msg!("Config fee admin does not match"); - return Err(VaultError::VaultConfigFeeAdminInvalid.into()); + return Err(VaultError::ConfigFeeAdminInvalid.into()); } config.program_fee_wallet = *new_fee_wallet.key; diff --git a/vault_sdk/src/error.rs b/vault_sdk/src/error.rs index ce18aa80..b4452b8a 100644 --- a/vault_sdk/src/error.rs +++ b/vault_sdk/src/error.rs @@ -29,10 +29,10 @@ pub enum VaultError { VaultNcnAdminInvalid, #[error("VaultFeeAdminInvalid")] VaultFeeAdminInvalid, - #[error("VaultConfigAdminInvalid")] - VaultConfigAdminInvalid, - #[error("VaultConfigFeeAdminInvalid")] - VaultConfigFeeAdminInvalid, + #[error("ConfigAdminInvalid")] + ConfigAdminInvalid, + #[error("ConfigFeeAdminInvalid")] + ConfigFeeAdminInvalid, #[error("VaultFeeCapExceeded")] VaultFeeCapExceeded, #[error("VaultFeeChangeTooSoon")] diff --git a/vault_sdk/src/instruction.rs b/vault_sdk/src/instruction.rs index 82de8aa1..07d0a1f8 100644 --- a/vault_sdk/src/instruction.rs +++ b/vault_sdk/src/instruction.rs @@ -308,6 +308,11 @@ pub enum VaultInstruction { uri: String, }, + /// Changes the admin for the config + #[account(0, writable, name = "config")] + #[account(1, signer, name = "old_admin")] + #[account(2, signer, name = "new_admin")] + SetConfigAdmin, } diff --git a/vault_sdk/src/sdk.rs b/vault_sdk/src/sdk.rs index e1ff0682..e643cedf 100644 --- a/vault_sdk/src/sdk.rs +++ b/vault_sdk/src/sdk.rs @@ -758,3 +758,21 @@ pub fn set_is_paused( .unwrap(), } } + +pub fn set_config_admin( + program_id: &Pubkey, + config: &Pubkey, + old_admin: &Pubkey, + new_admin: &Pubkey, +) -> Instruction { + let accounts = vec![ + AccountMeta::new(*config, false), + AccountMeta::new_readonly(*old_admin, true), + AccountMeta::new_readonly(*new_admin, true), + ]; + Instruction { + program_id: *program_id, + accounts, + data: VaultInstruction::SetConfigAdmin.try_to_vec().unwrap(), + } +}