Skip to content

Commit

Permalink
program: reference price feature gate (#782)
Browse files Browse the repository at this point in the history
* bigz/reference-price-offset-feature-gate-fixes

* fix liquidity ratio sign bug

* fix amm sdk tests

* fix cargo fmt

* fix format
  • Loading branch information
0xbigz authored Dec 19, 2023
1 parent 6779481 commit 46f52ce
Show file tree
Hide file tree
Showing 7 changed files with 470 additions and 69 deletions.
11 changes: 7 additions & 4 deletions programs/drift/src/controller/amm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,26 +167,29 @@ pub fn update_spread_reserves(amm: &mut AMM) -> DriftResult {
}

pub fn update_spreads(amm: &mut AMM, reserve_price: u64) -> DriftResult<(u32, u32)> {
let max_offset = amm.get_max_reference_price_offset()?;
let max_ref_offset = amm.get_max_reference_price_offset()?;

let reference_price_offset = if amm.curve_update_intensity > 0 {
let reference_price_offset = if max_ref_offset > 0 {
let liquidity_ratio = amm_spread::calculate_inventory_liquidity_ratio(
amm.base_asset_amount_with_amm,
amm.base_asset_reserve,
amm.max_base_asset_reserve,
amm.min_base_asset_reserve,
)?;

let signed_liquidity_ratio =
liquidity_ratio.safe_mul(amm.get_protocol_owned_position()?.signum().cast()?)?;

amm_spread::calculate_reference_price_offset(
reserve_price,
amm.last_24h_avg_funding_rate,
liquidity_ratio,
signed_liquidity_ratio,
amm.min_order_size,
amm.historical_oracle_data.last_oracle_price_twap_5min,
amm.last_mark_price_twap_5min,
amm.historical_oracle_data.last_oracle_price_twap,
amm.last_mark_price_twap,
max_offset,
max_ref_offset,
)?
} else {
0
Expand Down
4 changes: 3 additions & 1 deletion programs/drift/src/instructions/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1845,8 +1845,10 @@ pub fn handle_update_perp_market_curve_update_intensity(
ctx: Context<AdminUpdatePerpMarket>,
curve_update_intensity: u8,
) -> Result<()> {
// (0, 100] is for repeg / formulaic k intensity
// (100, 200] is for reference price offset intensity
validate!(
curve_update_intensity <= 100,
curve_update_intensity <= 200,
ErrorCode::DefaultError,
"invalid curve_update_intensity",
)?;
Expand Down
25 changes: 14 additions & 11 deletions programs/drift/src/math/amm_spread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ use crate::math::bn::U192;
use crate::math::casting::Cast;
use crate::math::constants::{
AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO_I128, AMM_TO_QUOTE_PRECISION_RATIO_I128,
BID_ASK_SPREAD_PRECISION, BID_ASK_SPREAD_PRECISION_I128, BID_ASK_SPREAD_PRECISION_U128,
DEFAULT_LARGE_BID_ASK_FACTOR, DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT,
FUNDING_RATE_BUFFER, MAX_BID_ASK_INVENTORY_SKEW_FACTOR, PEG_PRECISION, PERCENTAGE_PRECISION,
BID_ASK_SPREAD_PRECISION, BID_ASK_SPREAD_PRECISION_I128, DEFAULT_LARGE_BID_ASK_FACTOR,
DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT, FUNDING_RATE_BUFFER,
MAX_BID_ASK_INVENTORY_SKEW_FACTOR, PEG_PRECISION, PERCENTAGE_PRECISION,
PERCENTAGE_PRECISION_I128, PERCENTAGE_PRECISION_U64, PRICE_PRECISION, PRICE_PRECISION_I128,
PRICE_PRECISION_I64,
};
Expand Down Expand Up @@ -449,23 +449,26 @@ pub fn calculate_spread_reserves(
PositionDirection::Short => amm.short_spread,
};

let spread_with_offset: i32 = spread.cast::<i32>()?.safe_add(amm.reference_price_offset)?;
let spread_with_offset: i32 = if direction == PositionDirection::Short {
(-spread.cast::<i32>()?).safe_add(amm.reference_price_offset)?
} else {
spread.cast::<i32>()?.safe_add(amm.reference_price_offset)?
};

let quote_asset_reserve_delta = if spread > 0 {
let quote_asset_reserve_delta = if spread_with_offset.abs() > 1 {
amm.quote_asset_reserve
.safe_div(BID_ASK_SPREAD_PRECISION_U128 / (spread_with_offset.cast::<u128>()? / 2))?
.cast::<i128>()?
.safe_div(BID_ASK_SPREAD_PRECISION_I128 / (spread_with_offset.cast::<i128>()? / 2))?
} else {
0
};

let quote_asset_reserve = if spread_with_offset >= 0 && direction == PositionDirection::Long
|| spread_with_offset <= 0 && direction == PositionDirection::Short
{
let quote_asset_reserve = if quote_asset_reserve_delta > 0 {
amm.quote_asset_reserve
.safe_add(quote_asset_reserve_delta)?
.safe_add(quote_asset_reserve_delta.unsigned_abs())?
} else {
amm.quote_asset_reserve
.safe_sub(quote_asset_reserve_delta)?
.safe_sub(quote_asset_reserve_delta.unsigned_abs())?
};

let invariant_sqrt_u192 = U192::from(amm.sqrt_k);
Expand Down
84 changes: 79 additions & 5 deletions programs/drift/src/math/amm_spread/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#[cfg(test)]
mod test {
use crate::math::amm::calculate_price;
use crate::math::amm_spread::*;
use crate::math::constants::{
AMM_RESERVE_PRECISION, BASE_PRECISION_I128, BID_ASK_SPREAD_PRECISION,
Expand Down Expand Up @@ -362,29 +363,102 @@ mod test {

assert!(long_spread5 < long_spread4);
assert_eq!(short_spread5, short_spread4);
assert_eq!(long_spread5, 27270);
assert_eq!(short_spread5, 500);

let amm = AMM {
let mut amm = AMM {
base_asset_reserve: 2 * AMM_RESERVE_PRECISION,
quote_asset_reserve: 2 * AMM_RESERVE_PRECISION,
sqrt_k: 2 * AMM_RESERVE_PRECISION,
peg_multiplier: PEG_PRECISION,
long_spread: long_spread5,
short_spread: short_spread5,
max_spread: 1000,
curve_update_intensity: 100,
..AMM::default()
};

let max_ref_offset = amm.get_max_reference_price_offset().unwrap();
assert_eq!(max_ref_offset, 0);

amm.curve_update_intensity = 110;
let max_ref_offset = amm.get_max_reference_price_offset().unwrap();
assert_eq!(max_ref_offset, 1000); // 10 bps

amm.curve_update_intensity = 200;
let max_ref_offset = amm.get_max_reference_price_offset().unwrap();
assert_eq!(max_ref_offset, 10000); // 100 bps

amm.max_spread = 10000 * 10; // 10%
let max_ref_offset = amm.get_max_reference_price_offset().unwrap();
assert_eq!(max_ref_offset, 20000); // 200 bps (5% of max spread)

let orig_price = calculate_price(
amm.quote_asset_reserve,
amm.base_asset_reserve,
amm.peg_multiplier,
)
.unwrap();
assert_eq!(orig_price, 1000000);

let (bar_l, qar_l) = calculate_spread_reserves(&amm, PositionDirection::Long).unwrap();
let (bar_s, qar_s) = calculate_spread_reserves(&amm, PositionDirection::Short).unwrap();

assert!(qar_l > amm.quote_asset_reserve);
assert!(bar_l < amm.base_asset_reserve);
assert!(qar_s < amm.quote_asset_reserve);
assert!(bar_s > amm.base_asset_reserve);
assert_eq!(bar_s, 2000500125);
assert_eq!(bar_l, 1972972973);
assert_eq!(qar_l, 2027397260);
assert_eq!(qar_s, 1999500000);

assert!(qar_l > amm.quote_asset_reserve);
assert!(bar_l < amm.base_asset_reserve);
assert!(qar_s < amm.quote_asset_reserve);
assert!(bar_s > amm.base_asset_reserve);

let l_price = calculate_price(qar_l, bar_l, amm.peg_multiplier).unwrap();
let s_price = calculate_price(qar_s, bar_s, amm.peg_multiplier).unwrap();
assert_eq!(l_price, 1027584);
assert_eq!(s_price, 999500);
assert!(l_price > s_price);

amm.reference_price_offset = 1000; // 10 bps

let (bar_l, qar_l) = calculate_spread_reserves(&amm, PositionDirection::Long).unwrap();
let (bar_s, qar_s) = calculate_spread_reserves(&amm, PositionDirection::Short).unwrap();

assert!(qar_l > amm.quote_asset_reserve);
assert!(bar_l < amm.base_asset_reserve);
assert!(qar_s > amm.quote_asset_reserve);
assert!(bar_s < amm.base_asset_reserve);
assert_eq!(bar_s, 1999500124); // up
assert_eq!(bar_l, 1971830986); // down
assert_eq!(qar_l, 2028571428); // up
assert_eq!(qar_s, 2000500000); // down

let l_price = calculate_price(qar_l, bar_l, amm.peg_multiplier).unwrap();
let s_price = calculate_price(qar_s, bar_s, amm.peg_multiplier).unwrap();
assert_eq!(l_price, 1028775);
assert_eq!(s_price, 1000500);
assert!(l_price > s_price);

amm.reference_price_offset = -1000; // -10 bps
let (bar_l, qar_l) = calculate_spread_reserves(&amm, PositionDirection::Long).unwrap();
let (bar_s, qar_s) = calculate_spread_reserves(&amm, PositionDirection::Short).unwrap();

assert!(qar_l > amm.quote_asset_reserve);
assert!(bar_l < amm.base_asset_reserve);
assert!(qar_s < amm.quote_asset_reserve);
assert!(bar_s > amm.base_asset_reserve);
assert_eq!(bar_s, 2001501501); // up
assert_eq!(bar_l, 1974025974); // up
assert_eq!(qar_l, 2026315789); // down
assert_eq!(qar_s, 1998499625); // down

let l_price = calculate_price(qar_l, bar_l, amm.peg_multiplier).unwrap();
let s_price = calculate_price(qar_s, bar_s, amm.peg_multiplier).unwrap();
assert_eq!(l_price, 1026488);
assert_eq!(s_price, 998500);
assert!(l_price > s_price);

let (long_spread_btc, short_spread_btc) = calculate_spread(
500,
62099,
Expand Down
20 changes: 17 additions & 3 deletions programs/drift/src/state/perp_market.rs
Original file line number Diff line number Diff line change
Expand Up @@ -771,10 +771,24 @@ impl Default for AMM {
}

impl AMM {
pub fn get_protocol_owned_position(self) -> DriftResult<i64> {
self.base_asset_amount_with_amm
.safe_add(self.base_asset_amount_with_unsettled_lp)?
.cast::<i64>()
}

pub fn get_max_reference_price_offset(self) -> DriftResult<i64> {
// always allow 10 bps of price offset, up to a fifth of the market's max_spread
let ten_bps = PERCENTAGE_PRECISION.cast::<i64>()? / 1000;
let max_offset = (self.max_spread.cast::<i64>()? / 5).max(ten_bps);
if self.curve_update_intensity <= 100 {
return Ok(0);
}

let lower_bound_multiplier: i64 =
self.curve_update_intensity.safe_sub(100)?.cast::<i64>()?;

// always allow 1-100 bps of price offset, up to a fifth of the market's max_spread
let lb_bps =
(PERCENTAGE_PRECISION.cast::<i64>()? / 10000).safe_mul(lower_bound_multiplier)?;
let max_offset = (self.max_spread.cast::<i64>()? / 5).max(lb_bps);

Ok(max_offset)
}
Expand Down
64 changes: 37 additions & 27 deletions sdk/src/math/amm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -863,13 +863,14 @@ export function calculateSpreadReserves(
);

let quoteAssetReserve;
if (
(spread >= 0 && isVariant(direction, 'long')) ||
(spread <= 0 && isVariant(direction, 'short'))
) {
quoteAssetReserve = amm.quoteAssetReserve.add(quoteAssetReserveDelta);
if (quoteAssetReserveDelta.gte(ZERO)) {
quoteAssetReserve = amm.quoteAssetReserve.add(
quoteAssetReserveDelta.abs()
);
} else {
quoteAssetReserve = amm.quoteAssetReserve.sub(quoteAssetReserveDelta);
quoteAssetReserve = amm.quoteAssetReserve.sub(
quoteAssetReserveDelta.abs()
);
}

const baseAssetReserve = amm.sqrtK.mul(amm.sqrtK).div(quoteAssetReserve);
Expand All @@ -886,26 +887,35 @@ export function calculateSpreadReserves(
);

// always allow 10 bps of price offset, up to a fifth of the market's max_spread
const maxOffset = Math.max(
amm.maxSpread / 5,
PERCENTAGE_PRECISION.toNumber() / 1000
);
const liquidityFraction = calculateInventoryLiquidityRatio(
amm.baseAssetAmountWithAmm,
amm.baseAssetReserve,
amm.minBaseAssetReserve,
amm.maxBaseAssetReserve
);
const referencePriceOffset = calculateReferencePriceOffset(
reservePrice,
amm.last24HAvgFundingRate,
liquidityFraction,
amm.historicalOracleData.lastOraclePriceTwap5Min,
amm.lastMarkPriceTwap5Min,
amm.historicalOracleData.lastOraclePriceTwap,
amm.lastMarkPriceTwap,
maxOffset
);
let maxOffset = 0;
let referencePriceOffset = ZERO;
if (amm.curveUpdateIntensity > 100) {
maxOffset = Math.max(
amm.maxSpread / 5,
(PERCENTAGE_PRECISION.toNumber() / 10000) *
(amm.curveUpdateIntensity - 100)
);

const liquidityFraction = calculateInventoryLiquidityRatio(
amm.baseAssetAmountWithAmm,
amm.baseAssetReserve,
amm.minBaseAssetReserve,
amm.maxBaseAssetReserve
);
const liquidityFractionSigned = liquidityFraction.mul(
sigNum(amm.baseAssetAmountWithAmm.add(amm.baseAssetAmountWithUnsettledLp))
);
referencePriceOffset = calculateReferencePriceOffset(
reservePrice,
amm.last24HAvgFundingRate,
liquidityFractionSigned,
amm.historicalOracleData.lastOraclePriceTwap5Min,
amm.lastMarkPriceTwap5Min,
amm.historicalOracleData.lastOraclePriceTwap,
amm.lastMarkPriceTwap,
maxOffset
);
}

const [longSpread, shortSpread] = calculateSpread(
amm,
Expand All @@ -920,7 +930,7 @@ export function calculateSpreadReserves(
amm
);
const bidReserves = calculateSpreadReserve(
shortSpread + referencePriceOffset.toNumber(),
-shortSpread + referencePriceOffset.toNumber(),
PositionDirection.SHORT,
amm
);
Expand Down
Loading

0 comments on commit 46f52ce

Please sign in to comment.