From 47e62e40378c7df84bee834e5540b89c457f901a Mon Sep 17 00:00:00 2001 From: wphan Date: Sat, 9 Sep 2023 14:57:09 -0700 Subject: [PATCH] wip, getting change balance of read only account error, see test --- programs/drift_vaults/src/cpi.rs | 4 + programs/drift_vaults/src/error.rs | 2 + .../src/instructions/constraints.rs | 7 + .../src/instructions/delete_vault.rs | 96 +++++++++++++ programs/drift_vaults/src/instructions/mod.rs | 2 + programs/drift_vaults/src/lib.rs | 6 + tests/driftVaults.ts | 44 ++++++ ts/sdk/cli/cli.ts | 2 +- ts/sdk/cli/commands/initVault.ts | 45 +++--- ts/sdk/cli/commands/managerUpdateVault.ts | 4 + ts/sdk/src/idl/drift_vaults.json | 66 +++++++++ ts/sdk/src/types/drift_vaults.ts | 132 ++++++++++++++++++ ts/sdk/src/vaultClient.ts | 39 ++++++ 13 files changed, 432 insertions(+), 17 deletions(-) create mode 100644 programs/drift_vaults/src/instructions/delete_vault.rs diff --git a/programs/drift_vaults/src/cpi.rs b/programs/drift_vaults/src/cpi.rs index 292a1baa..0bd512d3 100644 --- a/programs/drift_vaults/src/cpi.rs +++ b/programs/drift_vaults/src/cpi.rs @@ -6,6 +6,10 @@ pub trait InitializeUserCPI { fn drift_initialize_user_stats(&self, name: [u8; 32], bump: u8) -> Result<()>; } +pub trait DeleteUserCPI { + fn delete_user(&self, name: [u8; 32], bump: u8) -> Result<()>; +} + pub trait DepositCPI { fn drift_deposit(&self, amount: u64) -> Result<()>; } diff --git a/programs/drift_vaults/src/error.rs b/programs/drift_vaults/src/error.rs index 4abcfb05..3c883e4b 100644 --- a/programs/drift_vaults/src/error.rs +++ b/programs/drift_vaults/src/error.rs @@ -51,6 +51,8 @@ pub enum ErrorCode { InvalidVaultDeposit, #[msg("OngoingLiquidation")] OngoingLiquidation, + #[msg("VaultCantBeDeleted")] + VaultCantBeDeleted, } impl From for ErrorCode { diff --git a/programs/drift_vaults/src/instructions/constraints.rs b/programs/drift_vaults/src/instructions/constraints.rs index bd124138..b43a3d6f 100644 --- a/programs/drift_vaults/src/instructions/constraints.rs +++ b/programs/drift_vaults/src/instructions/constraints.rs @@ -43,3 +43,10 @@ pub fn is_user_stats_for_vault( ) -> anchor_lang::Result { Ok(vault_depositor.load()?.user_stats.eq(user_stats.key)) } + +pub fn is_spot_market_index( + vault: &AccountLoader, + spot_market_index: u16, +) -> anchor_lang::Result { + Ok(vault.load()?.spot_market_index == spot_market_index) +} diff --git a/programs/drift_vaults/src/instructions/delete_vault.rs b/programs/drift_vaults/src/instructions/delete_vault.rs new file mode 100644 index 00000000..d968a35b --- /dev/null +++ b/programs/drift_vaults/src/instructions/delete_vault.rs @@ -0,0 +1,96 @@ +use crate::constraints::{ + is_manager_for_vault, is_spot_market_index, is_user_for_vault, is_user_stats_for_vault, +}; + +use crate::cpi::DeleteUserCPI; +use crate::declare_vault_seeds; +use crate::{error::ErrorCode, validate, Vault}; +use anchor_lang::prelude::*; +use anchor_spl::token::{Mint, TokenAccount}; +use drift::cpi::accounts::DeleteUser as DriftDeleteUser; +use drift::program::Drift; +use drift::state::spot_market::SpotMarket; +use drift::state::user::User; + +pub fn delete_vault<'info>(ctx: Context<'_, '_, '_, 'info, DeleteVault<'info>>) -> Result<()> { + let vault = ctx.accounts.vault.load()?; + + validate!( + vault.total_shares == 0, + ErrorCode::VaultCantBeDeleted, + "cannot delete vault with outstanding shares" + ); + + ctx.delete_user(vault.name, vault.bump)?; + + Ok(()) +} + +#[derive(Accounts)] +pub struct DeleteVault<'info> { + #[account( + mut, + constraint = is_manager_for_vault(&vault, &manager)?, + close = payer, + )] + pub vault: AccountLoader<'info, Vault>, + #[account( + mut, + close = payer, + )] + pub token_account: Box>, + #[account( + mut, + constraint = is_spot_market_index(&vault, drift_spot_market.load()?.market_index)?, + )] + pub drift_spot_market: AccountLoader<'info, SpotMarket>, + #[account( + mut, + constraint = drift_spot_market.load()?.mint.eq(&drift_spot_market_mint.key()) + )] + pub drift_spot_market_mint: Box>, + /// CHECK: checked in drift cpi + #[account( + mut, + constraint = is_user_stats_for_vault(&vault, &drift_user_stats)?, + )] + /// CHECK: checked in drift cpi + pub drift_user_stats: AccountInfo<'info>, + #[account( + mut, + constraint = is_user_for_vault(&vault, &drift_user.key())?, + // close = manager + )] + /// CHECK: checked in drift cpi + pub drift_user: AccountLoader<'info, User>, + /// CHECK: checked in drift cpi + #[account(mut)] + pub drift_state: AccountInfo<'info>, + pub drift_program: Program<'info, Drift>, + #[account(mut)] + pub manager: Signer<'info>, + #[account(mut)] + pub payer: Signer<'info>, + pub rent: Sysvar<'info, Rent>, +} + +impl<'info> DeleteUserCPI for Context<'_, '_, '_, 'info, DeleteVault<'info>> { + fn delete_user(&self, name: [u8; 32], bump: u8) -> Result<()> { + let signature_seeds = Vault::get_vault_signer_seeds(&name, &bump); + let signers = &[&signature_seeds[..]]; + + let cpi_program = self.accounts.drift_program.to_account_info().clone(); + let cpi_accounts = DriftDeleteUser { + state: self.accounts.drift_state.clone(), + user: self.accounts.drift_user.to_account_info().clone(), + user_stats: self.accounts.drift_user_stats.clone(), + authority: self.accounts.vault.to_account_info().clone(), + }; + let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signers); + // .with_remaining_accounts(self.remaining_accounts.into()); + + drift::cpi::delete_user(cpi_ctx)?; + + Ok(()) + } +} diff --git a/programs/drift_vaults/src/instructions/mod.rs b/programs/drift_vaults/src/instructions/mod.rs index 77c1a5b9..6c5a9673 100644 --- a/programs/drift_vaults/src/instructions/mod.rs +++ b/programs/drift_vaults/src/instructions/mod.rs @@ -3,6 +3,7 @@ pub use cancel_withdraw_request::*; pub use deposit::*; pub use force_withdraw::*; pub use initialize_vault::*; +pub use delete_vault::*; pub use initialize_vault_depositor::*; pub use liquidate::*; pub use manager_cancel_withdraw_request::*; @@ -23,6 +24,7 @@ pub mod constraints; mod deposit; mod force_withdraw; mod initialize_vault; +mod delete_vault; mod initialize_vault_depositor; mod liquidate; mod manager_cancel_withdraw_request; diff --git a/programs/drift_vaults/src/lib.rs b/programs/drift_vaults/src/lib.rs index fe7eb42a..6bb1de10 100644 --- a/programs/drift_vaults/src/lib.rs +++ b/programs/drift_vaults/src/lib.rs @@ -23,6 +23,12 @@ pub mod drift_vaults { instructions::initialize_vault(ctx, params) } + pub fn delete_vault<'info>( + ctx: Context<'_, '_, '_, 'info, DeleteVault<'info>>, + ) -> Result<()> { + instructions::delete_vault(ctx) + } + pub fn update_delegate<'info>( ctx: Context<'_, '_, '_, 'info, UpdateDelegate<'info>>, delegate: Pubkey, diff --git a/tests/driftVaults.ts b/tests/driftVaults.ts index 453c7bcc..daec3037 100644 --- a/tests/driftVaults.ts +++ b/tests/driftVaults.ts @@ -70,6 +70,7 @@ describe('driftVaults', () => { profitShare: ZERO, hurdleRate: ZERO, permissioned: false, + minDepositAmount: ZERO, }); await adminClient.fetchAccounts(); @@ -217,4 +218,47 @@ describe('driftVaults', () => { await printTxLogs(provider.connection, txSig); }); + + it('Delete Vault', async () => { + const newVaultName = "another vault"; + await vaultClient.initializeVault({ + name: encodeName(newVaultName), + spotMarketIndex: 0, + redeemPeriod: ZERO, + maxTokens: ZERO, + managementFee: ZERO, + profitShare: ZERO, + hurdleRate: ZERO, + permissioned: false, + minDepositAmount: ZERO, + }); + const vaultAddress = getVaultAddressSync(program.programId, encodeName(newVaultName)); + console.log(`New vault address: ${vaultAddress.toBase58()}`); + + await adminClient.fetchAccounts(); + assert(adminClient.getStateAccount().numberOfAuthorities.eq(new BN(2))); + assert(adminClient.getStateAccount().numberOfSubAccounts.eq(new BN(2))); + + console.log("deleting vault"); + await vaultClient.deleteVault(vaultAddress); + + await adminClient.fetchAccounts(); + assert(adminClient.getStateAccount().numberOfAuthorities.eq(new BN(1))); + assert(adminClient.getStateAccount().numberOfSubAccounts.eq(new BN(1))); + + // await vaultClient.initializeVault({ + // name: encodeName(newVaultName), + // spotMarketIndex: 0, + // redeemPeriod: ZERO, + // maxTokens: ZERO, + // managementFee: ZERO, + // profitShare: ZERO, + // hurdleRate: ZERO, + // permissioned: false, + // minDepositAmount: ZERO, + // }); + // await adminClient.fetchAccounts(); + // assert(adminClient.getStateAccount().numberOfAuthorities.eq(new BN(2))); + // assert(adminClient.getStateAccount().numberOfSubAccounts.eq(new BN(2))); + }); }); diff --git a/ts/sdk/cli/cli.ts b/ts/sdk/cli/cli.ts index 8d4de2e4..d0d287bf 100644 --- a/ts/sdk/cli/cli.ts +++ b/ts/sdk/cli/cli.ts @@ -27,7 +27,7 @@ program program .command("init") .description("Initialize a new vault") - .option("-n, --name ", "Name of the vault to create", "my new vault") + .addOption(new Option("--delegate ", "Address of the delegate to trade the vault, default is vault manager").makeOptionMandatory(false)) .action((opts) => initVault(program, opts)); program .command("view-vault") diff --git a/ts/sdk/cli/commands/initVault.ts b/ts/sdk/cli/commands/initVault.ts index 749d63fd..2bd059dd 100644 --- a/ts/sdk/cli/commands/initVault.ts +++ b/ts/sdk/cli/commands/initVault.ts @@ -27,27 +27,40 @@ export const initVault = async (program: Command, cmdOpts: OptionValues) => { } const spotPrecision = TEN.pow(new BN(spotMarket.decimals)); - let newVaultName = cmdOpts.name; - if (!newVaultName) { - newVaultName = "my new vault"; - } - const vaultNameBytes = encodeName(newVaultName!); - console.log(`Initializing a new vault named '${newVaultName}'`); - const initTx = await driftVault.initializeVault({ - name: vaultNameBytes, + // throw new Error("[initVault] You're gonna want to find this message and complete the code"); + + // WARNING: fill in the below + // const initTx = await driftVault.initializeVault({ + // name: encodeName("my new vault"), + // spotMarketIndex: 0, + // redeemPeriod: new BN(3 * 60 * 60), // 3 hours + // maxTokens: new BN(1000).mul(spotPrecision), // 1000 USDC cap + // managementFee: PERCENTAGE_PRECISION.div(new BN(50)), // 2% + // profitShare: PERCENTAGE_PRECISION.div(new BN(5)), // 20% + // hurdleRate: 0, + // permissioned: false, + // minDepositAmount: new BN(10).mul(spotPrecision), // 10 USDC minimum deposit + // }); + const vaultParams = { + name: encodeName("Supercharger Vault"), spotMarketIndex: 0, - redeemPeriod: new BN(3 * 60 * 60), // 3 hours - maxTokens: new BN(1000).mul(spotPrecision), // 1000 USDC cap - managementFee: PERCENTAGE_PRECISION.div(new BN(50)), // 2% - profitShare: PERCENTAGE_PRECISION.div(new BN(5)), // 20% + redeemPeriod: new BN(30 * 24 * 60 * 60), // 30 days + maxTokens: new BN(100_000).mul(spotPrecision), + managementFee: new BN(0), // 0% + profitShare: PERCENTAGE_PRECISION.mul(new BN(3)).div(new BN(10)), // 30% hurdleRate: 0, - permissioned: false, - minDepositAmount: new BN(10).mul(spotPrecision), // 10 USDC minimum deposit - }); + permissioned: true, + minDepositAmount: new BN(1).mul(spotPrecision), // 1 USDC minimum deposit + } + console.log(`Initializing vault based on params':\n${JSON.stringify(vaultParams, null, 2)}'`); + + // throw new Error("check it"); + + const initTx = await driftVault.initializeVault(vaultParams); console.log(`Initialized vault, tx: ${initTx}`); - const vaultAddress = getVaultAddressSync(VAULT_PROGRAM_ID, vaultNameBytes); + const vaultAddress = getVaultAddressSync(VAULT_PROGRAM_ID, vaultParams.name); console.log(`New vault address: ${vaultAddress}`); let delegate = cmdOpts.delegate; diff --git a/ts/sdk/cli/commands/managerUpdateVault.ts b/ts/sdk/cli/commands/managerUpdateVault.ts index d2ff931b..649366bb 100644 --- a/ts/sdk/cli/commands/managerUpdateVault.ts +++ b/ts/sdk/cli/commands/managerUpdateVault.ts @@ -20,6 +20,10 @@ export const managerUpdateVault = async (program: Command, cmdOpts: OptionValues driftVault } = await getCommandContext(program, true); + // throw new Error("[updateVaultDepositor]: You're gonna want to find this message and complete the code"); + + // WARNING: fill in the below + const newParams = { redeemPeriod: new BN(30 * 60 * 60 * 24), // 30 days maxTokens: null, diff --git a/ts/sdk/src/idl/drift_vaults.json b/ts/sdk/src/idl/drift_vaults.json index b484ef74..b376eff2 100644 --- a/ts/sdk/src/idl/drift_vaults.json +++ b/ts/sdk/src/idl/drift_vaults.json @@ -80,6 +80,67 @@ } ] }, + { + "name": "deleteVault", + "accounts": [ + { + "name": "vault", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenAccount", + "isMut": true, + "isSigner": false + }, + { + "name": "driftSpotMarket", + "isMut": true, + "isSigner": false + }, + { + "name": "driftSpotMarketMint", + "isMut": true, + "isSigner": false + }, + { + "name": "driftUserStats", + "isMut": true, + "isSigner": false + }, + { + "name": "driftUser", + "isMut": true, + "isSigner": false + }, + { + "name": "driftState", + "isMut": true, + "isSigner": false + }, + { + "name": "driftProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "manager", + "isMut": true, + "isSigner": true + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "rent", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, { "name": "updateDelegate", "accounts": [ @@ -1539,6 +1600,11 @@ "code": 6021, "name": "OngoingLiquidation", "msg": "OngoingLiquidation" + }, + { + "code": 6022, + "name": "VaultCantBeDeleted", + "msg": "VaultCantBeDeleted" } ], "metadata": { diff --git a/ts/sdk/src/types/drift_vaults.ts b/ts/sdk/src/types/drift_vaults.ts index 356b4e99..b60b1c69 100644 --- a/ts/sdk/src/types/drift_vaults.ts +++ b/ts/sdk/src/types/drift_vaults.ts @@ -80,6 +80,67 @@ export type DriftVaults = { } ]; }, + { + name: 'deleteVault'; + accounts: [ + { + name: 'vault'; + isMut: true; + isSigner: false; + }, + { + name: 'tokenAccount'; + isMut: true; + isSigner: false; + }, + { + name: 'driftSpotMarket'; + isMut: true; + isSigner: false; + }, + { + name: 'driftSpotMarketMint'; + isMut: true; + isSigner: false; + }, + { + name: 'driftUserStats'; + isMut: true; + isSigner: false; + }, + { + name: 'driftUser'; + isMut: true; + isSigner: false; + }, + { + name: 'driftState'; + isMut: true; + isSigner: false; + }, + { + name: 'driftProgram'; + isMut: false; + isSigner: false; + }, + { + name: 'manager'; + isMut: true; + isSigner: true; + }, + { + name: 'payer'; + isMut: true; + isSigner: true; + }, + { + name: 'rent'; + isMut: false; + isSigner: false; + } + ]; + args: []; + }, { name: 'updateDelegate'; accounts: [ @@ -1469,6 +1530,11 @@ export type DriftVaults = { code: 6021; name: 'OngoingLiquidation'; msg: 'OngoingLiquidation'; + }, + { + code: 6022; + name: 'VaultCantBeDeleted'; + msg: 'VaultCantBeDeleted'; } ]; }; @@ -1555,6 +1621,67 @@ export const IDL: DriftVaults = { }, ], }, + { + name: 'deleteVault', + accounts: [ + { + name: 'vault', + isMut: true, + isSigner: false, + }, + { + name: 'tokenAccount', + isMut: true, + isSigner: false, + }, + { + name: 'driftSpotMarket', + isMut: true, + isSigner: false, + }, + { + name: 'driftSpotMarketMint', + isMut: true, + isSigner: false, + }, + { + name: 'driftUserStats', + isMut: true, + isSigner: false, + }, + { + name: 'driftUser', + isMut: true, + isSigner: false, + }, + { + name: 'driftState', + isMut: true, + isSigner: false, + }, + { + name: 'driftProgram', + isMut: false, + isSigner: false, + }, + { + name: 'manager', + isMut: true, + isSigner: true, + }, + { + name: 'payer', + isMut: true, + isSigner: true, + }, + { + name: 'rent', + isMut: false, + isSigner: false, + }, + ], + args: [], + }, { name: 'updateDelegate', accounts: [ @@ -2945,5 +3072,10 @@ export const IDL: DriftVaults = { name: 'OngoingLiquidation', msg: 'OngoingLiquidation', }, + { + code: 6022, + name: 'VaultCantBeDeleted', + msg: 'VaultCantBeDeleted', + }, ], }; diff --git a/ts/sdk/src/vaultClient.ts b/ts/sdk/src/vaultClient.ts index 998101c6..1701c28f 100644 --- a/ts/sdk/src/vaultClient.ts +++ b/ts/sdk/src/vaultClient.ts @@ -169,6 +169,45 @@ export class VaultClient { .rpc(); } + public async deleteVault( + vault: PublicKey, + ): Promise { + const vaultAccount = await this.program.account.vault.fetch(vault); + + const user = new User({ + driftClient: this.driftClient, + userAccountPublicKey: vaultAccount.user, + }); + await user.subscribe(); + const remainingAccounts = this.driftClient.getRemainingAccounts({ + userAccounts: [user.getUserAccount()], + writableSpotMarketIndexes: [vaultAccount.spotMarketIndex], + }); + + const tokenAccount = getTokenVaultAddressSync( + this.program.programId, + vault + ); + const spotMarket = this.driftClient.getSpotMarketAccount( + vaultAccount.spotMarketIndex + ); + + return await this.program.methods + .deleteVault() + .accounts({ + vault: vault, + tokenAccount, + driftSpotMarket: spotMarket.pubkey, + driftSpotMarketMint: spotMarket.mint, + driftUser: vaultAccount.user, + driftUserStats: vaultAccount.userStats, + driftState: await this.driftClient.getStatePublicKey(), + driftProgram: this.driftClient.program.programId, + manager: this.driftClient.wallet.publicKey, + }) + .remainingAccounts(remainingAccounts) + .rpc(); + } /** * Updates the delegate address for a vault. The delegate address will be allowed to trade * on behalf of the vault.