Skip to content
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

bigz/amm-drawdown-guardrail #865

Merged
merged 10 commits into from
Feb 15, 2024
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Features

- program: amm drawdown check ([#865](https://github.com/drift-labs/protocol-v2/pull/865))
- program: relax oracle guardrail validity check for init margin calc for positive pnl ([#876](https://github.com/drift-labs/protocol-v2/pull/876))
- program: add more max spread baselines ([#858](https://github.com/drift-labs/protocol-v2/pull/858))

Expand Down
1 change: 1 addition & 0 deletions programs/drift/src/controller/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -970,6 +970,7 @@ pub fn fill_perp_order(
{
let market = &mut perp_market_map.get_ref_mut(&market_index)?;
amm_is_available &= !market.is_operation_paused(PerpOperation::AmmFill);
amm_is_available &= !market.has_too_much_drawdown()?;
validation::perp_market::validate_perp_market(market)?;
validate!(
!market.is_in_settlement(now),
Expand Down
64 changes: 63 additions & 1 deletion programs/drift/src/math/amm/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,68 @@ use crate::state::oracle::HistoricalOracleData;
use crate::state::perp_market::PerpMarket;
use crate::state::user::PerpPosition;

#[test]
fn calculate_amm_available_guards() {
let prev = 1656682258;
let _now = prev + 2;

// let px = 32 * PRICE_PRECISION;

let mut market: PerpMarket = PerpMarket::default_btc_test();

let _oracle_price_data = OraclePriceData {
price: (34 * PRICE_PRECISION) as i64,
confidence: PRICE_PRECISION_U64 / 100,
delay: 1,
has_sufficient_number_of_data_points: true,
};

assert_eq!(market.amm.net_revenue_since_last_funding, 0);
assert_eq!(market.amm.total_fee_minus_distributions, 0);

assert_eq!(!market.has_too_much_drawdown().unwrap(), true);

market.amm.net_revenue_since_last_funding = -100_000_000_000;
market.amm.total_fee_minus_distributions = 100_000_000_000;

assert_eq!(!market.has_too_much_drawdown().unwrap(), false);

market.amm.net_revenue_since_last_funding = -10_000_000_000;
market.amm.total_fee_minus_distributions = 100_000_000_000;

assert_eq!(!market.has_too_much_drawdown().unwrap(), false);

market.amm.net_revenue_since_last_funding = -5_000_000_000;
market.amm.total_fee_minus_distributions = 100_000_000_000;

assert_eq!(!market.has_too_much_drawdown().unwrap(), false);

market.amm.net_revenue_since_last_funding = -1_000_000_000;
market.amm.total_fee_minus_distributions = 100_000_000_000;

assert_eq!(!market.has_too_much_drawdown().unwrap(), true);

market.amm.net_revenue_since_last_funding = -1_000_000_000;
market.amm.total_fee_minus_distributions = 1_000_000;

assert_eq!(!market.has_too_much_drawdown().unwrap(), true);

market.amm.net_revenue_since_last_funding = -6_000_000_000;
market.amm.total_fee_minus_distributions = 1_000_000;

assert_eq!(!market.has_too_much_drawdown().unwrap(), false);

market.amm.net_revenue_since_last_funding = -5_000;
market.amm.total_fee_minus_distributions = -9279797219;

assert_eq!(!market.has_too_much_drawdown().unwrap(), true); // too small net_revenue_since_last_funding drawdown

market.amm.net_revenue_since_last_funding = -88_000_000_000;
market.amm.total_fee_minus_distributions = -9279797219;

assert_eq!(!market.has_too_much_drawdown().unwrap(), false); // too small net_revenue_since_last_funding drawdown
}

#[test]
fn calculate_net_user_pnl_test() {
let prev = 1656682258;
Expand Down Expand Up @@ -40,7 +102,7 @@ fn calculate_net_user_pnl_test() {
let net_user_pnl = calculate_net_user_pnl(&amm, oracle_price_data.price).unwrap();
assert_eq!(net_user_pnl, 0);

let market = PerpMarket::default_btc_test();
let market: PerpMarket = PerpMarket::default_btc_test();
let net_user_pnl = calculate_net_user_pnl(
&market.amm,
market.amm.historical_oracle_data.last_oracle_price,
Expand Down
45 changes: 42 additions & 3 deletions programs/drift/src/state/perp_market.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ use crate::math::constants::{
AMM_RESERVE_PRECISION, MAX_CONCENTRATION_COEFFICIENT, PRICE_PRECISION_I64,
};
use crate::math::constants::{
AMM_RESERVE_PRECISION_I128, BID_ASK_SPREAD_PRECISION, BID_ASK_SPREAD_PRECISION_U128,
AMM_RESERVE_PRECISION_I128, AMM_TO_QUOTE_PRECISION_RATIO, BID_ASK_SPREAD_PRECISION,
BID_ASK_SPREAD_PRECISION_U128, DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT,
LP_FEE_SLICE_DENOMINATOR, LP_FEE_SLICE_NUMERATOR, MARGIN_PRECISION_U128, PERCENTAGE_PRECISION,
SPOT_WEIGHT_PRECISION, TWENTY_FOUR_HOUR,
PERCENTAGE_PRECISION_I128, PRICE_PRECISION, SPOT_WEIGHT_PRECISION, TWENTY_FOUR_HOUR,
};
use crate::math::helpers::get_proportion_i128;

Expand All @@ -28,7 +29,6 @@ use crate::state::events::OrderActionExplanation;
use crate::state::oracle::{HistoricalOracleData, OracleSource};
use crate::state::spot_market::{AssetTier, SpotBalance, SpotBalanceType};
use crate::state::traits::{MarketIndexOffset, Size};
use crate::{AMM_TO_QUOTE_PRECISION_RATIO, PRICE_PRECISION};
use borsh::{BorshDeserialize, BorshSerialize};

use crate::state::paused_operations::PerpOperation;
Expand Down Expand Up @@ -292,6 +292,45 @@ impl PerpMarket {
PerpOperation::is_operation_paused(self.paused_operations, operation)
}

pub fn has_too_much_drawdown(&self) -> DriftResult<bool> {
0xbigz marked this conversation as resolved.
Show resolved Hide resolved
let quote_drawdown_limit_breached = match self.contract_tier {
ContractTier::A | ContractTier::B => {
self.amm.net_revenue_since_last_funding
<= DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT * 400
}
_ => {
self.amm.net_revenue_since_last_funding
<= DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT * 200
}
};

if quote_drawdown_limit_breached {
let percent_drawdown = self
.amm
.net_revenue_since_last_funding
0xbigz marked this conversation as resolved.
Show resolved Hide resolved
.cast::<i128>()?
.safe_mul(PERCENTAGE_PRECISION_I128)?
.safe_div(self.amm.total_fee_minus_distributions.max(1))?;

let percent_drawdown_limit_breached = match self.contract_tier {
ContractTier::A => percent_drawdown <= -PERCENTAGE_PRECISION_I128 / 50,
ContractTier::B => percent_drawdown <= -PERCENTAGE_PRECISION_I128 / 33,
ContractTier::C => percent_drawdown <= -PERCENTAGE_PRECISION_I128 / 25,
_ => percent_drawdown <= -PERCENTAGE_PRECISION_I128 / 20,
};

if percent_drawdown_limit_breached {
msg!("AMM has too much on-the-hour drawdown (percentage={}, quote={}) to accept fills",
percent_drawdown,
self.amm.net_revenue_since_last_funding
);
return Ok(true);
}
}

Ok(false)
}

pub fn get_sanitize_clamp_denominator(self) -> DriftResult<Option<i64>> {
Ok(match self.contract_tier {
ContractTier::A => Some(10_i64), // 10%
Expand Down
71 changes: 68 additions & 3 deletions sdk/src/math/exchangeStatus.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import {
DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT,
PERCENTAGE_PRECISION,
ONE,
} from '../constants/numericConstants';
import {
ExchangeStatus,
PerpMarketAccount,
PerpOperation,
SpotMarketAccount,
SpotOperation,
StateAccount,
isVariant,
} from '../types';
import { BN } from '@coral-xyz/anchor';

export function exchangePaused(state: StateAccount): boolean {
return state.exchangeStatus !== ExchangeStatus.ACTIVE;
Expand Down Expand Up @@ -41,10 +48,19 @@ export function ammPaused(
}

if (market.hasOwnProperty('amm')) {
return isOperationPaused(market.pausedOperations, PerpOperation.AMM_FILL);
} else {
return false;
const operationPaused = isOperationPaused(
market.pausedOperations,
PerpOperation.AMM_FILL
);
if (operationPaused) {
return true;
}
if (isAmmDrawdownPause(market as PerpMarketAccount)) {
return true;
}
}

return false;
}

export function isOperationPaused(
Expand All @@ -53,3 +69,52 @@ export function isOperationPaused(
): boolean {
return (pausedOperations & operation) > 0;
}

export function isAmmDrawdownPause(market: PerpMarketAccount): boolean {
let quoteDrawdownLimitBreached: boolean;

if (
isVariant(market.contractTier, 'a') ||
isVariant(market.contractTier, 'b')
) {
quoteDrawdownLimitBreached = market.amm.netRevenueSinceLastFunding.lte(
DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT.muln(400)
);
} else {
quoteDrawdownLimitBreached = market.amm.netRevenueSinceLastFunding.lte(
DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT.muln(200)
);
}

if (quoteDrawdownLimitBreached) {
const percentDrawdown = market.amm.netRevenueSinceLastFunding
.mul(PERCENTAGE_PRECISION)
.div(BN.max(market.amm.totalFeeMinusDistributions, ONE));

let percentDrawdownLimitBreached: boolean;

if (isVariant(market.contractTier, 'a')) {
percentDrawdownLimitBreached = percentDrawdown.lte(
PERCENTAGE_PRECISION.divn(50).neg()
);
} else if (isVariant(market.contractTier, 'b')) {
percentDrawdownLimitBreached = percentDrawdown.lte(
PERCENTAGE_PRECISION.divn(33).neg()
);
} else if (isVariant(market.contractTier, 'c')) {
percentDrawdownLimitBreached = percentDrawdown.lte(
PERCENTAGE_PRECISION.divn(25).neg()
);
} else {
percentDrawdownLimitBreached = percentDrawdown.lte(
PERCENTAGE_PRECISION.divn(20).neg()
);
}

if (percentDrawdownLimitBreached) {
return true;
}
}

return false;
}
6 changes: 6 additions & 0 deletions sdk/tests/dlob/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ export const mockPerpMarkets: Array<PerpMarketAccount> = [
},
quoteSpotMarketIndex: 0,
feeAdjustment: 0,
pausedOperations: 0,
},
{
status: MarketStatus.INITIALIZED,
Expand Down Expand Up @@ -219,6 +220,7 @@ export const mockPerpMarkets: Array<PerpMarketAccount> = [
},
quoteSpotMarketIndex: 0,
feeAdjustment: 0,
pausedOperations: 0,
},
{
status: MarketStatus.INITIALIZED,
Expand Down Expand Up @@ -257,6 +259,7 @@ export const mockPerpMarkets: Array<PerpMarketAccount> = [
},
quoteSpotMarketIndex: 0,
feeAdjustment: 0,
pausedOperations: 0,
},
];

Expand Down Expand Up @@ -341,6 +344,7 @@ export const mockSpotMarkets: Array<SpotMarketAccount> = [
lastIndexPriceTwap5Min: PRICE_PRECISION,
lastIndexPriceTwapTs: new BN(0),
},
pausedOperations: 0,
},
{
status: MarketStatus.ACTIVE,
Expand Down Expand Up @@ -422,6 +426,7 @@ export const mockSpotMarkets: Array<SpotMarketAccount> = [
lastIndexPriceTwap5Min: new BN(0),
lastIndexPriceTwapTs: new BN(0),
},
pausedOperations: 0,
},
{
status: MarketStatus.ACTIVE,
Expand Down Expand Up @@ -503,6 +508,7 @@ export const mockSpotMarkets: Array<SpotMarketAccount> = [
lastIndexPriceTwap5Min: new BN(0),
lastIndexPriceTwapTs: new BN(0),
},
pausedOperations: 0,
},
];

Expand Down
Loading