Skip to content

Commit

Permalink
bigz/amm-fee-pool-insurance (#332)
Browse files Browse the repository at this point in the history
* bigz/amm-fee-pool-insurance

* dead import / sign cleanup on losses_remaining etc

* sdk: calculateEstimatedPerpEntryPrice use getLimitBids/getLimitAsks

* program: fix paying fee_pool_delta when filling with open book

* program: better rounding for openbook limit price

* sdk: return worst and best price from calculateEstimatedPerpEntryPrice

* sdk: array of users to skip in calculateEstimatedPerpEntryPrice

* sdk: calculateEstimatedPerpEntryPrice accounts for limited vamm liquidity

* sdk: tweak available liquidity calc in calculateEstimatedPerpEntryPrice

* sdk: add calculateEstimatedSpotEntryPrice

* program: only let market orders match against resting limit orders (#328)

* program: add is_resting_limit_order

* programs: change is_maker_for_taker to account for resting limit orders

* tests working

* refine logic to let limit order immediately be maker if it is inside the vamm spread

* sdk: mirror new on-chain logic around resting limit orders

* add test for new amm bid/ask logic

* sdk: calculateEstimatedSpotEntryPrice uses getMakerLimitBids/Asks

* CHANGELOG

* program: allow for 2000 users

* program: add additional logging for casting error in calculate_fee_for_fulfillment_with_amm

* v2.12.0

* sdk: add loadDlob example

* tests: fix multipleMakerOrders.ts

* more progress on cargo test (wip)

* working cargo test

* tests/delistMarketLiq.ts: fix for fee pool as insurance

* fix tests/liquidatePerp.ts

* fix tests/liquidatePerpAndLp.ts

* fix tests/liquidatePerpAndLp.ts cumulativeFundingRateDelta

---------

Co-authored-by: Chris Heaney <[email protected]>
  • Loading branch information
0xbigz and crispheaney committed Jan 29, 2023
1 parent b7e2580 commit 9a1a830
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 15 deletions.
44 changes: 43 additions & 1 deletion programs/drift/src/controller/liquidation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::ops::{Deref, DerefMut};
use anchor_lang::prelude::*;
use solana_program::msg;

use crate::controller::amm::get_fee_pool_tokens;
use crate::controller::funding::settle_funding_payment;
use crate::controller::lp::burn_lp_shares;
use crate::controller::orders;
Expand Down Expand Up @@ -2081,7 +2082,48 @@ pub fn resolve_perp_bankruptcy(
if_payment
};

let loss_to_socialize = loss.safe_add(if_payment.cast::<i128>()?)?;
let losses_remaining: i128 = loss.safe_add(if_payment.cast::<i128>()?)?;
validate!(
losses_remaining <= 0,
ErrorCode::InvalidPerpPositionToLiquidate,
"losses_remaining must be non-positive"
)?;

let fee_pool_payment: i128 = if losses_remaining < 0 {
let perp_market = &mut perp_market_map.get_ref_mut(&market_index)?;
let spot_market = &mut spot_market_map.get_ref_mut(&QUOTE_SPOT_MARKET_INDEX)?;
let fee_pool_tokens = get_fee_pool_tokens(perp_market, spot_market)?;
msg!("fee_pool_tokens={:?}", fee_pool_tokens);

losses_remaining.abs().min(fee_pool_tokens.cast()?)
} else {
0
};
validate!(
fee_pool_payment >= 0,
ErrorCode::InvalidPerpPositionToLiquidate,
"fee_pool_payment must be non-negative"
)?;

if fee_pool_payment > 0 {
let perp_market = &mut perp_market_map.get_ref_mut(&market_index)?;
let spot_market = &mut spot_market_map.get_ref_mut(&QUOTE_SPOT_MARKET_INDEX)?;
msg!("fee_pool_payment={:?}", fee_pool_payment);
update_spot_balances(
fee_pool_payment.unsigned_abs(),
&SpotBalanceType::Borrow,
spot_market,
&mut perp_market.amm.fee_pool,
false,
)?;
}

let loss_to_socialize = losses_remaining.safe_add(fee_pool_payment.cast::<i128>()?)?;
validate!(
loss_to_socialize <= 0,
ErrorCode::InvalidPerpPositionToLiquidate,
"loss_to_socialize must be non-positive"
)?;

let cumulative_funding_rate_delta = calculate_funding_rate_deltas_to_resolve_bankruptcy(
loss_to_socialize,
Expand Down
220 changes: 217 additions & 3 deletions programs/drift/src/controller/liquidation/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5171,12 +5171,13 @@ pub mod resolve_perp_bankruptcy {
use crate::math::constants::{
AMM_RESERVE_PRECISION, BASE_PRECISION_I128, BASE_PRECISION_I64, BASE_PRECISION_U64,
FUNDING_RATE_PRECISION_I128, FUNDING_RATE_PRECISION_I64, LIQUIDATION_FEE_PRECISION,
PEG_PRECISION, QUOTE_PRECISION_I128, QUOTE_PRECISION_I64, SPOT_BALANCE_PRECISION_U64,
SPOT_CUMULATIVE_INTEREST_PRECISION, SPOT_WEIGHT_PRECISION,
PEG_PRECISION, QUOTE_PRECISION_I128, QUOTE_PRECISION_I64, QUOTE_SPOT_MARKET_INDEX,
SPOT_BALANCE_PRECISION, SPOT_BALANCE_PRECISION_U64, SPOT_CUMULATIVE_INTEREST_PRECISION,
SPOT_WEIGHT_PRECISION,
};
use crate::state::oracle::OracleSource;
use crate::state::oracle_map::OracleMap;
use crate::state::perp_market::{MarketStatus, PerpMarket, AMM};
use crate::state::perp_market::{MarketStatus, PerpMarket, PoolBalance, AMM};
use crate::state::perp_market_map::PerpMarketMap;
use crate::state::spot_market::{SpotBalanceType, SpotMarket};
use crate::state::spot_market_map::SpotMarketMap;
Expand Down Expand Up @@ -5390,6 +5391,219 @@ pub mod resolve_perp_bankruptcy {

assert_eq!(expected_affected_short_user, affected_short_user);
}

#[test]
pub fn successful_resolve_perp_bankruptcy_with_fee_pool() {
let now = 0_i64;
let slot = 0_u64;

let mut oracle_price = get_pyth_price(100, 6);
let oracle_price_key =
Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap();
let pyth_program = crate::ids::pyth_program::id();
create_account_info!(
oracle_price,
&oracle_price_key,
&pyth_program,
oracle_account_info
);
let mut oracle_map = OracleMap::load_one(&oracle_account_info, slot, None).unwrap();

let mut market = PerpMarket {
amm: AMM {
base_asset_reserve: 100 * AMM_RESERVE_PRECISION,
quote_asset_reserve: 100 * AMM_RESERVE_PRECISION,
bid_base_asset_reserve: 101 * AMM_RESERVE_PRECISION,
bid_quote_asset_reserve: 99 * AMM_RESERVE_PRECISION,
ask_base_asset_reserve: 99 * AMM_RESERVE_PRECISION,
ask_quote_asset_reserve: 101 * AMM_RESERVE_PRECISION,
sqrt_k: 100 * AMM_RESERVE_PRECISION,
peg_multiplier: 100 * PEG_PRECISION,
max_slippage_ratio: 50,
max_fill_reserve_fraction: 100,
order_step_size: 10000000,
quote_asset_amount: -150 * QUOTE_PRECISION_I128,
base_asset_amount_long: 5 * BASE_PRECISION_I128,
base_asset_amount_short: -5 * BASE_PRECISION_I128,
base_asset_amount_with_amm: BASE_PRECISION_I128,
oracle: oracle_price_key,
cumulative_funding_rate_long: 1000 * FUNDING_RATE_PRECISION_I128,
cumulative_funding_rate_short: -1000 * FUNDING_RATE_PRECISION_I128,
fee_pool: PoolBalance {
scaled_balance: 50 * SPOT_BALANCE_PRECISION,
market_index: QUOTE_SPOT_MARKET_INDEX,
..PoolBalance::default()
},
..AMM::default()
},
margin_ratio_initial: 1000,
margin_ratio_maintenance: 500,
status: MarketStatus::Initialized,
liquidator_fee: LIQUIDATION_FEE_PRECISION / 100,
number_of_users: 1,
..PerpMarket::default()
};
create_anchor_account_info!(market, PerpMarket, market_account_info);
let market_map = PerpMarketMap::load_one(&market_account_info, true).unwrap();

let mut spot_market = SpotMarket {
market_index: 0,
deposit_balance: 500 * SPOT_BALANCE_PRECISION,
oracle_source: OracleSource::QuoteAsset,
cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION,
decimals: 6,
initial_asset_weight: SPOT_WEIGHT_PRECISION,
..SpotMarket::default()
};
create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info);
let spot_market_map = SpotMarketMap::load_one(&spot_market_account_info, true).unwrap();

let mut user = User {
orders: get_orders(Order {
market_index: 0,
status: OrderStatus::Open,
order_type: OrderType::Limit,
direction: PositionDirection::Long,
base_asset_amount: BASE_PRECISION_U64,
slot: 0,
..Order::default()
}),
perp_positions: get_positions(PerpPosition {
market_index: 0,
base_asset_amount: 0,
quote_asset_amount: -100 * QUOTE_PRECISION_I64,
quote_entry_amount: -100 * QUOTE_PRECISION_I64,
quote_break_even_amount: -100 * QUOTE_PRECISION_I64,
open_orders: 1,
open_bids: BASE_PRECISION_I64,
..PerpPosition::default()
}),
spot_positions: [SpotPosition::default(); 8],
status: UserStatus::Bankrupt,
next_liquidation_id: 2,
..User::default()
};

let mut liquidator = User {
spot_positions: get_spot_positions(SpotPosition {
market_index: 0,
balance_type: SpotBalanceType::Deposit,
scaled_balance: 50 * SPOT_BALANCE_PRECISION_U64,
..SpotPosition::default()
}),
..User::default()
};

let user_key = Pubkey::default();
let liquidator_key = Pubkey::default();

let mut expected_user = user;
expected_user.status = UserStatus::Active;
expected_user.perp_positions[0].quote_asset_amount = 0;
expected_user.total_social_loss = 100000000;

let mut expected_market = market;
expected_market.amm.cumulative_funding_rate_long = 1005 * FUNDING_RATE_PRECISION_I128;
expected_market.amm.cumulative_funding_rate_short = -1005 * FUNDING_RATE_PRECISION_I128;
expected_market.amm.total_social_loss = 50000000;
expected_market.amm.quote_asset_amount = -50 * QUOTE_PRECISION_I128;
expected_market.number_of_users = 0;
expected_market.amm.fee_pool.scaled_balance = 0;

resolve_perp_bankruptcy(
0,
&mut user,
&user_key,
&mut liquidator,
&liquidator_key,
&market_map,
&spot_market_map,
&mut oracle_map,
now,
0,
)
.unwrap();

assert_eq!(user.total_social_loss, 100000000);
assert_eq!(expected_user, user);
assert_eq!(expected_market, market_map.get_ref(&0).unwrap().clone());

let mut affected_long_user = User {
orders: [Order::default(); 32],
perp_positions: get_positions(PerpPosition {
market_index: 0,
base_asset_amount: 5 * BASE_PRECISION_I64,
quote_asset_amount: -500 * QUOTE_PRECISION_I64,
quote_break_even_amount: -500 * QUOTE_PRECISION_I64,
quote_entry_amount: -500 * QUOTE_PRECISION_I64,
open_bids: BASE_PRECISION_I64,
last_cumulative_funding_rate: 1000 * FUNDING_RATE_PRECISION_I64,
..PerpPosition::default()
}),
spot_positions: [SpotPosition::default(); 8],
..User::default()
};

let mut expected_affected_long_user = affected_long_user;
expected_affected_long_user.perp_positions[0].quote_asset_amount =
-525 * QUOTE_PRECISION_I64; // loses $50
expected_affected_long_user.perp_positions[0].quote_break_even_amount =
-525 * QUOTE_PRECISION_I64; // loses $50
expected_affected_long_user.perp_positions[0].last_cumulative_funding_rate =
1005 * FUNDING_RATE_PRECISION_I64;
expected_affected_long_user.cumulative_perp_funding = -25 * QUOTE_PRECISION_I64;

{
let mut market = market_map.get_ref_mut(&0).unwrap();
settle_funding_payment(
&mut affected_long_user,
&Pubkey::default(),
&mut market,
now,
)
.unwrap()
}

assert_eq!(expected_affected_long_user, affected_long_user);

let mut affected_short_user = User {
orders: [Order::default(); 32],
perp_positions: get_positions(PerpPosition {
market_index: 0,
base_asset_amount: -5 * BASE_PRECISION_I64,
quote_asset_amount: 500 * QUOTE_PRECISION_I64,
quote_entry_amount: 500 * QUOTE_PRECISION_I64,
quote_break_even_amount: 500 * QUOTE_PRECISION_I64,
open_bids: BASE_PRECISION_I64,
last_cumulative_funding_rate: -1000 * FUNDING_RATE_PRECISION_I64,
..PerpPosition::default()
}),
spot_positions: [SpotPosition::default(); 8],
..User::default()
};

let mut expected_affected_short_user = affected_short_user;
expected_affected_short_user.perp_positions[0].quote_asset_amount =
475 * QUOTE_PRECISION_I64; // loses $50
expected_affected_short_user.perp_positions[0].quote_break_even_amount =
475 * QUOTE_PRECISION_I64; // loses $50
expected_affected_short_user.perp_positions[0].last_cumulative_funding_rate =
-1005 * FUNDING_RATE_PRECISION_I64;
expected_affected_short_user.cumulative_perp_funding = -25 * QUOTE_PRECISION_I64;

{
let mut market = market_map.get_ref_mut(&0).unwrap();
settle_funding_payment(
&mut affected_short_user,
&Pubkey::default(),
&mut market,
now,
)
.unwrap()
}

assert_eq!(expected_affected_short_user, affected_short_user);
}
}

pub mod resolve_spot_bankruptcy {
Expand Down
7 changes: 4 additions & 3 deletions tests/delistMarketLiq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -735,8 +735,8 @@ describe('delist market, liquidation of expired position', () => {
assert(marketAfter0.numberOfUsersWithBase === 0);

// old 1415296436
const finalPnlResultMin0 = new BN(2270702294000 - 11090000);
const finalPnlResultMax0 = new BN(2270702294000 + 11109000);
const finalPnlResultMin0 = new BN(2266346249000 - 11090000);
const finalPnlResultMax0 = new BN(2266346249000 + 11109000);

console.log(marketAfter0.pnlPool.scaledBalance.toString());
assert(marketAfter0.pnlPool.scaledBalance.gt(finalPnlResultMin0));
Expand All @@ -748,7 +748,8 @@ describe('delist market, liquidation of expired position', () => {
'totalExchangeFee:',
marketAfter0.amm.totalExchangeFee.toString()
);
assert(marketAfter0.amm.feePool.scaledBalance.eq(new BN(4356250000)));
assert(marketAfter0.amm.feePool.scaledBalance.eq(ZERO));
assert(marketAfter0.amm.totalExchangeFee.eq(new BN(8712501)));
await liquidatorDriftClientUser.unsubscribe();
});
});
12 changes: 8 additions & 4 deletions tests/liquidatePerp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,11 @@ describe('liquidate perp (no open orders)', () => {
assert(
marketAfterBankruptcy.insuranceClaim.quoteMaxInsurance.eq(QUOTE_PRECISION)
);
assert(marketAfterBankruptcy.amm.totalSocialLoss.eq(new BN(5785008)));
console.log(
'marketAfterBankruptcy.amm.totalSocialLoss:',
marketAfterBankruptcy.amm.totalSocialLoss.toString()
);
assert(marketAfterBankruptcy.amm.totalSocialLoss.eq(new BN(5776257)));

// assert(!driftClient.getUserAccount().isBankrupt);
// assert(!driftClient.getUserAccount().isBeingLiquidated);
Expand All @@ -474,7 +478,7 @@ describe('liquidate perp (no open orders)', () => {
);
assert(
perpBankruptcyRecord.perpBankruptcy.cumulativeFundingRateDelta.eq(
new BN(330572000)
new BN(330072000)
)
);

Expand All @@ -483,7 +487,7 @@ describe('liquidate perp (no open orders)', () => {
market.amm.cumulativeFundingRateLong.toString(),
market.amm.cumulativeFundingRateShort.toString()
);
assert(market.amm.cumulativeFundingRateLong.eq(new BN(330572000)));
assert(market.amm.cumulativeFundingRateShort.eq(new BN(-330572000)));
assert(market.amm.cumulativeFundingRateLong.eq(new BN(330072000)));
assert(market.amm.cumulativeFundingRateShort.eq(new BN(-330072000)));
});
});
12 changes: 8 additions & 4 deletions tests/liquidatePerpAndLp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,11 @@ describe('liquidate perp and lp', () => {
assert(
marketAfterBankruptcy.insuranceClaim.quoteMaxInsurance.eq(QUOTE_PRECISION)
);
assert(marketAfterBankruptcy.amm.totalSocialLoss.eq(new BN(5785008)));
console.log(
'marketAfterBankruptcy.amm.totalSocialLoss:',
marketAfterBankruptcy.amm.totalSocialLoss.toString()
);
assert(marketAfterBankruptcy.amm.totalSocialLoss.eq(new BN(5776257)));

// assert(!driftClient.getUserAccount().isBankrupt);
// assert(!driftClient.getUserAccount().isBeingLiquidated);
Expand All @@ -479,7 +483,7 @@ describe('liquidate perp and lp', () => {
);
assert(
perpBankruptcyRecord.perpBankruptcy.cumulativeFundingRateDelta.eq(
new BN(330572000)
new BN(330072000)
)
);

Expand All @@ -488,7 +492,7 @@ describe('liquidate perp and lp', () => {
market.amm.cumulativeFundingRateLong.toString(),
market.amm.cumulativeFundingRateShort.toString()
);
assert(market.amm.cumulativeFundingRateLong.eq(new BN(330572000)));
assert(market.amm.cumulativeFundingRateShort.eq(new BN(-330572000)));
assert(market.amm.cumulativeFundingRateLong.eq(new BN(330072000)));
assert(market.amm.cumulativeFundingRateShort.eq(new BN(-330072000)));
});
});

0 comments on commit 9a1a830

Please sign in to comment.