Skip to content

Commit 0e9a454

Browse files
Nour/settle testing (#1725)
* refactor settle pnl to modularize and add tests * more cargo tests
1 parent bac790e commit 0e9a454

File tree

9 files changed

+954
-207
lines changed

9 files changed

+954
-207
lines changed

programs/drift/src/instructions/keeper.rs

Lines changed: 90 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use crate::math::casting::Cast;
3030
use crate::math::constants::QUOTE_PRECISION;
3131
use crate::math::constants::QUOTE_SPOT_MARKET_INDEX;
3232
use crate::math::constants::SPOT_BALANCE_PRECISION;
33+
use crate::math::lp_pool::perp_lp_pool_settlement;
3334
use crate::math::margin::{calculate_user_equity, meets_settle_pnl_maintenance_margin_requirement};
3435
use crate::math::orders::{estimate_price_from_side, find_bids_and_asks_from_users};
3536
use crate::math::position::calculate_base_asset_value_and_pnl_with_oracle_price;
@@ -2944,12 +2945,17 @@ pub fn handle_pause_spot_market_deposit_withdraw(
29442945
Ok(())
29452946
}
29462947

2948+
// Refactored main function
29472949
pub fn handle_settle_perp_to_lp_pool<'c: 'info, 'info>(
29482950
ctx: Context<'_, '_, 'c, 'info, SettleAmmPnlToLp<'info>>,
29492951
) -> Result<()> {
2950-
let slot = Clock::get()?.slot;
2952+
use perp_lp_pool_settlement::*;
29512953

2954+
let slot = Clock::get()?.slot;
2955+
let timestamp = Clock::get()?.unix_timestamp;
29522956
let state = &ctx.accounts.state;
2957+
2958+
// Validation and setup code (unchanged)
29532959
let amm_cache_key = &ctx.accounts.amm_cache.key();
29542960
let mut amm_cache: AccountZeroCopyMut<'_, CacheInfo, _> =
29552961
ctx.accounts.amm_cache.load_zc_mut()?;
@@ -2958,8 +2964,7 @@ pub fn handle_settle_perp_to_lp_pool<'c: 'info, 'info>(
29582964
let constituent_token_account = &mut ctx.accounts.constituent_quote_token_account;
29592965
let mut lp_pool = ctx.accounts.lp_pool.load_mut()?;
29602966

2961-
let clock = Clock::get()?;
2962-
2967+
// PDA validation (unchanged)
29632968
let expected_pda = &Pubkey::create_program_address(
29642969
&[
29652970
AMM_POSITIONS_CACHE.as_ref(),
@@ -2987,12 +2992,19 @@ pub fn handle_settle_perp_to_lp_pool<'c: 'info, 'info>(
29872992
None,
29882993
)?;
29892994

2995+
let precision_increase = SPOT_BALANCE_PRECISION.safe_div(QUOTE_PRECISION)?;
2996+
let mint = Some(*ctx.accounts.mint.clone());
2997+
29902998
for (_, perp_market_loader) in perp_market_map.0.iter() {
29912999
let mut perp_market = perp_market_loader.load_mut()?;
3000+
if perp_market.lp_status == 0 {
3001+
continue;
3002+
}
3003+
29923004
let cached_info = amm_cache.get_mut(perp_market.market_index as u32);
29933005

3006+
// Early validation checks (unchanged)
29943007
if slot.saturating_sub(cached_info.oracle_slot) > SETTLE_AMM_ORACLE_MAX_DELAY {
2995-
// If the oracle slot is not up to date, skip this market
29963008
msg!(
29973009
"Skipping settling perp market {} to dlp because oracle slot is not up to date",
29983010
perp_market.market_index
@@ -3001,6 +3013,7 @@ pub fn handle_settle_perp_to_lp_pool<'c: 'info, 'info>(
30013013
}
30023014

30033015
validate_market_within_price_band(&perp_market, state, cached_info.oracle_price)?;
3016+
30043017
if perp_market.is_operation_paused(PerpOperation::SettlePnl) {
30053018
msg!(
30063019
"Cannot settle pnl under current market = {} status",
@@ -3011,207 +3024,99 @@ pub fn handle_settle_perp_to_lp_pool<'c: 'info, 'info>(
30113024

30123025
if cached_info.slot != slot {
30133026
msg!("Skipping settling perp market {} to lp pool because amm cache was not updated in the same slot",
3014-
perp_market.market_index
3015-
);
3027+
perp_market.market_index);
30163028
return Err(ErrorCode::AMMCacheStale.into());
30173029
}
30183030

3019-
let mint = *ctx.accounts.mint.clone();
3020-
// Transfer balance if it's available
3021-
if cached_info.quote_owed_from_lp > 0 {
3022-
if quote_constituent.token_balance == 0 {
3023-
msg!("LP Pool has no usdc to settle",);
3024-
continue;
3025-
}
3026-
let amount_to_send =
3027-
if cached_info.quote_owed_from_lp > quote_constituent.token_balance as i64 {
3028-
quote_constituent.token_balance
3029-
} else {
3030-
cached_info.quote_owed_from_lp as u64
3031-
};
3032-
3033-
cached_info.quote_owed_from_lp = cached_info
3034-
.quote_owed_from_lp
3035-
.safe_sub(amount_to_send as i64)?;
3036-
3037-
controller::token::send_from_program_vault(
3038-
&ctx.accounts.token_program,
3039-
constituent_token_account,
3040-
&ctx.accounts.quote_token_vault,
3041-
&ctx.accounts.drift_signer,
3042-
state.signer_nonce,
3043-
amount_to_send,
3044-
&Some(mint),
3045-
)?;
3046-
3047-
// Send all revenues to the perp market fee pool
3048-
let precision_increase = SPOT_BALANCE_PRECISION.safe_div(QUOTE_PRECISION)?;
3049-
perp_market
3050-
.amm
3051-
.fee_pool
3052-
.increase_balance((amount_to_send as u128).safe_mul(precision_increase)?)?;
3053-
3054-
// Update LP Pool Stats
3055-
lp_pool.cumulative_usdc_sent_to_perp_markets = lp_pool
3056-
.cumulative_usdc_sent_to_perp_markets
3057-
.saturating_add(amount_to_send.cast::<u128>()?);
3058-
3059-
// Decrement cached fee pool token amount
3060-
cached_info.last_fee_pool_token_amount = cached_info
3061-
.last_fee_pool_token_amount
3062-
.safe_add(amount_to_send as u128)?;
3063-
3064-
// Sync the constituent token account balance
3065-
constituent_token_account.reload()?;
3066-
quote_constituent.sync_token_balance(constituent_token_account.amount);
3067-
3068-
// Update the last settle info
3069-
cached_info.last_settle_amount = amount_to_send.cast::<u64>()?;
3070-
cached_info.last_settle_ts = Clock::get()?.unix_timestamp;
3071-
3072-
// Update LP Pool Stats
3073-
lp_pool.cumulative_usdc_sent_to_perp_markets = lp_pool
3074-
.cumulative_usdc_sent_to_perp_markets
3075-
.saturating_add(amount_to_send.cast::<u128>()?);
3076-
} else if cached_info.quote_owed_from_lp < 0 {
3077-
// We now send from the perp market to dlp and wipe out the amount owed from the perp market
3078-
let amount_to_send = cached_info.quote_owed_from_lp.abs() as u64;
3079-
3080-
// Take from the fee pool if it can cover the whole balance, otherwise also take from pnl pool
3081-
let precision_increase = SPOT_BALANCE_PRECISION.safe_div(QUOTE_PRECISION)?;
3082-
let fee_pool_token_amount = get_token_amount(
3031+
// Create settlement context
3032+
let settlement_ctx = SettlementContext {
3033+
quote_owed_from_lp: cached_info.quote_owed_from_lp_pool,
3034+
quote_constituent_token_balance: quote_constituent.token_balance,
3035+
fee_pool_balance: get_token_amount(
30833036
perp_market.amm.fee_pool.scaled_balance,
3084-
&quote_market,
3037+
quote_market,
30853038
&SpotBalanceType::Deposit,
3086-
)?;
3087-
if fee_pool_token_amount > amount_to_send as u128 {
3088-
perp_market
3089-
.amm
3090-
.fee_pool
3091-
.decrease_balance((amount_to_send as u128).safe_mul(precision_increase)?)?;
3092-
cached_info.last_fee_pool_token_amount = cached_info
3093-
.last_fee_pool_token_amount
3094-
.safe_sub(amount_to_send as u128)?;
3095-
cached_info.quote_owed_from_lp = 0;
3096-
3097-
controller::token::send_from_program_vault(
3039+
)?,
3040+
pnl_pool_balance: get_token_amount(
3041+
perp_market.pnl_pool.scaled_balance,
3042+
quote_market,
3043+
&SpotBalanceType::Deposit,
3044+
)?,
3045+
quote_market,
3046+
};
3047+
3048+
// Calculate settlement
3049+
let settlement_result = calculate_settlement_amount(&settlement_ctx)?;
3050+
3051+
if settlement_result.direction == SettlementDirection::None {
3052+
continue;
3053+
}
3054+
3055+
// Execute token transfer
3056+
match settlement_result.direction {
3057+
SettlementDirection::FromLpPool => {
3058+
execute_token_transfer(
3059+
&ctx.accounts.token_program,
3060+
constituent_token_account,
3061+
&ctx.accounts.quote_token_vault,
3062+
&ctx.accounts.drift_signer,
3063+
state.signer_nonce,
3064+
settlement_result.amount_transferred,
3065+
&mint,
3066+
)?;
3067+
}
3068+
SettlementDirection::ToLpPool => {
3069+
execute_token_transfer(
30983070
&ctx.accounts.token_program,
30993071
&ctx.accounts.quote_token_vault,
31003072
constituent_token_account,
31013073
&ctx.accounts.drift_signer,
31023074
state.signer_nonce,
3103-
amount_to_send.cast::<u64>()?,
3104-
&Some(mint),
3075+
settlement_result.amount_transferred,
3076+
&mint,
31053077
)?;
3078+
}
3079+
SettlementDirection::None => unreachable!(),
3080+
}
31063081

3107-
// Sync the constituent token account balance
3108-
constituent_token_account.reload()?;
3109-
quote_constituent.sync_token_balance(constituent_token_account.amount);
3082+
// Update market pools
3083+
update_perp_market_pools(&mut perp_market, &settlement_result, precision_increase)?;
3084+
3085+
// Calculate new quote owed amount
3086+
let new_quote_owed = match settlement_result.direction {
3087+
SettlementDirection::FromLpPool => cached_info
3088+
.quote_owed_from_lp_pool
3089+
.safe_sub(settlement_result.amount_transferred as i64)?,
3090+
SettlementDirection::ToLpPool => cached_info
3091+
.quote_owed_from_lp_pool
3092+
.safe_add(settlement_result.amount_transferred as i64)?,
3093+
SettlementDirection::None => cached_info.quote_owed_from_lp_pool,
3094+
};
31103095

3111-
// Update the last settle info
3112-
cached_info.last_settle_amount = amount_to_send.cast::<u64>()?;
3113-
cached_info.last_settle_ts = Clock::get()?.unix_timestamp;
3096+
// Update cache info
3097+
update_cache_info(cached_info, &settlement_result, new_quote_owed, timestamp)?;
31143098

3115-
// Update LP Pool Stats
3099+
// Update LP pool stats
3100+
match settlement_result.direction {
3101+
SettlementDirection::FromLpPool => {
3102+
lp_pool.cumulative_usdc_sent_to_perp_markets = lp_pool
3103+
.cumulative_usdc_sent_to_perp_markets
3104+
.saturating_add(settlement_result.amount_transferred as u128);
3105+
}
3106+
SettlementDirection::ToLpPool => {
31163107
lp_pool.cumulative_usdc_received_from_perp_markets = lp_pool
31173108
.cumulative_usdc_received_from_perp_markets
3118-
.saturating_add(amount_to_send.cast::<u128>()?);
3119-
} else {
3120-
// If the fee pool cannot cover the whole amount, we take the rest from the pnl pool and set the
3121-
// fee pool balances to 0
3122-
3123-
let remaining_amount_to_send =
3124-
(amount_to_send as u128).safe_sub(fee_pool_token_amount)?;
3125-
3126-
perp_market
3127-
.amm
3128-
.fee_pool
3129-
.decrease_balance(fee_pool_token_amount.safe_mul(precision_increase)?)?;
3130-
cached_info.last_fee_pool_token_amount = 0;
3131-
cached_info.quote_owed_from_lp += fee_pool_token_amount.cast::<i64>()?;
3132-
3133-
// Similarly, can the pnl pool cover the rest?
3134-
let pnl_pool_token_amount = get_token_amount(
3135-
perp_market.pnl_pool.scaled_balance,
3136-
&quote_market,
3137-
&SpotBalanceType::Deposit,
3138-
)?;
3139-
if remaining_amount_to_send > pnl_pool_token_amount {
3140-
let transfer_amount = if pnl_pool_token_amount == 0 {
3141-
fee_pool_token_amount
3142-
} else {
3143-
perp_market.pnl_pool.decrease_balance(
3144-
pnl_pool_token_amount.safe_mul(precision_increase)?,
3145-
)?;
3146-
cached_info.last_net_pnl_pool_token_amount = cached_info
3147-
.last_net_pnl_pool_token_amount
3148-
.safe_sub(pnl_pool_token_amount.cast::<i128>()?)?;
3149-
cached_info.quote_owed_from_lp += pnl_pool_token_amount.cast::<i64>()?;
3150-
3151-
fee_pool_token_amount.safe_add(pnl_pool_token_amount)?
3152-
};
3153-
3154-
controller::token::send_from_program_vault(
3155-
&ctx.accounts.token_program,
3156-
&ctx.accounts.quote_token_vault,
3157-
constituent_token_account,
3158-
&ctx.accounts.drift_signer,
3159-
state.signer_nonce,
3160-
transfer_amount.cast::<u64>()?,
3161-
&Some(mint),
3162-
)?;
3163-
3164-
// Sync the constituent token account balance
3165-
constituent_token_account.reload()?;
3166-
quote_constituent.sync_token_balance(constituent_token_account.amount);
3167-
3168-
// Update the last settle info
3169-
cached_info.last_settle_amount = transfer_amount.cast::<u64>()?;
3170-
cached_info.last_settle_ts = Clock::get()?.unix_timestamp;
3171-
3172-
// Update LP Pool Stats
3173-
lp_pool.cumulative_usdc_received_from_perp_markets = lp_pool
3174-
.cumulative_usdc_received_from_perp_markets
3175-
.saturating_add(transfer_amount.cast::<u128>()?);
3176-
} else {
3177-
perp_market
3178-
.pnl_pool
3179-
.decrease_balance(remaining_amount_to_send.safe_mul(precision_increase)?)?;
3180-
cached_info
3181-
.last_net_pnl_pool_token_amount
3182-
.safe_sub(remaining_amount_to_send.cast::<i128>()?)?;
3183-
cached_info.quote_owed_from_lp += remaining_amount_to_send.cast::<i64>()?;
3184-
3185-
controller::token::send_from_program_vault(
3186-
&ctx.accounts.token_program,
3187-
&ctx.accounts.quote_token_vault,
3188-
constituent_token_account,
3189-
&ctx.accounts.drift_signer,
3190-
state.signer_nonce,
3191-
amount_to_send.cast::<u64>()?,
3192-
&Some(mint),
3193-
)?;
3194-
3195-
// Sync the constituent token account balance
3196-
constituent_token_account.reload()?;
3197-
quote_constituent.sync_token_balance(constituent_token_account.amount);
3198-
3199-
// Update the last settle info
3200-
cached_info.last_settle_amount = amount_to_send.cast::<u64>()?;
3201-
cached_info.last_settle_ts = Clock::get()?.unix_timestamp;
3202-
3203-
// Update LP Pool Stats
3204-
lp_pool.cumulative_usdc_received_from_perp_markets = lp_pool
3205-
.cumulative_usdc_received_from_perp_markets
3206-
.saturating_add(amount_to_send.cast::<u128>()?);
3207-
}
3109+
.saturating_add(settlement_result.amount_transferred as u128);
32083110
}
3209-
} else {
3210-
// nothing owed to settle
3211-
continue;
3111+
SettlementDirection::None => {}
32123112
}
3113+
3114+
// Sync constituent token balance
3115+
constituent_token_account.reload()?;
3116+
quote_constituent.sync_token_balance(constituent_token_account.amount);
32133117
}
32143118

3119+
// Final validation
32153120
math::spot_withdraw::validate_spot_market_vault_amount(
32163121
quote_market,
32173122
ctx.accounts.quote_token_vault.amount,

programs/drift/src/instructions/lp_pool.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ pub fn handle_update_lp_pool_aum<'c: 'info, 'info>(
325325

326326
let mut aum_i128 = aum.cast::<i128>()?;
327327
for cache_datum in amm_cache.iter() {
328-
aum_i128 -= cache_datum.quote_owed_from_lp as i128;
328+
aum_i128 -= cache_datum.quote_owed_from_lp_pool as i128;
329329
}
330330
aum = aum_i128.max(0i128).cast::<u128>()?;
331331

0 commit comments

Comments
 (0)