diff --git a/programs/drift/src/instructions/keeper.rs b/programs/drift/src/instructions/keeper.rs index d6a28c6f17..67175094cc 100644 --- a/programs/drift/src/instructions/keeper.rs +++ b/programs/drift/src/instructions/keeper.rs @@ -3114,10 +3114,11 @@ pub fn handle_settle_perp_to_lp_pool<'c: 'info, 'info>( )?; // Send all revenues to the perp market fee pool + let precision_increase = SPOT_BALANCE_PRECISION.safe_div(QUOTE_PRECISION)?; perp_market .amm .fee_pool - .increase_balance(amount_to_send as u128)?; + .increase_balance((amount_to_send as u128).safe_mul(precision_increase)?)?; lp_pool.cumulative_usdc_sent_to_perp_markets = lp_pool .cumulative_usdc_sent_to_perp_markets @@ -3131,10 +3132,14 @@ pub fn handle_settle_perp_to_lp_pool<'c: 'info, 'info>( } else { let amount_to_send = if cached_info.quote_owed_from_lp > 0 { if amount_to_send > cached_info.quote_owed_from_lp as u64 { + let new_amount_to_send = + amount_to_send.safe_sub(cached_info.quote_owed_from_lp as u64)?; cached_info.quote_owed_from_lp = 0; - amount_to_send - cached_info.quote_owed_from_lp as u64 + new_amount_to_send } else { - cached_info.quote_owed_from_lp -= amount_to_send as i64; + cached_info.quote_owed_from_lp = cached_info + .quote_owed_from_lp + .safe_sub(amount_to_send as i64)?; 0 } } else { @@ -3196,6 +3201,9 @@ pub fn handle_settle_perp_to_lp_pool<'c: 'info, 'info>( lp_pool.last_aum = lp_pool .last_aum .saturating_add(amount_to_send.cast::()?); + } else { + cached_info.last_fee_pool_token_amount = fee_pool_token_amount; + cached_info.last_net_pnl_pool_token_amount = net_pnl_pool_token_amount; } } diff --git a/sdk/src/adminClient.ts b/sdk/src/adminClient.ts index 382ad39a67..d4c6dd433d 100644 --- a/sdk/src/adminClient.ts +++ b/sdk/src/adminClient.ts @@ -1115,6 +1115,13 @@ export class AdminClient extends DriftClient { sourceVault: PublicKey ): Promise { const spotMarket = this.getQuoteSpotMarketAccount(); + const remainingAccounts = [ + { + pubkey: spotMarket.mint, + isWritable: false, + isSigner: false, + }, + ]; return await this.program.instruction.depositIntoPerpMarketFeePool(amount, { accounts: { @@ -1132,6 +1139,7 @@ export class AdminClient extends DriftClient { spotMarketVault: spotMarket.vault, tokenProgram: TOKEN_PROGRAM_ID, }, + remainingAccounts, }); } diff --git a/tests/lpPool.ts b/tests/lpPool.ts index 34c00c8f77..adf7c37058 100644 --- a/tests/lpPool.ts +++ b/tests/lpPool.ts @@ -43,6 +43,7 @@ import { SPOT_MARKET_BALANCE_PRECISION, SpotBalanceType, getTokenAmount, + TWO, } from '../sdk/src'; import { @@ -780,7 +781,7 @@ describe('LP Pool', () => { ); }); - it('will fail gracefully when trying to settle pnl from constituents to perp markets if not enough usdc in the constituent vault', async () => { + it('will settle gracefully when trying to settle pnl from constituents to perp markets if not enough usdc in the constituent vault', async () => { let lpPool = (await adminClient.program.account.lpPool.fetch( lpPoolKey )) as LPPoolAccount; @@ -876,8 +877,85 @@ describe('LP Pool', () => { ) ) ); + assert( + adminClient + .getPerpMarketAccount(0) + .amm.feePool.scaledBalance.eq( + new BN(constituentUSDCBalanceBefore.toString()).mul( + SPOT_MARKET_BALANCE_PRECISION.div(QUOTE_PRECISION) + ) + ) + ); // NAV should have gone down the max that is has assert(lpPool.lastAum.eq(ZERO)); }); + + it('perp market will not transfer with the constituent vault if it is owed from dlp', async () => { + let ammCache = (await adminClient.program.account.ammCache.fetch( + getAmmCachePublicKey(program.programId) + )) as AmmCache; + const owedAmount = ammCache.cache[0].quoteOwedFromLp; + + // Give the perp market half of its owed amount + const perpMarket = adminClient.getPerpMarketAccount(0); + perpMarket.amm.feePool.scaledBalance = + perpMarket.amm.feePool.scaledBalance.add( + owedAmount + .div(TWO) + .mul(SPOT_MARKET_BALANCE_PRECISION.div(QUOTE_PRECISION)) + ); + await overWritePerpMarket( + adminClient, + bankrunContextWrapper, + perpMarket.pubkey, + perpMarket + ); + + await adminClient.settlePerpToLpPool(encodeName(lpPoolName), [0, 1, 2]); + + ammCache = (await adminClient.program.account.ammCache.fetch( + getAmmCachePublicKey(program.programId) + )) as AmmCache; + const constituent = (await adminClient.program.account.constituent.fetch( + getConstituentPublicKey(program.programId, lpPoolKey, 0) + )) as ConstituentAccount; + + assert(ammCache.cache[0].quoteOwedFromLp.eq(owedAmount.divn(2))); + assert(constituent.tokenBalance.eq(ZERO)); + }); + + it('perp market will transfer with the constituent vault if it should send more than its owed', async () => { + let ammCache = (await adminClient.program.account.ammCache.fetch( + getAmmCachePublicKey(program.programId) + )) as AmmCache; + const owedAmount = ammCache.cache[0].quoteOwedFromLp; + + // Give the perp market half of its owed amount + const perpMarket = adminClient.getPerpMarketAccount(0); + perpMarket.amm.feePool.scaledBalance = + perpMarket.amm.feePool.scaledBalance.add( + owedAmount + .mul(TWO) + .mul(SPOT_MARKET_BALANCE_PRECISION.div(QUOTE_PRECISION)) + ); + await overWritePerpMarket( + adminClient, + bankrunContextWrapper, + perpMarket.pubkey, + perpMarket + ); + + await adminClient.settlePerpToLpPool(encodeName(lpPoolName), [0, 1, 2]); + + ammCache = (await adminClient.program.account.ammCache.fetch( + getAmmCachePublicKey(program.programId) + )) as AmmCache; + const constituent = (await adminClient.program.account.constituent.fetch( + getConstituentPublicKey(program.programId, lpPoolKey, 0) + )) as ConstituentAccount; + + assert(ammCache.cache[0].quoteOwedFromLp.eq(ZERO)); + assert(constituent.tokenBalance.eq(owedAmount)); + }); });