diff --git a/clients/js/vault_client/instructions/index.ts b/clients/js/vault_client/instructions/index.ts index 7bc28cbe..9624cbd2 100644 --- a/clients/js/vault_client/instructions/index.ts +++ b/clients/js/vault_client/instructions/index.ts @@ -26,6 +26,7 @@ export * from './initializeVaultOperatorDelegation'; export * from './initializeVaultUpdateStateTracker'; export * from './initializeVaultWithMint'; export * from './mintTo'; +export * from './revokeDelegateTokenAccount'; export * from './setAdmin'; export * from './setConfigAdmin'; export * from './setDepositCapacity'; diff --git a/clients/js/vault_client/instructions/revokeDelegateTokenAccount.ts b/clients/js/vault_client/instructions/revokeDelegateTokenAccount.ts new file mode 100644 index 00000000..f3263b6e --- /dev/null +++ b/clients/js/vault_client/instructions/revokeDelegateTokenAccount.ts @@ -0,0 +1,247 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { + combineCodec, + getStructDecoder, + getStructEncoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type Address, + type Codec, + type Decoder, + type Encoder, + type IAccountMeta, + type IAccountSignerMeta, + type IInstruction, + type IInstructionWithAccounts, + type IInstructionWithData, + type ReadonlyAccount, + type ReadonlySignerAccount, + type TransactionSigner, + type WritableAccount, +} from '@solana/web3.js'; +import { JITO_VAULT_PROGRAM_ADDRESS } from '../programs'; +import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; + +export const REVOKE_DELEGATE_TOKEN_ACCOUNT_DISCRIMINATOR = 32; + +export function getRevokeDelegateTokenAccountDiscriminatorBytes() { + return getU8Encoder().encode(REVOKE_DELEGATE_TOKEN_ACCOUNT_DISCRIMINATOR); +} + +export type RevokeDelegateTokenAccountInstruction< + TProgram extends string = typeof JITO_VAULT_PROGRAM_ADDRESS, + TAccountConfig extends string | IAccountMeta = string, + TAccountVault extends string | IAccountMeta = string, + TAccountDelegateAssetAdmin extends string | IAccountMeta = string, + TAccountTokenMint extends string | IAccountMeta = string, + TAccountTokenAccount extends string | IAccountMeta = string, + TAccountTokenProgram extends + | string + | IAccountMeta = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', + TRemainingAccounts extends readonly IAccountMeta[] = [], +> = IInstruction & + IInstructionWithData & + IInstructionWithAccounts< + [ + TAccountConfig extends string + ? ReadonlyAccount + : TAccountConfig, + TAccountVault extends string + ? ReadonlyAccount + : TAccountVault, + TAccountDelegateAssetAdmin extends string + ? ReadonlySignerAccount & + IAccountSignerMeta + : TAccountDelegateAssetAdmin, + TAccountTokenMint extends string + ? ReadonlyAccount + : TAccountTokenMint, + TAccountTokenAccount extends string + ? WritableAccount + : TAccountTokenAccount, + TAccountTokenProgram extends string + ? ReadonlyAccount + : TAccountTokenProgram, + ...TRemainingAccounts, + ] + >; + +export type RevokeDelegateTokenAccountInstructionData = { + discriminator: number; +}; + +export type RevokeDelegateTokenAccountInstructionDataArgs = {}; + +export function getRevokeDelegateTokenAccountInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([['discriminator', getU8Encoder()]]), + (value) => ({ + ...value, + discriminator: REVOKE_DELEGATE_TOKEN_ACCOUNT_DISCRIMINATOR, + }) + ); +} + +export function getRevokeDelegateTokenAccountInstructionDataDecoder(): Decoder { + return getStructDecoder([['discriminator', getU8Decoder()]]); +} + +export function getRevokeDelegateTokenAccountInstructionDataCodec(): Codec< + RevokeDelegateTokenAccountInstructionDataArgs, + RevokeDelegateTokenAccountInstructionData +> { + return combineCodec( + getRevokeDelegateTokenAccountInstructionDataEncoder(), + getRevokeDelegateTokenAccountInstructionDataDecoder() + ); +} + +export type RevokeDelegateTokenAccountInput< + TAccountConfig extends string = string, + TAccountVault extends string = string, + TAccountDelegateAssetAdmin extends string = string, + TAccountTokenMint extends string = string, + TAccountTokenAccount extends string = string, + TAccountTokenProgram extends string = string, +> = { + config: Address; + vault: Address; + delegateAssetAdmin: TransactionSigner; + tokenMint: Address; + tokenAccount: Address; + tokenProgram?: Address; +}; + +export function getRevokeDelegateTokenAccountInstruction< + TAccountConfig extends string, + TAccountVault extends string, + TAccountDelegateAssetAdmin extends string, + TAccountTokenMint extends string, + TAccountTokenAccount extends string, + TAccountTokenProgram extends string, +>( + input: RevokeDelegateTokenAccountInput< + TAccountConfig, + TAccountVault, + TAccountDelegateAssetAdmin, + TAccountTokenMint, + TAccountTokenAccount, + TAccountTokenProgram + > +): RevokeDelegateTokenAccountInstruction< + typeof JITO_VAULT_PROGRAM_ADDRESS, + TAccountConfig, + TAccountVault, + TAccountDelegateAssetAdmin, + TAccountTokenMint, + TAccountTokenAccount, + TAccountTokenProgram +> { + // Program address. + const programAddress = JITO_VAULT_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + config: { value: input.config ?? null, isWritable: false }, + vault: { value: input.vault ?? null, isWritable: false }, + delegateAssetAdmin: { + value: input.delegateAssetAdmin ?? null, + isWritable: false, + }, + tokenMint: { value: input.tokenMint ?? null, isWritable: false }, + tokenAccount: { value: input.tokenAccount ?? null, isWritable: true }, + tokenProgram: { value: input.tokenProgram ?? null, isWritable: false }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + // Resolve default values. + if (!accounts.tokenProgram.value) { + accounts.tokenProgram.value = + 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' as Address<'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'>; + } + + const getAccountMeta = getAccountMetaFactory(programAddress, 'programId'); + const instruction = { + accounts: [ + getAccountMeta(accounts.config), + getAccountMeta(accounts.vault), + getAccountMeta(accounts.delegateAssetAdmin), + getAccountMeta(accounts.tokenMint), + getAccountMeta(accounts.tokenAccount), + getAccountMeta(accounts.tokenProgram), + ], + programAddress, + data: getRevokeDelegateTokenAccountInstructionDataEncoder().encode({}), + } as RevokeDelegateTokenAccountInstruction< + typeof JITO_VAULT_PROGRAM_ADDRESS, + TAccountConfig, + TAccountVault, + TAccountDelegateAssetAdmin, + TAccountTokenMint, + TAccountTokenAccount, + TAccountTokenProgram + >; + + return instruction; +} + +export type ParsedRevokeDelegateTokenAccountInstruction< + TProgram extends string = typeof JITO_VAULT_PROGRAM_ADDRESS, + TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], +> = { + programAddress: Address; + accounts: { + config: TAccountMetas[0]; + vault: TAccountMetas[1]; + delegateAssetAdmin: TAccountMetas[2]; + tokenMint: TAccountMetas[3]; + tokenAccount: TAccountMetas[4]; + tokenProgram: TAccountMetas[5]; + }; + data: RevokeDelegateTokenAccountInstructionData; +}; + +export function parseRevokeDelegateTokenAccountInstruction< + TProgram extends string, + TAccountMetas extends readonly IAccountMeta[], +>( + instruction: IInstruction & + IInstructionWithAccounts & + IInstructionWithData +): ParsedRevokeDelegateTokenAccountInstruction { + if (instruction.accounts.length < 6) { + // 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(), + vault: getNextAccount(), + delegateAssetAdmin: getNextAccount(), + tokenMint: getNextAccount(), + tokenAccount: getNextAccount(), + tokenProgram: getNextAccount(), + }, + data: getRevokeDelegateTokenAccountInstructionDataDecoder().decode( + instruction.data + ), + }; +} diff --git a/clients/js/vault_client/programs/jitoVault.ts b/clients/js/vault_client/programs/jitoVault.ts index 83cd3290..05e811c4 100644 --- a/clients/js/vault_client/programs/jitoVault.ts +++ b/clients/js/vault_client/programs/jitoVault.ts @@ -33,6 +33,7 @@ import { type ParsedInitializeVaultUpdateStateTrackerInstruction, type ParsedInitializeVaultWithMintInstruction, type ParsedMintToInstruction, + type ParsedRevokeDelegateTokenAccountInstruction, type ParsedSetAdminInstruction, type ParsedSetConfigAdminInstruction, type ParsedSetDepositCapacityInstruction, @@ -94,6 +95,7 @@ export enum JitoVaultInstruction { CreateTokenMetadata, UpdateTokenMetadata, SetConfigAdmin, + RevokeDelegateTokenAccount, } export function identifyJitoVaultInstruction( @@ -196,6 +198,9 @@ export function identifyJitoVaultInstruction( if (containsBytes(data, getU8Encoder().encode(31), 0)) { return JitoVaultInstruction.SetConfigAdmin; } + if (containsBytes(data, getU8Encoder().encode(32), 0)) { + return JitoVaultInstruction.RevokeDelegateTokenAccount; + } throw new Error( 'The provided instruction could not be identified as a jitoVault instruction.' ); @@ -299,4 +304,7 @@ export type ParsedJitoVaultInstruction< } & ParsedUpdateTokenMetadataInstruction) | ({ instructionType: JitoVaultInstruction.SetConfigAdmin; - } & ParsedSetConfigAdminInstruction); + } & ParsedSetConfigAdminInstruction) + | ({ + instructionType: JitoVaultInstruction.RevokeDelegateTokenAccount; + } & ParsedRevokeDelegateTokenAccountInstruction); diff --git a/clients/rust/vault_client/src/generated/instructions/mod.rs b/clients/rust/vault_client/src/generated/instructions/mod.rs index 5e426226..aafc25c3 100644 --- a/clients/rust/vault_client/src/generated/instructions/mod.rs +++ b/clients/rust/vault_client/src/generated/instructions/mod.rs @@ -24,6 +24,7 @@ pub(crate) mod r#initialize_vault_operator_delegation; 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#revoke_delegate_token_account; pub(crate) mod r#set_admin; pub(crate) mod r#set_config_admin; pub(crate) mod r#set_deposit_capacity; @@ -46,8 +47,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_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::*, + r#revoke_delegate_token_account::*, 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/revoke_delegate_token_account.rs b/clients/rust/vault_client/src/generated/instructions/revoke_delegate_token_account.rs new file mode 100644 index 00000000..7ec74562 --- /dev/null +++ b/clients/rust/vault_client/src/generated/instructions/revoke_delegate_token_account.rs @@ -0,0 +1,486 @@ +//! 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 RevokeDelegateTokenAccount { + pub config: solana_program::pubkey::Pubkey, + + pub vault: solana_program::pubkey::Pubkey, + + pub delegate_asset_admin: solana_program::pubkey::Pubkey, + + pub token_mint: solana_program::pubkey::Pubkey, + + pub token_account: solana_program::pubkey::Pubkey, + + pub token_program: solana_program::pubkey::Pubkey, +} + +impl RevokeDelegateTokenAccount { + 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(6 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.config, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.vault, false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.delegate_asset_admin, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.token_mint, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + self.token_account, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + self.token_program, + false, + )); + accounts.extend_from_slice(remaining_accounts); + let data = RevokeDelegateTokenAccountInstructionData::new() + .try_to_vec() + .unwrap(); + + solana_program::instruction::Instruction { + program_id: crate::JITO_VAULT_ID, + accounts, + data, + } + } +} + +#[derive(BorshDeserialize, BorshSerialize)] +pub struct RevokeDelegateTokenAccountInstructionData { + discriminator: u8, +} + +impl RevokeDelegateTokenAccountInstructionData { + pub fn new() -> Self { + Self { discriminator: 32 } + } +} + +impl Default for RevokeDelegateTokenAccountInstructionData { + fn default() -> Self { + Self::new() + } +} + +/// Instruction builder for `RevokeDelegateTokenAccount`. +/// +/// ### Accounts: +/// +/// 0. `[]` config +/// 1. `[]` vault +/// 2. `[signer]` delegate_asset_admin +/// 3. `[]` token_mint +/// 4. `[writable]` token_account +/// 5. `[optional]` token_program (default to `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`) +#[derive(Clone, Debug, Default)] +pub struct RevokeDelegateTokenAccountBuilder { + config: Option, + vault: Option, + delegate_asset_admin: Option, + token_mint: Option, + token_account: Option, + token_program: Option, + __remaining_accounts: Vec, +} + +impl RevokeDelegateTokenAccountBuilder { + 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 vault(&mut self, vault: solana_program::pubkey::Pubkey) -> &mut Self { + self.vault = Some(vault); + self + } + #[inline(always)] + pub fn delegate_asset_admin( + &mut self, + delegate_asset_admin: solana_program::pubkey::Pubkey, + ) -> &mut Self { + self.delegate_asset_admin = Some(delegate_asset_admin); + self + } + #[inline(always)] + pub fn token_mint(&mut self, token_mint: solana_program::pubkey::Pubkey) -> &mut Self { + self.token_mint = Some(token_mint); + self + } + #[inline(always)] + pub fn token_account(&mut self, token_account: solana_program::pubkey::Pubkey) -> &mut Self { + self.token_account = Some(token_account); + self + } + /// `[optional account, default to 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA']` + #[inline(always)] + pub fn token_program(&mut self, token_program: solana_program::pubkey::Pubkey) -> &mut Self { + self.token_program = Some(token_program); + 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 = RevokeDelegateTokenAccount { + config: self.config.expect("config is not set"), + vault: self.vault.expect("vault is not set"), + delegate_asset_admin: self + .delegate_asset_admin + .expect("delegate_asset_admin is not set"), + token_mint: self.token_mint.expect("token_mint is not set"), + token_account: self.token_account.expect("token_account is not set"), + token_program: self.token_program.unwrap_or(solana_program::pubkey!( + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + )), + }; + + accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) + } +} + +/// `revoke_delegate_token_account` CPI accounts. +pub struct RevokeDelegateTokenAccountCpiAccounts<'a, 'b> { + pub config: &'b solana_program::account_info::AccountInfo<'a>, + + pub vault: &'b solana_program::account_info::AccountInfo<'a>, + + pub delegate_asset_admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub token_mint: &'b solana_program::account_info::AccountInfo<'a>, + + pub token_account: &'b solana_program::account_info::AccountInfo<'a>, + + pub token_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +/// `revoke_delegate_token_account` CPI instruction. +pub struct RevokeDelegateTokenAccountCpi<'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 vault: &'b solana_program::account_info::AccountInfo<'a>, + + pub delegate_asset_admin: &'b solana_program::account_info::AccountInfo<'a>, + + pub token_mint: &'b solana_program::account_info::AccountInfo<'a>, + + pub token_account: &'b solana_program::account_info::AccountInfo<'a>, + + pub token_program: &'b solana_program::account_info::AccountInfo<'a>, +} + +impl<'a, 'b> RevokeDelegateTokenAccountCpi<'a, 'b> { + pub fn new( + program: &'b solana_program::account_info::AccountInfo<'a>, + accounts: RevokeDelegateTokenAccountCpiAccounts<'a, 'b>, + ) -> Self { + Self { + __program: program, + config: accounts.config, + vault: accounts.vault, + delegate_asset_admin: accounts.delegate_asset_admin, + token_mint: accounts.token_mint, + token_account: accounts.token_account, + token_program: accounts.token_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(6 + remaining_accounts.len()); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.config.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.vault.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.delegate_asset_admin.key, + true, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.token_mint.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new( + *self.token_account.key, + false, + )); + accounts.push(solana_program::instruction::AccountMeta::new_readonly( + *self.token_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 = RevokeDelegateTokenAccountInstructionData::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(6 + 1 + remaining_accounts.len()); + account_infos.push(self.__program.clone()); + account_infos.push(self.config.clone()); + account_infos.push(self.vault.clone()); + account_infos.push(self.delegate_asset_admin.clone()); + account_infos.push(self.token_mint.clone()); + account_infos.push(self.token_account.clone()); + account_infos.push(self.token_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 `RevokeDelegateTokenAccount` via CPI. +/// +/// ### Accounts: +/// +/// 0. `[]` config +/// 1. `[]` vault +/// 2. `[signer]` delegate_asset_admin +/// 3. `[]` token_mint +/// 4. `[writable]` token_account +/// 5. `[]` token_program +#[derive(Clone, Debug)] +pub struct RevokeDelegateTokenAccountCpiBuilder<'a, 'b> { + instruction: Box>, +} + +impl<'a, 'b> RevokeDelegateTokenAccountCpiBuilder<'a, 'b> { + pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { + let instruction = Box::new(RevokeDelegateTokenAccountCpiBuilderInstruction { + __program: program, + config: None, + vault: None, + delegate_asset_admin: None, + token_mint: None, + token_account: None, + token_program: 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 vault(&mut self, vault: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self { + self.instruction.vault = Some(vault); + self + } + #[inline(always)] + pub fn delegate_asset_admin( + &mut self, + delegate_asset_admin: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.delegate_asset_admin = Some(delegate_asset_admin); + self + } + #[inline(always)] + pub fn token_mint( + &mut self, + token_mint: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.token_mint = Some(token_mint); + self + } + #[inline(always)] + pub fn token_account( + &mut self, + token_account: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.token_account = Some(token_account); + self + } + #[inline(always)] + pub fn token_program( + &mut self, + token_program: &'b solana_program::account_info::AccountInfo<'a>, + ) -> &mut Self { + self.instruction.token_program = Some(token_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 = RevokeDelegateTokenAccountCpi { + __program: self.instruction.__program, + + config: self.instruction.config.expect("config is not set"), + + vault: self.instruction.vault.expect("vault is not set"), + + delegate_asset_admin: self + .instruction + .delegate_asset_admin + .expect("delegate_asset_admin is not set"), + + token_mint: self.instruction.token_mint.expect("token_mint is not set"), + + token_account: self + .instruction + .token_account + .expect("token_account is not set"), + + token_program: self + .instruction + .token_program + .expect("token_program is not set"), + }; + instruction.invoke_signed_with_remaining_accounts( + signers_seeds, + &self.instruction.__remaining_accounts, + ) + } +} + +#[derive(Clone, Debug)] +struct RevokeDelegateTokenAccountCpiBuilderInstruction<'a, 'b> { + __program: &'b solana_program::account_info::AccountInfo<'a>, + config: Option<&'b solana_program::account_info::AccountInfo<'a>>, + vault: Option<&'b solana_program::account_info::AccountInfo<'a>>, + delegate_asset_admin: Option<&'b solana_program::account_info::AccountInfo<'a>>, + token_mint: Option<&'b solana_program::account_info::AccountInfo<'a>>, + token_account: Option<&'b solana_program::account_info::AccountInfo<'a>>, + token_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/idl/jito_vault.json b/idl/jito_vault.json index 00e1c9bf..ce734b6b 100644 --- a/idl/jito_vault.json +++ b/idl/jito_vault.json @@ -1391,6 +1391,46 @@ "type": "u8", "value": 31 } + }, + { + "name": "RevokeDelegateTokenAccount", + "accounts": [ + { + "name": "config", + "isMut": false, + "isSigner": false + }, + { + "name": "vault", + "isMut": false, + "isSigner": false + }, + { + "name": "delegateAssetAdmin", + "isMut": false, + "isSigner": true + }, + { + "name": "tokenMint", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenAccount", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [], + "discriminant": { + "type": "u8", + "value": 32 + } } ], "accounts": [ diff --git a/integration_tests/tests/fixtures/vault_client.rs b/integration_tests/tests/fixtures/vault_client.rs index f8683331..1251c9a4 100644 --- a/integration_tests/tests/fixtures/vault_client.rs +++ b/integration_tests/tests/fixtures/vault_client.rs @@ -844,6 +844,33 @@ impl VaultProgramClient { .await } + pub async fn revoke_delegate_token_account( + &mut self, + config: &Pubkey, + vault: &Pubkey, + delegate_asset_admin: &Keypair, + token_mint: &Pubkey, + token_account: &Pubkey, + token_program_id: &Pubkey, + ) -> Result<(), TestError> { + let blockhash = self.banks_client.get_latest_blockhash().await?; + self._process_transaction(&Transaction::new_signed_with_payer( + &[jito_vault_sdk::sdk::revoke_delegate_token_account( + &jito_vault_program::id(), + config, + vault, + &delegate_asset_admin.pubkey(), + token_mint, + token_account, + token_program_id, + )], + Some(&self.payer.pubkey()), + &[&self.payer, delegate_asset_admin], + blockhash, + )) + .await + } + pub async fn set_admin( &mut self, config: &Pubkey, diff --git a/integration_tests/tests/vault/mod.rs b/integration_tests/tests/vault/mod.rs index 86a02b09..1d091368 100644 --- a/integration_tests/tests/vault/mod.rs +++ b/integration_tests/tests/vault/mod.rs @@ -14,6 +14,7 @@ mod initialize_vault_ncn_slasher_ticket; mod initialize_vault_ncn_ticket; mod initialize_vault_operator_delegation; mod initialize_vault_update_state_tracker; +mod revoke_delegate_token_account; mod reward_fee; mod set_admin; mod set_capacity; diff --git a/integration_tests/tests/vault/revoke_delegate_token_account.rs b/integration_tests/tests/vault/revoke_delegate_token_account.rs new file mode 100644 index 00000000..82c4ff54 --- /dev/null +++ b/integration_tests/tests/vault/revoke_delegate_token_account.rs @@ -0,0 +1,254 @@ +#[cfg(test)] +mod tests { + use jito_vault_core::config::Config; + use jito_vault_sdk::error::VaultError; + use solana_program::{program_option::COption, pubkey::Pubkey}; + use solana_sdk::signature::{Keypair, Signer}; + use spl_associated_token_account::get_associated_token_address; + use test_case::test_case; + + use crate::fixtures::{ + fixture::TestBuilder, + vault_client::{assert_vault_error, VaultRoot}, + }; + + const MINT_AMOUNT: u64 = 100_000; + + async fn setup(token_program_id: &Pubkey) -> (TestBuilder, Pubkey, Keypair, Keypair, Keypair) { + let mut fixture = TestBuilder::new().await; + + let mut vault_program_client = fixture.vault_program_client(); + + let ( + _config_admin, + VaultRoot { + vault_pubkey, + vault_admin, + }, + ) = vault_program_client + .setup_config_and_vault(99, 100, 101) + .await + .unwrap(); + + let random_mint = Keypair::new(); + vault_program_client + .create_token_mint(&random_mint, token_program_id) + .await + .unwrap(); + + let vault_token_account = Keypair::new(); + if token_program_id.eq(&spl_token::id()) { + fixture + .mint_spl_to( + &random_mint.pubkey(), + &vault_pubkey, + MINT_AMOUNT, + token_program_id, + ) + .await + .unwrap(); + } else { + fixture + .create_token_account( + token_program_id, + &vault_token_account, + &random_mint.pubkey(), + &vault_pubkey, + &[], + ) + .await + .unwrap(); + fixture + .mint_spl_to( + &random_mint.pubkey(), + &vault_token_account.pubkey(), + MINT_AMOUNT, + token_program_id, + ) + .await + .unwrap(); + } + + ( + fixture, + vault_pubkey, + vault_admin, + random_mint, + vault_token_account, + ) + } + + #[test_case(spl_token::id(); "token")] + // #[test_case(spl_token_2022::id(); "token-2022")] + #[tokio::test] + async fn test_delegate_token_account_ok(token_program_id: Pubkey) { + let (mut fixture, vault_pubkey, vault_admin, random_mint, vault_token_account) = + setup(&token_program_id).await; + let mut vault_program_client = fixture.vault_program_client(); + let config_pubkey = Config::find_program_address(&jito_vault_program::id()).0; + + let bob = Pubkey::new_unique(); + if token_program_id.eq(&spl_token::id()) { + // Delegate + vault_program_client + .delegate_token_account( + &config_pubkey, + &vault_pubkey, + &vault_admin, + &random_mint.pubkey(), + &get_associated_token_address(&vault_pubkey, &random_mint.pubkey()), + &bob, + &token_program_id, + ) + .await + .unwrap(); + let ata = get_associated_token_address(&vault_pubkey, &random_mint.pubkey()); + let token_account_acc = fixture.get_token_account(&ata).await.unwrap(); + + assert_eq!(token_account_acc.delegate, COption::Some(bob)); + assert_eq!(token_account_acc.delegated_amount, u64::MAX); + + // Revoke + vault_program_client + .revoke_delegate_token_account( + &config_pubkey, + &vault_pubkey, + &vault_admin, + &random_mint.pubkey(), + &get_associated_token_address(&vault_pubkey, &random_mint.pubkey()), + &token_program_id, + ) + .await + .unwrap(); + let ata = get_associated_token_address(&vault_pubkey, &random_mint.pubkey()); + let token_account_acc = fixture.get_token_account(&ata).await.unwrap(); + + assert_eq!(token_account_acc.delegate, COption::None); + assert_eq!(token_account_acc.delegated_amount, 0); + } else { + vault_program_client + .delegate_token_account( + &config_pubkey, + &vault_pubkey, + &vault_admin, + &random_mint.pubkey(), + &vault_token_account.pubkey(), + &bob, + &token_program_id, + ) + .await + .unwrap(); + + let vault_token_acc = fixture + .get_token_account(&vault_token_account.pubkey()) + .await + .unwrap(); + + assert_eq!(vault_token_acc.delegate, COption::Some(bob)); + assert_eq!(vault_token_acc.delegated_amount, u64::MAX); + + // Revoke + vault_program_client + .delegate_token_account( + &config_pubkey, + &vault_pubkey, + &vault_admin, + &random_mint.pubkey(), + &vault_token_account.pubkey(), + &bob, + &token_program_id, + ) + .await + .unwrap(); + + let vault_token_acc = fixture + .get_token_account(&vault_token_account.pubkey()) + .await + .unwrap(); + + assert_eq!(vault_token_acc.delegate, COption::None); + assert_eq!(vault_token_acc.delegated_amount, 0); + } + } + + #[test_case(spl_token::id(); "token")] + // #[test_case(spl_token_2022::id(); "token-2022")] + #[tokio::test] + async fn test_delegate_vault_wrong_delegate_asset_admin_fails(token_program_id: Pubkey) { + let (mut fixture, vault_pubkey, vault_admin, random_mint, vault_token_account) = + setup(&token_program_id).await; + let mut vault_program_client = fixture.vault_program_client(); + let config_pubkey = Config::find_program_address(&jito_vault_program::id()).0; + + let wrong_delegate_asset_admin = Keypair::new(); + let bob = Pubkey::new_unique(); + if token_program_id.eq(&spl_token::id()) { + // Delegate + vault_program_client + .delegate_token_account( + &config_pubkey, + &vault_pubkey, + &vault_admin, + &random_mint.pubkey(), + &get_associated_token_address(&vault_pubkey, &random_mint.pubkey()), + &bob, + &token_program_id, + ) + .await + .unwrap(); + let ata = get_associated_token_address(&vault_pubkey, &random_mint.pubkey()); + let token_account_acc = fixture.get_token_account(&ata).await.unwrap(); + + assert_eq!(token_account_acc.delegate, COption::Some(bob)); + assert_eq!(token_account_acc.delegated_amount, u64::MAX); + + // Revoke + let response = vault_program_client + .revoke_delegate_token_account( + &config_pubkey, + &vault_pubkey, + &wrong_delegate_asset_admin, + &random_mint.pubkey(), + &get_associated_token_address(&vault_pubkey, &random_mint.pubkey()), + &token_program_id, + ) + .await; + + assert_vault_error(response, VaultError::VaultDelegateAssetAdminInvalid); + } else { + vault_program_client + .delegate_token_account( + &config_pubkey, + &vault_pubkey, + &vault_admin, + &random_mint.pubkey(), + &vault_token_account.pubkey(), + &bob, + &token_program_id, + ) + .await + .unwrap(); + + let vault_token_acc = fixture + .get_token_account(&vault_token_account.pubkey()) + .await + .unwrap(); + + assert_eq!(vault_token_acc.delegate, COption::Some(bob)); + assert_eq!(vault_token_acc.delegated_amount, u64::MAX); + + let response = vault_program_client + .revoke_delegate_token_account( + &config_pubkey, + &vault_pubkey, + &wrong_delegate_asset_admin, + &random_mint.pubkey(), + &vault_token_account.pubkey(), + &token_program_id, + ) + .await; + + assert_vault_error(response, VaultError::VaultDelegateAssetAdminInvalid); + } + } +} diff --git a/vault_program/src/lib.rs b/vault_program/src/lib.rs index ad620244..c730e336 100644 --- a/vault_program/src/lib.rs +++ b/vault_program/src/lib.rs @@ -18,6 +18,7 @@ mod initialize_vault_operator_delegation; mod initialize_vault_update_state_tracker; mod initialize_vault_with_mint; mod mint_to; +mod revoke_delegate_token_account; mod set_admin; mod set_capacity; mod set_config_admin; @@ -59,6 +60,7 @@ use crate::{ initialize_vault_operator_delegation::process_initialize_vault_operator_delegation, initialize_vault_update_state_tracker::process_initialize_vault_update_state_tracker, initialize_vault_with_mint::process_initialize_vault_with_mint, mint_to::process_mint, + revoke_delegate_token_account::process_revoke_delegate_token_account, set_admin::process_set_admin, set_capacity::process_set_deposit_capacity, 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, @@ -162,6 +164,10 @@ pub fn process_instruction( msg!("Instruction: DelegateTokenAccount"); process_delegate_token_account(program_id, accounts) } + VaultInstruction::RevokeDelegateTokenAccount => { + msg!("Instruction: RevokeDelegateTokenAccount"); + process_revoke_delegate_token_account(program_id, accounts) + } VaultInstruction::SetFees { deposit_fee_bps, withdrawal_fee_bps, diff --git a/vault_program/src/revoke_delegate_token_account.rs b/vault_program/src/revoke_delegate_token_account.rs new file mode 100644 index 00000000..8003ee2b --- /dev/null +++ b/vault_program/src/revoke_delegate_token_account.rs @@ -0,0 +1,84 @@ +use jito_bytemuck::AccountDeserialize; +use jito_jsm_core::loader::{load_signer, load_token_account, load_token_mint, load_token_program}; +use jito_vault_core::{config::Config, vault::Vault}; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, msg, program::invoke_signed, + program_error::ProgramError, pubkey::Pubkey, +}; + +/// Processes the revoke delegate token account instruction: [`crate::VaultInstruction::RevokeDelegateTokenAccount`] +/// +/// This instruction handles the revoke delegation of a delegate set by the [`crate::VaultInstruction::DelegateTokenAccount`] instruction. +/// +/// # Arguments +/// * `program_id` - The public key of the program to ensure the correct program is being executed. +/// * `accounts` - A slice of `AccountInfo` representing the accounts required for this instruction. +/// +/// # Returns +/// * `ProgramResult` - Returns `Ok(())` if the delegation is successful, otherwise returns an appropriate `ProgramError`. +pub fn process_revoke_delegate_token_account( + program_id: &Pubkey, + accounts: &[AccountInfo], +) -> ProgramResult { + let [config, vault_info, delegate_asset_admin, token_mint, token_account, token_program_info] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + Config::load(program_id, config, false)?; + Vault::load(program_id, vault_info, false)?; + load_signer(delegate_asset_admin, false)?; + load_token_mint(token_mint)?; + load_token_account( + token_account, + vault_info.key, + token_mint.key, + token_program_info, + )?; + // Only the original spl token program is allowed + load_token_program(token_program_info)?; + + // The owner of token mint and token account must match + if token_mint.owner.ne(token_account.owner) { + return Err(ProgramError::InvalidAccountData); + } + + let vault_data = vault_info.data.borrow(); + let vault = Vault::try_from_slice_unchecked(&vault_data)?; + if vault.supported_mint.eq(token_mint.key) { + msg!("Cannot delegate away the supported mint for a vault!"); + return Err(ProgramError::InvalidAccountData); + } + + // The Vault delegate_asset_admin shall be the signer of the transaction + vault.check_delegate_asset_admin(delegate_asset_admin.key)?; + + let vault_seeds = vault.signing_seeds(); + + drop(vault_data); + + // This is compatible with the spl-token and spl-token-2022 programs + let ix = spl_token_2022::instruction::revoke( + token_program_info.key, + token_account.key, + vault_info.key, + &[], + )?; + + invoke_signed( + &ix, + &[ + token_program_info.clone(), + token_account.clone(), + vault_info.clone(), + ], + &[vault_seeds + .iter() + .map(|seed| seed.as_slice()) + .collect::>() + .as_slice()], + )?; + + Ok(()) +} diff --git a/vault_sdk/src/instruction.rs b/vault_sdk/src/instruction.rs index e29892d6..5a4e1d65 100644 --- a/vault_sdk/src/instruction.rs +++ b/vault_sdk/src/instruction.rs @@ -320,6 +320,14 @@ pub enum VaultInstruction { #[account(2, name = "new_admin")] SetConfigAdmin, + /// Revoke Delegate of the token account + #[account(0, name = "config")] + #[account(1, name = "vault")] + #[account(2, signer, name = "delegate_asset_admin")] + #[account(3, name = "token_mint")] + #[account(4, writable, name = "token_account")] + #[account(5, name = "token_program")] + RevokeDelegateTokenAccount, } #[derive(Debug, BorshSerialize, BorshDeserialize)] diff --git a/vault_sdk/src/sdk.rs b/vault_sdk/src/sdk.rs index c4faaea6..69d201df 100644 --- a/vault_sdk/src/sdk.rs +++ b/vault_sdk/src/sdk.rs @@ -302,6 +302,33 @@ pub fn delegate_token_account( } } +#[allow(clippy::too_many_arguments)] +pub fn revoke_delegate_token_account( + program_id: &Pubkey, + config: &Pubkey, + vault: &Pubkey, + delegate_asset_admin: &Pubkey, + token_mint: &Pubkey, + token_account: &Pubkey, + token_program_id: &Pubkey, +) -> Instruction { + let accounts = vec![ + AccountMeta::new_readonly(*config, false), + AccountMeta::new_readonly(*vault, false), + AccountMeta::new_readonly(*delegate_asset_admin, true), + AccountMeta::new_readonly(*token_mint, false), + AccountMeta::new(*token_account, false), + AccountMeta::new_readonly(*token_program_id, false), + ]; + Instruction { + program_id: *program_id, + accounts, + data: VaultInstruction::RevokeDelegateTokenAccount + .try_to_vec() + .unwrap(), + } +} + pub fn set_admin( program_id: &Pubkey, config: &Pubkey,