Skip to content

Commit

Permalink
program: add lp shares rebase (#568)
Browse files Browse the repository at this point in the history
* start test

* improve lp_delta_quote

* improve per_lp_delta_quote

* init per_lp_base

* add rebase functions (wip)

* rm err msg

* add controller rebase to proper locations

* add rebase cargo test

* (wip) k out of wack

* fix rebase test and add more asserts

* format fix

* incorp basic example to typescript tests

* fix rebase math in position.rs (wip)

* wip continue

* working baseAssetAmountWithUnsettledLp

* mvp encapsulation of update_lp_market_position

* src/user.ts: add perLpBase to empty position

* remove logs

* simplify base unit logic more

* add get_per_lp_base_unit

* simplify per_lp_fee and add numbers to test

* properly set per_lp_base for lp_share=0 in mint_lp_shares (and add sdk/test)

* incorp feedback / format

* tests/liquidityProvider.ts: work with sdk change

* admin.rs: add constraint on per_lp_base range

* add apply_lp_rebase_to_perp_position to simulate_settled_lp_position

* use get_per_lp_base_unit consistently (wip sdk test for negative lp base)

* cut excess return values in calculate_lp_delta

* fix getPerpPositionWithLPSettle with negative perLpBase delta

---------

Co-authored-by: Chris Heaney <[email protected]>
  • Loading branch information
0xbigz and crispheaney committed Aug 22, 2023
1 parent 8ffa1c5 commit fc5aa58
Show file tree
Hide file tree
Showing 19 changed files with 982 additions and 106 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- program: add reduce only user status ([#560](https://github.com/drift-labs/protocol-v2/pull/560))
- program: add conditionally smaller conf_component logic for amm spread ([#577](https://github.com/drift-labs/protocol-v2/pull/577))
- program: add per_lp_base on market/position ([#568](https://github.com/drift-labs/protocol-v2/pull/568))

### Fixes

Expand All @@ -25,7 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- program: add deposit_into_spot_market_revenue_pool ([#520](https://github.com/drift-labs/protocol-v2/pull/520))
- program: make users w excessive withdraws pay fees ([#547](https://github.com/drift-labs/protocol-v2/pull/547))
- program: allow settle pnl and spot fills via match when utilization is 100% ([#525](https://github.com/drift-labs/protocol-v2/pull/525))
- program: allow settle pnl and spot fills via match when utilization is 100% ([#525](https://github.com/drift-labs/protocol-v2/pull/525))
- program: new update_perp_bid_ask_twap ix ([#548](https://github.com/drift-labs/protocol-v2/pull/548))
- program: dont check price bands for place order ([#556](https://github.com/drift-labs/protocol-v2/pull/556))

Expand Down
108 changes: 107 additions & 1 deletion programs/drift/src/controller/lp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,108 @@ use anchor_lang::prelude::Account;
#[cfg(test)]
mod tests;

pub fn apply_lp_rebase_to_perp_market(
perp_market: &mut PerpMarket,
expo_diff: i8,
) -> DriftResult<()> {
// target_base_asset_amount_per_lp is the only one that it doesnt get applied
// thus changing the base of lp and without changing target_base_asset_amount_per_lp
// causes an implied change

validate!(expo_diff != 0, ErrorCode::DefaultError, "expo_diff = 0")?;

perp_market.amm.per_lp_base = perp_market.amm.per_lp_base.safe_add(expo_diff)?;
let rebase_divisor: i128 = 10_i128.pow(expo_diff.abs().cast()?);

if expo_diff > 0 {
perp_market.amm.base_asset_amount_per_lp = perp_market
.amm
.base_asset_amount_per_lp
.safe_mul(rebase_divisor)?;

perp_market.amm.quote_asset_amount_per_lp = perp_market
.amm
.quote_asset_amount_per_lp
.safe_mul(rebase_divisor)?;

perp_market.amm.total_fee_earned_per_lp = perp_market
.amm
.total_fee_earned_per_lp
.safe_mul(rebase_divisor.cast()?)?;
} else {
perp_market.amm.base_asset_amount_per_lp = perp_market
.amm
.base_asset_amount_per_lp
.safe_div(rebase_divisor)?;

perp_market.amm.quote_asset_amount_per_lp = perp_market
.amm
.quote_asset_amount_per_lp
.safe_div(rebase_divisor)?;

perp_market.amm.total_fee_earned_per_lp = perp_market
.amm
.total_fee_earned_per_lp
.safe_div(rebase_divisor.cast()?)?;
}

msg!(
"rebasing perp market_index={} per_lp_base expo_diff={}",
perp_market.market_index,
expo_diff,
);

crate::validation::perp_market::validate_perp_market(perp_market)?;

Ok(())
}

pub fn apply_lp_rebase_to_perp_position(
perp_market: &PerpMarket,
perp_position: &mut PerpPosition,
) -> DriftResult<()> {
let expo_diff = perp_market
.amm
.per_lp_base
.safe_sub(perp_position.per_lp_base)?;

if expo_diff > 0 {
let rebase_divisor: i64 = 10_i64.pow(expo_diff.cast()?);

perp_position.last_base_asset_amount_per_lp = perp_position
.last_base_asset_amount_per_lp
.safe_mul(rebase_divisor)?;
perp_position.last_quote_asset_amount_per_lp = perp_position
.last_quote_asset_amount_per_lp
.safe_mul(rebase_divisor)?;

msg!(
"rebasing perp position for market_index={} per_lp_base by expo_diff={}",
perp_market.market_index,
expo_diff,
);
} else if expo_diff < 0 {
let rebase_divisor: i64 = 10_i64.pow(expo_diff.abs().cast()?);

perp_position.last_base_asset_amount_per_lp = perp_position
.last_base_asset_amount_per_lp
.safe_div(rebase_divisor)?;
perp_position.last_quote_asset_amount_per_lp = perp_position
.last_quote_asset_amount_per_lp
.safe_div(rebase_divisor)?;

msg!(
"rebasing perp position for market_index={} per_lp_base by expo_diff={}",
perp_market.market_index,
expo_diff,
);
}

perp_position.per_lp_base = perp_position.per_lp_base.safe_add(expo_diff)?;

Ok(())
}

pub fn mint_lp_shares(
position: &mut PerpPosition,
market: &mut PerpMarket,
Expand All @@ -40,6 +142,7 @@ pub fn mint_lp_shares(
} else {
position.last_base_asset_amount_per_lp = amm.base_asset_amount_per_lp.cast()?;
position.last_quote_asset_amount_per_lp = amm.quote_asset_amount_per_lp.cast()?;
position.per_lp_base = amm.per_lp_base;
}

// add share balance
Expand Down Expand Up @@ -78,6 +181,8 @@ pub fn settle_lp_position(
)?;
}

apply_lp_rebase_to_perp_position(market, position)?;

let mut lp_metrics: crate::math::lp::LPMetrics =
calculate_settle_lp_metrics(&market.amm, position)?;

Expand Down Expand Up @@ -184,9 +289,10 @@ pub fn burn_lp_shares(
crate::validate!(
unsettled_remainder.unsigned_abs() <= market.amm.order_step_size as u128,
ErrorCode::UnableToBurnLPTokens,
"unsettled baa on final burn too big rel to stepsize {}: {}",
"unsettled baa on final burn too big rel to stepsize {}: {} (remainder:{})",
market.amm.order_step_size,
market.amm.base_asset_amount_with_unsettled_lp,
position.remainder_base_asset_amount
)?;

// sub bc lps take the opposite side of the user
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 @@ -709,6 +709,8 @@ fn test_lp_margin_calc() {
market.amm.cumulative_funding_rate_long *= 2;
market.amm.cumulative_funding_rate_short *= 2;

apply_lp_rebase_to_perp_market(&mut market, 1).unwrap();

let sim_user_pos = user.perp_positions[0]
.simulate_settled_lp_position(&market, oracle_price_data.price)
.unwrap();
Expand All @@ -718,7 +720,7 @@ fn test_lp_margin_calc() {
);
assert_eq!(sim_user_pos.base_asset_amount, 101000000000);
assert_eq!(sim_user_pos.quote_asset_amount, -20000000000);
assert_eq!(sim_user_pos.last_cumulative_funding_rate, 0);
assert_eq!(sim_user_pos.last_cumulative_funding_rate, 16900000000);

// ensure margin calc doesnt incorrectly count funding rate (funding pnl MUST come before settling lp)
let (margin_requirement, weighted_unrealized_pnl, worse_case_base_asset_value) =
Expand Down
70 changes: 14 additions & 56 deletions programs/drift/src/controller/position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@ use crate::controller;
use crate::controller::amm::SwapDirection;
use crate::error::{DriftResult, ErrorCode};
use crate::math::casting::Cast;
use crate::math::constants::{
AMM_RESERVE_PRECISION, AMM_RESERVE_PRECISION_I128, LP_FEE_SLICE_DENOMINATOR,
LP_FEE_SLICE_NUMERATOR, MAX_BASE_ASSET_AMOUNT_WITH_AMM, PERP_DECIMALS,
};
use crate::math::helpers::get_proportion_i128;
use crate::math::constants::{MAX_BASE_ASSET_AMOUNT_WITH_AMM, PERP_DECIMALS};
// use crate::math::helpers::get_proportion_i128;
use crate::math::orders::{
calculate_quote_asset_amount_for_maker_order, get_position_delta_for_fill,
is_multiple_of_step_size,
Expand Down Expand Up @@ -383,42 +380,21 @@ pub fn update_lp_market_position(
delta: &PositionDelta,
fee_to_market: i128,
liquidity_split: AMMLiquiditySplit,
) -> DriftResult<(i128, i128, i128)> {
let user_lp_shares = market.amm.user_lp_shares;

if user_lp_shares == 0 || liquidity_split == AMMLiquiditySplit::ProtocolOwned {
return Ok((0, 0, 0)); // no need to split with LP
) -> DriftResult<i128> {
if market.amm.user_lp_shares == 0 || liquidity_split == AMMLiquiditySplit::ProtocolOwned {
return Ok(0); // no need to split with LP
}

let total_lp_shares = if liquidity_split == AMMLiquiditySplit::LPOwned {
market.amm.user_lp_shares
} else {
market.amm.sqrt_k
};

// update Market per lp position
let per_lp_delta_base = get_proportion_i128(
delta.base_asset_amount.cast()?,
AMM_RESERVE_PRECISION,
total_lp_shares,
)?;
let base_unit: i128 = market.amm.get_per_lp_base_unit()?;

let mut per_lp_delta_quote = get_proportion_i128(
delta.quote_asset_amount.cast()?,
AMM_RESERVE_PRECISION,
total_lp_shares,
)?;
let (per_lp_delta_base, per_lp_delta_quote, per_lp_fee) =
market
.amm
.calculate_per_lp_delta(delta, fee_to_market, liquidity_split, base_unit)?;

// user position delta is short => lp position delta is long
if per_lp_delta_base < 0 {
// add one => lp subtract 1
per_lp_delta_quote = per_lp_delta_quote.safe_add(1)?;
}

let lp_delta_base =
get_proportion_i128(per_lp_delta_base, user_lp_shares, AMM_RESERVE_PRECISION)?;
let lp_delta_quote =
get_proportion_i128(per_lp_delta_quote, user_lp_shares, AMM_RESERVE_PRECISION)?;
let lp_delta_base = market
.amm
.calculate_lp_base_delta(per_lp_delta_base, base_unit)?;

market.amm.base_asset_amount_per_lp = market
.amm
Expand All @@ -430,24 +406,6 @@ pub fn update_lp_market_position(
.quote_asset_amount_per_lp
.safe_add(-per_lp_delta_quote)?;

// 1/5 of fee auto goes to market
// the rest goes to lps/market proportional
let lp_fee = get_proportion_i128(
fee_to_market,
LP_FEE_SLICE_NUMERATOR,
LP_FEE_SLICE_DENOMINATOR,
)?
.safe_mul(user_lp_shares.cast::<i128>()?)?
.safe_div(total_lp_shares.cast::<i128>()?)?;

let per_lp_fee: i128 = if lp_fee > 0 {
lp_fee
.safe_mul(AMM_RESERVE_PRECISION_I128)?
.safe_div(user_lp_shares.cast::<i128>()?)?
} else {
0
};

// track total fee earned by lps (to attribute breakdown of IL)
market.amm.total_fee_earned_per_lp = market
.amm
Expand All @@ -468,7 +426,7 @@ pub fn update_lp_market_position(
.base_asset_amount_with_unsettled_lp
.safe_add(lp_delta_base)?;

Ok((lp_delta_base, lp_delta_quote, lp_fee))
Ok(lp_delta_base)
}

pub fn update_position_with_base_asset_amount(
Expand Down
Loading

0 comments on commit fc5aa58

Please sign in to comment.