Skip to content

Commit

Permalink
misc cli updates, fix tests, clippy
Browse files Browse the repository at this point in the history
  • Loading branch information
wphan committed May 7, 2024
1 parent 18872fd commit 7d2b126
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 85 deletions.
9 changes: 2 additions & 7 deletions programs/drift_vaults/src/state/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,12 @@ pub struct VaultDepositorRecord {
pub management_fee_shares: i64,
}

#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, PartialEq, Eq, Default)]
pub enum VaultDepositorAction {
#[default]
Deposit,
WithdrawRequest,
CancelWithdrawRequest,
Withdraw,
FeePayment,
}

impl Default for VaultDepositorAction {
fn default() -> Self {
VaultDepositorAction::Deposit
}
}
1 change: 0 additions & 1 deletion programs/drift_vaults/src/state/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
pub use account_maps::*;
pub use events::*;
pub use traits::*;
pub use vault::*;
pub use vault_depositor::*;
Expand Down
4 changes: 4 additions & 0 deletions tests/driftVaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ describe('driftVaults', () => {
const adminClient = new AdminClient({
connection,
wallet: provider.wallet,
accountSubscription: {
type: 'websocket',
resubTimeoutMs: 30_000,
},
});

const vaultClient = new VaultClient({
Expand Down
3 changes: 3 additions & 0 deletions tests/testHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
QUOTE_PRECISION,
User,
OracleSource,
MarketStatus,
} from '@drift-labs/sdk';

export async function mockOracle(
Expand Down Expand Up @@ -824,6 +825,7 @@ export async function initializeQuoteSpotMarket(
marketIndex,
new BN(10 ** 10).mul(QUOTE_PRECISION)
);
await admin.updateSpotMarketStatus(marketIndex, MarketStatus.ACTIVE);
}

export async function initializeSolSpotMarket(
Expand Down Expand Up @@ -868,5 +870,6 @@ export async function initializeSolSpotMarket(
marketIndex,
new BN(10 ** 10).mul(QUOTE_PRECISION)
);
await admin.updateSpotMarketStatus(marketIndex, MarketStatus.ACTIVE);
return txSig;
}
1 change: 1 addition & 0 deletions ts/sdk/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.env
lib
node_modules
*.txt
42 changes: 25 additions & 17 deletions ts/sdk/cli/commands/applyProfitShare.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ComputeBudgetProgram, PublicKey, Transaction, TransactionInstruction } from "@solana/web3.js";
import { ComputeBudgetProgram, PublicKey, TransactionInstruction, VersionedTransactionResponse } from "@solana/web3.js";
import {
OptionValues,
Command
Expand All @@ -21,11 +21,11 @@ export const applyProfitShare = async (program: Command, cmdOpts: OptionValues)
const {
driftVault,
driftClient,
} = await getCommandContext(program, true);
} = await getCommandContext(program, true, true);

const vault = await driftVault.getVault(vaultAddress);
const vdWithNoWithdrawRequests = await driftVault.getAllVaultDepositorsWithNoWithdrawRequest(vaultAddress);
const vaultEquity = await driftVault.calculateVaultEquity({ vault });
const vaultEquitySpot = await driftVault.calculateVaultEquityInDepositAsset({ vault });

const spotMarket = driftClient.getSpotMarketAccount(vault.spotMarketIndex);
if (!spotMarket) {
Expand All @@ -36,7 +36,7 @@ export const applyProfitShare = async (program: Command, cmdOpts: OptionValues)
const thresholdBN = numberToSafeBN(thresholdNumber, spotMarketPrecision);
let pendingProfitShareToRealize = ZERO;
const vdWithPendingProfitShare = vdWithNoWithdrawRequests.filter((vd: ProgramAccount<VaultDepositor>) => {
const pendingProfitShares = calculateApplyProfitShare(vd.account, vaultEquity, vault);
const pendingProfitShares = calculateApplyProfitShare(vd.account, vaultEquitySpot, vault);
const doRealize = pendingProfitShares.profitShareAmount.gt(thresholdBN);
if (doRealize) {
pendingProfitShareToRealize = pendingProfitShareToRealize.add(pendingProfitShares.profitShareAmount);
Expand All @@ -49,7 +49,7 @@ export const applyProfitShare = async (program: Command, cmdOpts: OptionValues)
console.log(`${vdWithPendingProfitShare.length}/${vdWithNoWithdrawRequests.length} depositors have pending profit shares above threshold ${cmdOpts.threshold} (${thresholdBN.toString()})`);
console.log(`Applying profit share for ${vdWithPendingProfitShare.length} depositors.`);

const chunkSize = 5;
const chunkSize = 1;
const ixChunks: Array<Array<TransactionInstruction>> = [];
for (let i = 0; i < vdWithPendingProfitShare.length; i += chunkSize) {
const chunk = vdWithPendingProfitShare.slice(i, i + chunkSize);
Expand All @@ -65,22 +65,30 @@ export const applyProfitShare = async (program: Command, cmdOpts: OptionValues)
const ixs = ixChunks[i];
console.log(`Sending chunk ${i + 1}/${ixChunks.length}`);
try {
ixs.unshift(ComputeBudgetProgram.setComputeUnitLimit({
units: 1_400_000,
}));
ixs.unshift(ComputeBudgetProgram.setComputeUnitPrice({
microLamports: 100,
microLamports: 10000,
}));
ixs.unshift(ComputeBudgetProgram.setComputeUnitLimit({
units: 170_000,
}));

const tx = new Transaction();
tx.add(...ixs);
const { txSig } = await driftVault.driftClient.sendTransaction(
tx,
[],
driftVault.driftClient.opts
);
const tx = await driftClient.txSender.getVersionedTransaction(ixs, [], undefined, undefined);

let attempt = 0;
let txResp: VersionedTransactionResponse | null = null;
while (txResp === null) {
attempt++;
const { txSig } = await driftClient.txSender.sendVersionedTransaction(
tx,
);
console.log(`[${i}]: https://solscan.io/tx/${txSig} (attempt ${attempt})`);

await new Promise(resolve => setTimeout(resolve, 1000));

txResp = await driftClient.connection.getTransaction(txSig, { commitment: 'confirmed', maxSupportedTransactionVersion: 0 });
}
console.log(txResp);

console.log(`[${i}]: https://solscan.io/tx/${txSig}`);

} catch (e) {
console.error(e);
Expand Down
2 changes: 1 addition & 1 deletion ts/sdk/cli/commands/forceWithdraw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ export const forceWithdraw = async (program: Command, cmdOpts: OptionValues) =>
driftVault
} = await getCommandContext(program, true);

const tx = await driftVault.forceWithdraw(vaultDepositorAddress, false);
const tx = await driftVault.forceWithdraw(vaultDepositorAddress);
console.log(`Forced withdraw from vault: ${tx}`);
};
13 changes: 2 additions & 11 deletions ts/sdk/cli/commands/managerUpdateVault.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ComputeBudgetProgram, PublicKey } from "@solana/web3.js";
import { PublicKey } from "@solana/web3.js";
import {
OptionValues,
Command
Expand Down Expand Up @@ -119,19 +119,10 @@ export const managerUpdateVault = async (program: Command, cmdOpts: OptionValues
permissioned,
};

const preIxs = [
ComputeBudgetProgram.setComputeUnitLimit({
units: 600_000,
}),
ComputeBudgetProgram.setComputeUnitPrice({
microLamports: 100_000,
}),
];

let done = false;
while (!done) {
try {
const tx = await driftVault.managerUpdateVault(vaultAddress, newParams, preIxs, { maxRetries: 0 });
const tx = await driftVault.managerUpdateVault(vaultAddress, newParams);
console.log(`Updated vault params as vault manager: https://solana.fm/tx/${tx}`);
done = true;
break;
Expand Down
142 changes: 124 additions & 18 deletions ts/sdk/cli/commands/vaultInvariantChecks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
Command
} from "commander";
import { getCommandContext } from "../utils";
import { BN, QUOTE_PRECISION, ZERO, convertToNumber } from "@drift-labs/sdk";
import { BN, PERCENTAGE_PRECISION, PRICE_PRECISION, QUOTE_PRECISION, User, ZERO, convertToNumber, decodeName } from "@drift-labs/sdk";
import {
calculateApplyProfitShare,
} from "../../src/math";
Expand All @@ -21,7 +21,8 @@ export const vaultInvariantChecks = async (program: Command, cmdOpts: OptionValu
}

const {
driftVault
driftVault,
driftClient,
} = await getCommandContext(program, true);

/*
Expand All @@ -32,34 +33,58 @@ export const vaultInvariantChecks = async (program: Command, cmdOpts: OptionValu


const vault = await driftVault.getVault(vaultAddress);
const spotMarket = driftVault.driftClient.getSpotMarketAccount(vault.spotMarketIndex);
const spotPrecision = new BN(10).pow(new BN(spotMarket!.decimals));
const spotOracle = driftVault.driftClient.getOracleDataForSpotMarket(vault.spotMarketIndex);
const spotOraclePriceNum = convertToNumber(spotOracle.price, PRICE_PRECISION);
const spotSymbol = decodeName(spotMarket!.name);

const user = new User({
// accountSubscription,
driftClient,
userAccountPublicKey: vault.user,
});
await user.subscribe();

const vaultEquity = await driftVault.calculateVaultEquity({
vault,
});
const spotMarket = driftVault.driftClient.getSpotMarketAccount(vault.spotMarketIndex);
const spotPrecision = new BN(10).pow(new BN(spotMarket!.decimals));
const vaultEquitySpot = vaultEquity.mul(spotPrecision).div(spotOracle.price);

const allVaultDepositors = await driftVault.getAllVaultDepositors(vaultAddress);
const approxSlot = await driftVault.driftClient.connection.getSlot();
const now = Date.now();

let nonZeroDepositors = allVaultDepositors.filter(vd => vd.account.vaultShares.gt(new BN(0)));
nonZeroDepositors = nonZeroDepositors.sort((a, b) => b.account.vaultShares.cmp(a.account.vaultShares));
// let nonZeroDepositors = allVaultDepositors.filter(vd => vd.account.vaultShares.gt(new BN(0)));
// nonZeroDepositors = nonZeroDepositors.sort((a, b) => b.account.vaultShares.cmp(a.account.vaultShares));

let totalUserShares = new BN(0);
let totalUserProfits = 0;
let totalUserCumProfits = 0;
let totalUserProfitSharePaid = new BN(0);
let totalUserProfitShareSharesPaid = new BN(0);
let totalPendingProfitShareAmount = new BN(0);
let totalPendingProfitShareShares = new BN(0);
let totalUserNetDeposits = new BN(0);

console.log(`Vault ${vaultAddress} vd and invariant check at approx slot: ${approxSlot}, date: ${new Date(now).toLocaleString()}`);
console.log(`Depositors with 0 shares: ${allVaultDepositors.length - nonZeroDepositors.length}/${allVaultDepositors.length}`);
for (const vd of nonZeroDepositors) {
let nonZero = 0;
let usersWithoutPendingProfitShare = 0;
let usersWithPendingProfitShare = 0;
const sortedVd = allVaultDepositors.sort((a, b) => b.account.vaultShares.cmp(a.account.vaultShares));
const checkAuths: Array<string> = [];
for (const vd of sortedVd) {
const vdAccount = vd.account as VaultDepositor;

if (vdAccount.vaultShares.gt(new BN(0))) {
nonZero++;
}
totalUserNetDeposits = totalUserNetDeposits.add(vdAccount.netDeposits);

totalUserShares = totalUserShares.add(vdAccount.vaultShares);
const vdAuth = vdAccount.authority.toBase58();
const vdPct = vdAccount.vaultShares.toNumber() / vault.totalShares.toNumber();
console.log(`- ${vdAuth} has ${vdAccount.vaultShares.toNumber()} shares (${(vdPct * 100.0).toFixed(2)}% of vault)`);
console.log(`- ${vdAuth} has ${vdAccount.vaultShares.toNumber()} shares (${(vdPct * 100.0)}% of vault)`);

if (!vdAccount.lastWithdrawRequest.shares.eq(new BN(0))) {
const withdrawRequested = vdAccount.lastWithdrawRequest.ts.toNumber();
Expand All @@ -77,13 +102,47 @@ export const vaultInvariantChecks = async (program: Command, cmdOpts: OptionValu
totalUserProfitSharePaid = totalUserProfitSharePaid.add(vdAccount.profitShareFeePaid);
totalUserProfitShareSharesPaid = totalUserProfitShareSharesPaid.add(vdAccount.cumulativeProfitShareAmount);

const pendingProfitShares = calculateApplyProfitShare(vdAccount, vaultEquity, vault);
const pendingProfitShares = calculateApplyProfitShare(vdAccount, vaultEquitySpot, vault);
const pendingProfitShareAmtNum = convertToNumber(pendingProfitShares.profitShareAmount, spotPrecision);
if (pendingProfitShares.profitShareAmount.gt(ZERO)) {
totalPendingProfitShareAmount = totalPendingProfitShareAmount.add(pendingProfitShares.profitShareAmount);
totalPendingProfitShareShares = totalPendingProfitShareShares.add(pendingProfitShares.profitShareShares);
console.log(` - pending profit share amount: $${convertToNumber(pendingProfitShares.profitShareAmount, spotPrecision)}`);
console.log(` - pending profit share amount: ${convertToNumber(pendingProfitShares.profitShareAmount, spotPrecision)}`);
usersWithPendingProfitShare++;
} else {
console.log(` - no pending profit share`);
usersWithoutPendingProfitShare++;
}

const userShareValue = vaultEquitySpot.mul(vdAccount.vaultShares).div(vault.totalShares);
const userShareValueNum = convertToNumber(userShareValue, spotPrecision);
const netDepositsNum = convertToNumber(vdAccount.netDeposits, spotPrecision);
const vdProfits = userShareValueNum - netDepositsNum;
const profitSharePaid = convertToNumber(vdAccount.profitShareFeePaid, spotPrecision);
const cumProfitShareNum = convertToNumber(vdAccount.cumulativeProfitShareAmount, spotPrecision);
totalUserProfits += vdProfits;
totalUserCumProfits += cumProfitShareNum;
console.log(` - net deposits: ${netDepositsNum}`);
console.log(` - vd profit: ${vdProfits}`);
console.log(` - cumProfitshareAmt: ${cumProfitShareNum}`);
console.log(` - profitShareFeePaid: ${convertToNumber(vdAccount.profitShareFeePaid, spotPrecision)}`);
const inclProfitShare = (profitSharePaid + pendingProfitShareAmtNum) / (cumProfitShareNum + pendingProfitShareAmtNum) * 100.0;
console.log(` - pftSharePaidPct (excl pend): ${(profitSharePaid / cumProfitShareNum * 100.0).toFixed(2)}%`);
console.log(` - pftSharePaidPct (incl pend): ${(inclProfitShare).toFixed(2)}% `);
if (inclProfitShare < 29.9 && inclProfitShare > 0) {
console.log(` ^^^ weird: ${inclProfitShare}`);
checkAuths.push(vdAuth);
}

if (vdAccount.vaultSharesBase !== 0) {
console.log(` - Nonzero vault shares base: ${vdAccount.vaultSharesBase} `);
}
}
console.log(`Check these auths:\n${checkAuths.join("\n")}`);
console.log(`Depositors with 0 shares: ${allVaultDepositors.length - nonZero} /${allVaultDepositors.length}`);
console.log(`Depositors with pending profit share: ${usersWithPendingProfitShare}`);
console.log(`Depositors without pending profit share: ${usersWithoutPendingProfitShare}`);

console.log(`==== Vault Depositor Shares == vault.user_shares ====`);
console.log(`total vd shares: ${totalUserShares.toString()}`);
console.log(`total vault usershares: ${vault.userShares.toString()}`);
Expand All @@ -97,8 +156,27 @@ export const vaultInvariantChecks = async (program: Command, cmdOpts: OptionValu

console.log(``);
console.log(`==== Pending profit shares to realize ====`);
console.log(`${convertToNumber(totalPendingProfitShareAmount, spotPrecision)}`);
console.log(`csv: ${cmdOpts.csv}`);
const totalPendingProfitShareAmountNum = convertToNumber(totalPendingProfitShareAmount, spotPrecision);
const totalUserProfitSharePaidNum = convertToNumber(totalUserProfitSharePaid, spotPrecision);
console.log(`amount: ${totalPendingProfitShareAmountNum}`);
console.log(`shares: ${totalPendingProfitShareShares.toNumber()}`);
// console.log(`csv: ${cmdOpts.csv}`);

console.log(``);
console.log(`==== Agg user profit share paid ====`);
console.log(`vd total profit (incl unrealized profit share): ${totalUserProfits}`);
console.log(`vd total cum profits (before pending profit share): ${totalUserCumProfits}`);
console.log(`vd total profit share paid): ${totalUserProfitSharePaidNum}`);
console.log(`vd total pending profit share: ${totalPendingProfitShareAmountNum}`);
console.log(`vd total net deposits: ${convertToNumber(totalUserNetDeposits, spotPrecision)}`);
const driftUserDeposits = user.getUserAccount().totalDeposits;
const driftUserWithdraws = user.getUserAccount().totalWithdraws;
const driftUserSocialLoss = user.getUserAccount().totalSocialLoss;
console.log(`vd drift user net deposits: ${convertToNumber(driftUserDeposits.sub(driftUserWithdraws).sub(driftUserSocialLoss), spotPrecision)}`);
console.log(` vd drift user deps: ${convertToNumber(driftUserDeposits, spotPrecision)}`);
console.log(` vd drift user with: ${convertToNumber(driftUserWithdraws, spotPrecision)}`);
console.log(` vd drift user scls: ${convertToNumber(driftUserSocialLoss, spotPrecision)}`);


console.log(``);
console.log(`==== Manager share ====`);
Expand All @@ -107,8 +185,36 @@ export const vaultInvariantChecks = async (program: Command, cmdOpts: OptionValu
const managerSharePct = managerShares.toNumber() / vault.totalShares.toNumber();
const managerShareWithPendingPct = managerShares.add(totalPendingProfitShareShares).toNumber() / vault.totalShares.toNumber();
console.log(` Manager shares: ${managerShares.toString()} (${(managerSharePct * 100.0).toFixed(4)}%)`);
const vaultEquityNum = convertToNumber(vaultEquity, QUOTE_PRECISION);
console.log(`vaultEquity (USDC): $${vaultEquityNum}`);
console.log(`manager share (w/o pending) (USDC): $${managerSharePct * vaultEquityNum}`);
console.log(`manager share (with pending) (USDC): $${managerShareWithPendingPct * vaultEquityNum}`);
};
const vaultEquityNum = convertToNumber(vaultEquity, spotPrecision);
const vaultEquitySpotNum = convertToNumber(vaultEquitySpot, spotPrecision);
const vaultPnlNum = convertToNumber(user.getTotalAllTimePnl(), QUOTE_PRECISION);
console.log(`vaultEquity (USDC): $${vaultEquityNum}`);
console.log(`vaultEquity (deposit asset): ${vaultEquitySpotNum}`);
const managerValueWoPending = managerSharePct * vaultEquitySpotNum;
const managerValueWithPending = managerShareWithPendingPct * vaultEquitySpotNum;
console.log(`manager share (w/o pending) (deposit asset): ${managerValueWoPending} (share: ${managerValueWoPending / vaultPnlNum * 100.0}%)`);
console.log(`manager share (with pending) (deposit asset): ${managerValueWithPending} (share: ${managerValueWithPending / vaultPnlNum * 100.0}%)`);

console.log(``);
const profitSharePct = vault.profitShare / PERCENTAGE_PRECISION.toNumber();
const vdPnlBeforeProfitShare = convertToNumber(totalUserProfitSharePaid, spotPrecision) / profitSharePct;
console.log(`back out vault pnl: (userPnl + managerShareValue): ${totalUserProfits} + ${managerSharePct * vaultEquitySpotNum} = ${totalUserProfits + managerSharePct * vaultEquitySpotNum}`);
console.log(`vaultDepositors pnl (before profit share): ${vdPnlBeforeProfitShare}`);

console.log(`vault PnL (spot): ${vaultEquitySpotNum - convertToNumber(vault.netDeposits, spotPrecision)}`);
console.log(`vault PnL (USD) ${vaultPnlNum}`);
console.log(`vault PnL (spot) ${vaultPnlNum / spotOraclePriceNum}`);

console.log(``);
console.log(`==== ${decodeName(vault.name)} Profit Summary ====`);
console.log(`Depositors' total PnL: ${totalUserProfits} ${spotSymbol}`);
console.log(`Depositors' profit share paid to date: ${totalUserProfitSharePaidNum} ${spotSymbol}`);
console.log(`Unrealized profit share: ${totalPendingProfitShareAmountNum} ${spotSymbol}`);
console.log(`Vault manager net deposits: ${convertToNumber(vault.managerNetDeposits, spotPrecision)} ${spotSymbol}`);
console.log(`Vault manager profit share received: ${convertToNumber(vault.managerTotalProfitShare, spotPrecision)} ${spotSymbol}`);
console.log(`Vault manager share value: ${managerValueWithPending} ${spotSymbol} (share of vault: ${managerValueWithPending / vaultPnlNum * 100.0}%)`);
if (spotSymbol !== 'USDC') {
console.log(`Vault manager share value: ${managerValueWithPending * spotOraclePriceNum} USDC`);
}
};

Loading

0 comments on commit 7d2b126

Please sign in to comment.