Skip to content

program: lp pool corner case tests #1711

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: bigz/init-lp-pool
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion programs/drift/src/state/lp_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,12 +311,16 @@ impl LPPool {
dlp_total_supply: u64,
) -> DriftResult<(u64, u128, i64, i128)> {
let lp_fee_to_charge_pct = self.get_mint_redeem_fee(now, false)?;
let lp_fee_to_charge = lp_burn_amount
let mut lp_fee_to_charge = lp_burn_amount
.cast::<i128>()?
.safe_mul(lp_fee_to_charge_pct.cast::<i128>()?)?
.safe_div(PERCENTAGE_PRECISION_I128)?
.cast::<i64>()?;

if dlp_total_supply.saturating_sub(lp_burn_amount) <= QUOTE_PRECISION_U64 && lp_fee_to_charge <= (QUOTE_PRECISION_U64 as i64) {
lp_fee_to_charge = (lp_burn_amount.min(QUOTE_PRECISION_U64) as i64);
}

let lp_amount_less_fees = (lp_burn_amount as i128).safe_sub(lp_fee_to_charge as i128)?;
msg!("lp_amount_less_fees: {}", lp_amount_less_fees);

Expand Down
7 changes: 5 additions & 2 deletions programs/drift/src/state/lp_pool/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,8 +371,9 @@ mod tests {
}
};

// tuple is (market_index, base, price)
let amm_inventory_and_prices: Vec<(u16, i64, i64)> = vec![(0, 1_000_000, 142_000_000)];
let constituents_indexes_and_decimals_and_prices = vec![(1, 6, 142_000_000)];
let constituents_indexes_and_decimals_and_prices = vec![(1, 9, 142_000_000)];

let prices = vec![142_000_000];
let aum = 0;
Expand All @@ -399,8 +400,10 @@ mod tests {
.unwrap();

assert_eq!(target_zc_mut.len(), 1);
assert_eq!(target_zc_mut.get(0).target_base, -1_000); // despite no aum, desire to reach target
assert_eq!(target_zc_mut.get(0).target_base, -1_000_000); // despite no aum, desire to reach target
assert_eq!(target_zc_mut.get(0).last_slot, now_ts);


}

#[test]
Expand Down
65 changes: 65 additions & 0 deletions sdk/src/math/lpPool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { calculateNetUserPnl } from './market';

import {
PerpMarketAccount,
SpotMarketAccount,
SpotBalanceType,
ConstituentAccount,
CacheInfo,
getSignedTokenAmount,
PRICE_PRECISION,
} from '..';
import { OraclePriceData } from '.././oracles/types';
import { BN } from '@coral-xyz/anchor';
import { getTokenAmount } from './spotBalance';

export function getLpPoolNAVSpotComponent(
constituent: ConstituentAccount,
spotMarket: SpotMarketAccount,
oraclePriceData: OraclePriceData
): BN {
const tokenPrecision = new BN(Math.pow(10, spotMarket.decimals));

return constituent.tokenBalance
.add(
getSignedTokenAmount(
getTokenAmount(
constituent.spotBalance.scaledBalance,
spotMarket,
constituent.spotBalance.balanceType
),
constituent.spotBalance.balanceType
)
)
.mul(oraclePriceData.price)
.div(PRICE_PRECISION)
.div(tokenPrecision);
}

export function getLpPoolNAVPerpComponent(
ammCacheInfo: CacheInfo,
perpMarket: PerpMarketAccount,
spotMarket: SpotMarketAccount,
oraclePriceData: OraclePriceData
): BN {
const netUserPnl = calculateNetUserPnl(perpMarket, oraclePriceData);

const pnlPool = getTokenAmount(
perpMarket.pnlPool.scaledBalance,
spotMarket,
SpotBalanceType.DEPOSIT
);
const feePool = getTokenAmount(
perpMarket.amm.feePool.scaledBalance,
spotMarket,
SpotBalanceType.DEPOSIT
);

const currentNetPnlPoolTokenAmount = pnlPool.add(feePool).sub(netUserPnl);

const perpPerformanceDelta = currentNetPnlPoolTokenAmount.sub(
ammCacheInfo.lastNetPnlPoolTokenAmount
);

return perpPerformanceDelta;
}
104 changes: 102 additions & 2 deletions tests/lpPool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,12 @@ describe('LP Pool', () => {

await adminClient.sendTransaction(new Transaction().add(createAtaIx), []);

await adminClient.updateLpPoolAum(lpPool, [0, 1]);
lpPool = (await adminClient.program.account.lpPool.fetch(
lpPoolKey
)) as LPPoolAccount;
assert(lpPool.lastAum.eq(ZERO));

const tx = new Transaction();
tx.add(await adminClient.getUpdateLpPoolAumIxs(lpPool, [0, 1]));
tx.add(
Expand Down Expand Up @@ -1098,10 +1104,12 @@ describe('LP Pool', () => {
lpPoolKey
)) as LPPoolAccount;

assert(ammCache.cache[0].quoteOwedFromLp.eq(owedAmount.divn(2)));
assert(constituent.tokenBalance.eq(ZERO));
assert(lpPool.lastAum.eq(ZERO));

// assert(ammCache.cache[0].quoteOwedFromLp.eq(owedAmount.divn(2)));
expect(ammCache.cache[0].quoteOwedFromLp.toNumber()).to.eq(
owedAmount.divn(2).toNumber()
);
// Deposit here to DLP to make sure aum calc work with perp market debt
await overWriteMintAccount(
bankrunContextWrapper,
Expand Down Expand Up @@ -1335,4 +1343,96 @@ describe('LP Pool', () => {
10
);
});

it('remove aum then add back', async () => {
const lpPool = (await adminClient.program.account.lpPool.fetch(
lpPoolKey
)) as LPPoolAccount;
const tx = new Transaction();
expect(lpPool.lastAum.toNumber()).to.eq(1049220180);

const lpTokenBalanceBefore =
await bankrunContextWrapper.connection.getTokenAccount(
userLpTokenAccount
);
expect(Number(lpTokenBalanceBefore.amount.toString())).to.equal(1000000000);

const mintInfo = await getMint(
bankrunContextWrapper.connection.toConnection(),
lpPool.mint as PublicKey
);
expect(mintInfo.decimals).to.equal(tokenDecimals);
expect(Number(mintInfo.supply)).to.equal(1000000000);
expect(mintInfo.mintAuthority?.toBase58()).to.equal(
adminClient.getSignerPublicKey().toBase58()
);

// console.log(lpPool);
tx.add(await adminClient.getUpdateLpPoolAumIxs(lpPool, [0, 1, 2, 3]));
tx.add(
await adminClient.getLpPoolRemoveLiquidityIx({
lpPool,
minAmountOut: new BN(1000).mul(QUOTE_PRECISION),
lpToBurn: new BN(1000).mul(QUOTE_PRECISION),
outMarketIndex: 0,
})
);
await adminClient.sendTransaction(tx);

const lpPoolAfter = (await adminClient.program.account.lpPool.fetch(
lpPoolKey
)) as LPPoolAccount;

const deltaAum = lpPool.lastAum.sub(lpPoolAfter.lastAum);

expect(lpPoolAfter.lastAum.toNumber()).to.eq(1363672); // residual fee
expect(deltaAum.toNumber()).to.eq(
1049820000 - 1363672 - 1400000 // price of 1 dlp
);

const mintInfoAfter = await getMint(
bankrunContextWrapper.connection.toConnection(),
lpPool.mint as PublicKey
);
expect(Number(mintInfoAfter.supply)).to.equal(1000000);

const lpTokenBalanceAfter =
await bankrunContextWrapper.connection.getTokenAccount(
userLpTokenAccount
);
expect(Number(lpTokenBalanceAfter.amount)).to.equal(0);

// TODO: below shoudn't fail (Slippage outside limit: lp_mint_amount_net_fees(0) < min_mint_amount(10))
const txNext = new Transaction();
txNext.add(await adminClient.getUpdateLpPoolAumIxs(lpPool, [0, 1, 2, 3]));
txNext.add(
await adminClient.getLpPoolAddLiquidityIx({
lpPool,
inAmount: new BN(1000).mul(QUOTE_PRECISION),
minMintAmount: new BN(10),
inMarketIndex: 0,
})
);
await adminClient.sendTransaction(txNext);

const lpPoolAfter2 = (await adminClient.program.account.lpPool.fetch(
lpPoolKey
)) as LPPoolAccount;

// expect(lpPoolAfter2.lastAum).to.equal(1000000000);
expect(Number(lpPoolAfter2.lastAum.toNumber())).to.equal(1000314947);

const mintInfoAfter2 = await getMint(
bankrunContextWrapper.connection.toConnection(),
lpPool.mint as PublicKey
);
expect(Number(mintInfoAfter2.supply)).to.equal(1000000);

const lpTokenBalanceAfter2 =
await bankrunContextWrapper.connection.getTokenAccount(
userLpTokenAccount
);
// expect(Number(lpTokenBalanceAfter2.amount)).to.equal(1000000000);
expect(Number(lpTokenBalanceAfter2.amount)).to.equal(3174);
});
});