Skip to content

Commit

Permalink
program: settle_mulitple_pnls that handles invalid oracle and efficie…
Browse files Browse the repository at this point in the history
…nt cus (#1030)

* settle_pnls init

* add sdk

* add tests

* fix user_must_settle_themself

* tweak log msg

* CHANGELOG

* prettify:fix
  • Loading branch information
crispheaney committed May 28, 2024
1 parent bba2434 commit 19de114
Show file tree
Hide file tree
Showing 14 changed files with 415 additions and 66 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Features

- program: add settle pnl mode ([#1030](https://github.com/drift-labs/protocol-v2/pull/1030))

### Fixes

- program: update_perp_auction_params_limit_orders unwraps oracle_price_offset
Expand Down
4 changes: 3 additions & 1 deletion programs/drift/src/controller/lp/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use crate::controller::lp::*;
use crate::controller::pnl::settle_pnl;
use crate::state::perp_market::AMM;
use crate::state::user::PerpPosition;
use crate::BASE_PRECISION_I64;
use crate::PRICE_PRECISION;
use crate::{SettlePnlMode, BASE_PRECISION_I64};
use std::str::FromStr;

use anchor_lang::Owner;
Expand Down Expand Up @@ -573,6 +573,8 @@ pub fn test_lp_settle_pnl() {
&mut oracle_map,
&clock,
&state,
None,
SettlePnlMode::MustSettle,
);

assert_eq!(result, Ok(()));
Expand Down
150 changes: 88 additions & 62 deletions programs/drift/src/controller/pnl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use crate::state::oracle_map::OracleMap;
use crate::state::paused_operations::PerpOperation;
use crate::state::perp_market::MarketStatus;
use crate::state::perp_market_map::PerpMarketMap;
use crate::state::settle_pnl_mode::SettlePnlMode;
use crate::state::spot_market::{SpotBalance, SpotBalanceType};
use crate::state::spot_market_map::SpotMarketMap;
use crate::state::state::State;
Expand All @@ -56,6 +57,8 @@ pub fn settle_pnl(
oracle_map: &mut OracleMap,
clock: &Clock,
state: &State,
meets_margin_requirement: Option<bool>,
mode: SettlePnlMode,
) -> DriftResult {
validate!(!user.is_bankrupt(), ErrorCode::UserBankrupt)?;
let now = clock.unix_timestamp;
Expand Down Expand Up @@ -119,14 +122,24 @@ pub fn settle_pnl(
}
}
} else if unrealized_pnl < 0 {
// may already be cached
let meets_margin_requirement = match meets_margin_requirement {
Some(meets_margin_requirement) => meets_margin_requirement,
None => meets_maintenance_margin_requirement(
user,
perp_market_map,
spot_market_map,
oracle_map,
)?,
};

// cannot settle pnl this way on a user who is in liquidation territory
if !(meets_maintenance_margin_requirement(
user,
perp_market_map,
spot_market_map,
oracle_map,
)?) {
return Err(ErrorCode::InsufficientCollateralForSettlingPNL);
if !meets_margin_requirement {
let msg = format!(
"Does not meet margin requirement while settling Market = {}",
market_index
);
return mode.result(ErrorCode::InsufficientCollateralForSettlingPNL, &msg);
}
}

Expand All @@ -151,55 +164,64 @@ pub fn settle_pnl(
if !is_oracle_valid_for_action(oracle_validity, Some(DriftAction::SettlePnl))?
|| !perp_market.is_price_divergence_ok_for_settle_pnl(oracle_price)?
{
validate!(
perp_market.amm.last_oracle_valid,
ErrorCode::InvalidOracle,
"Oracle Price detected as invalid ({}) on last perp market update",
oracle_validity
)?;

validate!(
oracle_map.slot == perp_market.amm.last_update_slot,
ErrorCode::AMMNotUpdatedInSameSlot,
"Market={} AMM must be updated in a prior instruction within same slot (current={} != amm={}, last_oracle_valid={})",
market_index,
oracle_map.slot,
perp_market.amm.last_update_slot,
perp_market.amm.last_oracle_valid
)?;
if !perp_market.amm.last_oracle_valid {
let msg = format!(
"Oracle Price detected as invalid ({}) on last perp market update for Market = {}",
oracle_validity,
market_index
);
return mode.result(ErrorCode::InvalidOracle, &msg);
}

if oracle_map.slot != perp_market.amm.last_update_slot {
let msg = format!(
"Market={} AMM must be updated in a prior instruction within same slot (current={} != amm={}, last_oracle_valid={})",
market_index,
oracle_map.slot,
perp_market.amm.last_update_slot,
perp_market.amm.last_oracle_valid
);
return mode.result(ErrorCode::AMMNotUpdatedInSameSlot, &msg);
}
}
}
}

validate!(
!perp_market.is_operation_paused(PerpOperation::SettlePnl),
ErrorCode::InvalidMarketStatusToSettlePnl,
"Cannot settle pnl under current market = {} status",
market_index
)?;

if user.perp_positions[position_index].base_asset_amount != 0 {
validate!(
!perp_market.is_operation_paused(PerpOperation::SettlePnlWithPosition),
ErrorCode::InvalidMarketStatusToSettlePnl,
"Cannot settle pnl with position under current market = {} operation paused",
if perp_market.is_operation_paused(PerpOperation::SettlePnl) {
let msg = format!(
"Cannot settle pnl under current market = {} status",
market_index
)?;
);
return mode.result(ErrorCode::InvalidMarketStatusToSettlePnl, &msg);
}

validate!(
perp_market.status == MarketStatus::Active,
ErrorCode::InvalidMarketStatusToSettlePnl,
"Cannot settle pnl with position under non-Active current market = {} status",
market_index
)?;
let base_asset_amount = user.perp_positions[position_index].base_asset_amount;
if base_asset_amount != 0 {
if perp_market.is_operation_paused(PerpOperation::SettlePnlWithPosition) {
let msg = format!(
"Cannot settle pnl with position under current market = {} operation paused",
market_index
);
return mode.result(ErrorCode::InvalidMarketStatusToSettlePnl, &msg);
}

if perp_market.status != MarketStatus::Active {
let msg = format!(
"Cannot settle pnl with position under non-Active current market = {} status",
market_index
);
return mode.result(ErrorCode::InvalidMarketStatusToSettlePnl, &msg);
}
} else {
validate!(
perp_market.status == MarketStatus::Active
|| perp_market.status == MarketStatus::ReduceOnly,
ErrorCode::InvalidMarketStatusToSettlePnl,
"Cannot settle pnl under current market = {} status (neither Active or ReduceOnly)",
market_index
)?;
if perp_market.status != MarketStatus::Active
&& perp_market.status != MarketStatus::ReduceOnly
{
let msg = format!(
"Cannot settle pnl under current market = {} status (neither Active or ReduceOnly)",
market_index
);
return mode.result(ErrorCode::InvalidMarketStatusToSettlePnl, &msg);
}
}

let pnl_pool_token_amount = get_token_amount(
Expand Down Expand Up @@ -237,25 +259,30 @@ pub fn settle_pnl(
user_unsettled_pnl,
now,
)?;

if user_unsettled_pnl == 0 {
msg!("User has no unsettled pnl for market {}", market_index);
return Ok(());
let msg = format!("User has no unsettled pnl for market {}", market_index);
return mode.result(ErrorCode::NoUnsettledPnl, &msg);
} else if pnl_to_settle_with_user == 0 {
msg!(
let msg = format!(
"Pnl Pool cannot currently settle with user for market {}",
market_index
);
return Ok(());
return mode.result(ErrorCode::PnlPoolCantSettleUser, &msg);
}

validate!(
pnl_to_settle_with_user < 0
|| max_pnl_pool_excess > 0
|| (pnl_to_settle_with_user > 0 && user.is_being_liquidated())
|| (user.authority.eq(authority) || user.delegate.eq(authority)),
ErrorCode::UserMustSettleTheirOwnPositiveUnsettledPNL,
"User must settle their own unsettled pnl when its positive and pnl pool not in excess"
)?;
let user_must_settle_themself = pnl_to_settle_with_user >= 0
&& max_pnl_pool_excess <= 0
&& !(pnl_to_settle_with_user > 0 && base_asset_amount == 0 && user.is_being_liquidated())
&& !(user.authority.eq(authority) || user.delegate.eq(authority));

if user_must_settle_themself {
let msg = format!(
"Market = {} user must settle their own unsettled pnl when its positive and pnl pool not in excess",
market_index
);
return mode.result(ErrorCode::UserMustSettleTheirOwnPositiveUnsettledPNL, &msg);
}

update_spot_balances(
pnl_to_settle_with_user.unsigned_abs(),
Expand All @@ -277,7 +304,6 @@ pub fn settle_pnl(

update_settled_pnl(user, position_index, pnl_to_settle_with_user.cast()?)?;

let base_asset_amount = user.perp_positions[position_index].base_asset_amount;
let quote_asset_amount_after = user.perp_positions[position_index].quote_asset_amount;
let quote_entry_amount = user.perp_positions[position_index].quote_entry_amount;

Expand Down
Loading

0 comments on commit 19de114

Please sign in to comment.