From c717124e28adb9e1f0ea3e4894b6bca35100218a Mon Sep 17 00:00:00 2001 From: wphan Date: Mon, 14 Aug 2023 14:30:32 -0700 Subject: [PATCH] sdk: add liquidate --- .../drift_vaults/src/state/vault_depositor.rs | 58 ++++++++++++++++++ ts/sdk/src/vaultClient.ts | 61 +++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/programs/drift_vaults/src/state/vault_depositor.rs b/programs/drift_vaults/src/state/vault_depositor.rs index 4f3a09ed..0ea27d35 100644 --- a/programs/drift_vaults/src/state/vault_depositor.rs +++ b/programs/drift_vaults/src/state/vault_depositor.rs @@ -986,4 +986,62 @@ mod tests { assert_eq!(withdraw_amount, 50000000); assert_eq!(vd.cumulative_profit_share_amount, 0); // $0 } + + #[test] + fn test_vault_depositor_request_in_profit_withdraw_in_loss() { + // test for vault depositor who requests withdraw when in pofit + // then waits redeem period for withdraw + // upon withdraw, vault depositor is in loss even though they withdrew in profit + // should get withdraw vaulation and not break invariants + + let now = 1000; + let vault = &mut Vault::default(); + + let vd = + &mut VaultDepositor::new(Pubkey::default(), Pubkey::default(), Pubkey::default(), now); + + let mut vault_equity: u64 = 100 * QUOTE_PRECISION_U64; + let amount: u64 = 100 * QUOTE_PRECISION_U64; + vd.deposit(amount, vault_equity, vault, now).unwrap(); + assert_eq!(vd.vault_shares_base, 0); + assert_eq!(vd.checked_vault_shares(vault).unwrap(), 100000000); + assert_eq!(vault.user_shares, 100000000); + assert_eq!(vault.total_shares, 200000000); + + vault.profit_share = 100000; // 10% profit share + vault.redeem_period = 3600; // 1 hour + vault_equity = 200 * QUOTE_PRECISION_U64; + + assert_eq!(vd.checked_vault_shares(vault).unwrap(), 100000000); + assert_eq!(vd.cumulative_profit_share_amount, 0); // $0 + assert_eq!(vault.user_shares, 100000000); + assert_eq!(vault.total_shares, 200000000); + + // let vault_before = vault; + vd.realize_profits(vault_equity, vault, now).unwrap(); // should be noop + + // request withdraw all + vd.request_withdraw( + PERCENTAGE_PRECISION, + WithdrawUnit::SharesPercent, + vault_equity, + vault, + now + 20, + ) + .unwrap(); + assert_eq!(vd.checked_vault_shares(vault).unwrap(), 100000000); + + assert_eq!(vd.last_withdraw_request_value, 100000000); + assert_eq!(vd.last_withdraw_request_ts, now + 20); + + vault_equity /= 5; // down 80% + + let (withdraw_amount, _ll) = vd.withdraw(vault_equity, vault, now + 20 + 3600).unwrap(); + assert_eq!(vd.checked_vault_shares(vault).unwrap(), 0); + assert_eq!(vd.vault_shares_base, 0); + assert_eq!(vault.user_shares, 0); + assert_eq!(vault.total_shares, 100000000); + assert_eq!(withdraw_amount, 20000000); // getting back 20% of deposit + assert_eq!(vd.cumulative_profit_share_amount, 0); // $0 + } } diff --git a/ts/sdk/src/vaultClient.ts b/ts/sdk/src/vaultClient.ts index a740e7d2..13024e1e 100644 --- a/ts/sdk/src/vaultClient.ts +++ b/ts/sdk/src/vaultClient.ts @@ -562,6 +562,67 @@ export class VaultClient { } } + /** + * Liquidates (become delegate for) a vault. + * @param + * @param + * @returns + */ + public async liquidate( + vaultDepositor: PublicKey + ): Promise { + const vaultDepositorAccount = + await this.program.account.vaultDepositor.fetch(vaultDepositor); + const vaultPubKey = vaultDepositorAccount.vault; + + const vaultAccount = await this.program.account.vault.fetch(vaultPubKey); + + 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 userStatsKey = getUserStatsAccountPublicKey( + this.driftClient.program.programId, + vaultPubKey + ); + + const driftStateKey = await this.driftClient.getStatePublicKey(); + + const accounts = { + vault: vaultPubKey, + vaultDepositor, + vaultTokenAccount: vaultAccount.tokenAccount, + driftUserStats: userStatsKey, + driftUser: vaultAccount.user, + driftState: driftStateKey, + driftProgram: this.driftClient.program.programId, + }; + + if (this.cliMode) { + return await this.program.methods + .liquidate() + .accounts(accounts) + .remainingAccounts(remainingAccounts) + .rpc(); + } else { + const liquidateIx = this.program.instruction.liquidate({ + accounts: { + authority: this.driftClient.wallet.publicKey, + ...accounts, + }, + remainingAccounts, + }); + + return await this.createAndSendTxn(liquidateIx); + } + } + /** * Used for UI wallet adapters compatibility */