Perps: Support trusted markets
This commit is contained in:
parent
8ba52f46c2
commit
7e180c7b3a
|
@ -66,7 +66,7 @@ pub fn fetch_top(
|
|||
}
|
||||
}
|
||||
|
||||
// Negative pnl needs to be limited by spot_health.
|
||||
// Negative pnl needs to be limited by perp_settle_health.
|
||||
// We're doing it in a second step, because it's pretty expensive and we don't
|
||||
// want to run this for all accounts.
|
||||
if direction == Direction::MaxNegative {
|
||||
|
@ -78,10 +78,10 @@ pub fn fetch_top(
|
|||
} else {
|
||||
I80F48::ZERO
|
||||
};
|
||||
let spot_health = crate::health_cache::new(context, account_fetcher, &acc)?
|
||||
.spot_health(HealthType::Maint);
|
||||
let settleable_pnl = if spot_health > 0 && !acc.being_liquidated() {
|
||||
(*pnl).max(-spot_health)
|
||||
let perp_settle_health =
|
||||
crate::health_cache::new(context, account_fetcher, &acc)?.perp_settle_health();
|
||||
let settleable_pnl = if perp_settle_health > 0 && !acc.being_liquidated() {
|
||||
(*pnl).max(-perp_settle_health)
|
||||
} else {
|
||||
I80F48::ZERO
|
||||
};
|
||||
|
|
|
@ -226,7 +226,7 @@ impl<'a> LiquidateHelper<'a> {
|
|||
}
|
||||
|
||||
fn perp_settle_pnl(&self) -> anyhow::Result<Option<Signature>> {
|
||||
let spot_health = self.health_cache.spot_health(HealthType::Maint);
|
||||
let perp_settle_health = self.health_cache.perp_settle_health();
|
||||
let mut perp_settleable_pnl = self
|
||||
.liqee
|
||||
.active_perp_positions()
|
||||
|
@ -237,8 +237,8 @@ impl<'a> LiquidateHelper<'a> {
|
|||
let pnl = pp.quote_position_native();
|
||||
let settleable_pnl = if pnl > 0 {
|
||||
pnl
|
||||
} else if pnl < 0 && spot_health > 0 {
|
||||
pnl.max(-spot_health)
|
||||
} else if pnl < 0 && perp_settle_health > 0 {
|
||||
pnl.max(-perp_settle_health)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
|
|
@ -66,13 +66,12 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
|
|||
|
||||
let a_init_health;
|
||||
let a_maint_health;
|
||||
let b_spot_health;
|
||||
let b_settle_health;
|
||||
{
|
||||
let retriever =
|
||||
ScanningAccountRetriever::new(ctx.remaining_accounts, &ctx.accounts.group.key())
|
||||
.context("create account retriever")?;
|
||||
b_spot_health =
|
||||
new_health_cache(&account_b.borrow(), &retriever)?.spot_health(HealthType::Maint);
|
||||
b_settle_health = new_health_cache(&account_b.borrow(), &retriever)?.perp_settle_health();
|
||||
let a_cache = new_health_cache(&account_a.borrow(), &retriever)?;
|
||||
a_init_health = a_cache.health(HealthType::Init);
|
||||
a_maint_health = a_cache.health(HealthType::Maint);
|
||||
|
@ -83,7 +82,7 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
|
|||
// Example: With +100 USDC and -2 SOL (-80 USD) and -500 USD PNL the account may still settle
|
||||
// 100 - 1.1*80 = 12 USD perp pnl, even though the overall health is already negative.
|
||||
// Further settlement would convert perp-losses into token-losses and isn't allowed.
|
||||
require!(b_spot_health >= 0, MangoError::HealthMustBePositive);
|
||||
require!(b_settle_health >= 0, MangoError::HealthMustBePositive);
|
||||
|
||||
let mut bank = ctx.accounts.quote_bank.load_mut()?;
|
||||
let perp_market = ctx.accounts.perp_market.load()?;
|
||||
|
@ -117,8 +116,8 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
|
|||
require!(a_pnl.is_positive(), MangoError::ProfitabilityMismatch);
|
||||
require!(b_pnl.is_negative(), MangoError::ProfitabilityMismatch);
|
||||
|
||||
// Settle for the maximum possible capped to b's spot health
|
||||
let settlement = a_pnl.abs().min(b_pnl.abs()).min(b_spot_health);
|
||||
// Settle for the maximum possible capped to b's settle health
|
||||
let settlement = a_pnl.abs().min(b_pnl.abs()).min(b_settle_health);
|
||||
a_perp_position.change_quote_position(-settlement);
|
||||
b_perp_position.change_quote_position(settlement);
|
||||
|
||||
|
|
|
@ -526,6 +526,7 @@ pub struct PerpInfo {
|
|||
pub quote: I80F48,
|
||||
pub oracle_price: I80F48,
|
||||
pub has_open_orders: bool,
|
||||
pub trusted_market: bool,
|
||||
}
|
||||
|
||||
impl PerpInfo {
|
||||
|
@ -612,13 +613,14 @@ impl PerpInfo {
|
|||
quote,
|
||||
oracle_price,
|
||||
has_open_orders: perp_position.has_open_orders(),
|
||||
trusted_market: perp_market.trusted_market(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Total health contribution from perp balances
|
||||
///
|
||||
/// Due to isolation of perp markets, users may never borrow against perp
|
||||
/// positions without settling first: perp health is capped at zero.
|
||||
/// positions in untrusted without settling first: perp health is capped at zero.
|
||||
///
|
||||
/// Users need to settle their perp pnl with other perp market participants
|
||||
/// in order to realize their gains if they want to use them as collateral.
|
||||
|
@ -631,9 +633,11 @@ impl PerpInfo {
|
|||
fn health_contribution(&self, health_type: HealthType) -> I80F48 {
|
||||
let c = self.uncapped_health_contribution(health_type);
|
||||
|
||||
// FUTURE: Allow v3-style "reliable" markets where we can return
|
||||
// `self.quote + weight * self.base` here
|
||||
c.min(I80F48::ZERO)
|
||||
if self.trusted_market {
|
||||
c
|
||||
} else {
|
||||
c.min(I80F48::ZERO)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -837,7 +841,18 @@ impl HealthCache {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn spot_health(&self, health_type: HealthType) -> I80F48 {
|
||||
/// Compute the health when it comes to settling perp pnl
|
||||
///
|
||||
/// Examples:
|
||||
/// - An account may have maint_health < 0, but settling perp pnl could still be allowed.
|
||||
/// (+100 USDC health, -50 USDT health, -50 perp health -> allow settling 50 health worth)
|
||||
/// - Positive health from trusted pnl markets counts
|
||||
/// - If overall health is 0 with two trusted perp pnl < 0, settling may still be possible.
|
||||
/// (+100 USDC health, -150 perp1 health, -150 perp2 health -> allow settling 100 health worth)
|
||||
/// - Positive trusted perp pnl can enable settling.
|
||||
/// (+100 trusted perp1 health, -100 perp2 health -> allow settling of 100 health worth)
|
||||
pub fn perp_settle_health(&self) -> I80F48 {
|
||||
let health_type = HealthType::Maint;
|
||||
let mut health = I80F48::ZERO;
|
||||
for token_info in self.token_infos.iter() {
|
||||
let contrib = token_info.health_contribution(health_type);
|
||||
|
@ -847,6 +862,12 @@ impl HealthCache {
|
|||
let contrib = serum3_info.health_contribution(health_type, &self.token_infos);
|
||||
cm!(health += contrib);
|
||||
}
|
||||
for perp_info in self.perp_infos.iter() {
|
||||
if perp_info.trusted_market {
|
||||
let positive_contrib = perp_info.health_contribution(health_type).max(I80F48::ZERO);
|
||||
cm!(health += positive_contrib);
|
||||
}
|
||||
}
|
||||
health
|
||||
}
|
||||
|
||||
|
@ -1988,6 +2009,7 @@ mod tests {
|
|||
quote: I80F48::ZERO,
|
||||
oracle_price: I80F48::from_num(2.0),
|
||||
has_open_orders: false,
|
||||
trusted_market: false,
|
||||
};
|
||||
|
||||
let health_cache = HealthCache {
|
||||
|
|
|
@ -130,6 +130,10 @@ impl PerpMarket {
|
|||
self.group_insurance_fund = if v { 1 } else { 0 };
|
||||
}
|
||||
|
||||
pub fn trusted_market(&self) -> bool {
|
||||
self.trusted_market == 1
|
||||
}
|
||||
|
||||
pub fn gen_order_id(&mut self, side: Side, price: i64) -> i128 {
|
||||
self.seq_num += 1;
|
||||
|
||||
|
|
|
@ -526,8 +526,8 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
let liqee_spot_health_before = 1000.0 + 1.0 * 2.0 * 0.8;
|
||||
let remaining_pnl = 20.0 * 100.0 - liq_amount_2 + liqee_spot_health_before;
|
||||
let liqee_settle_health_before = 1000.0 + 1.0 * 2.0 * 0.8;
|
||||
let remaining_pnl = 20.0 * 100.0 - liq_amount_2 + liqee_settle_health_before;
|
||||
assert!(remaining_pnl < 0.0);
|
||||
let liqee_data = solana.get_account::<MangoAccount>(account_1).await;
|
||||
assert_eq!(liqee_data.perps[0].base_position_lots(), 0);
|
||||
|
|
|
@ -4462,6 +4462,10 @@ export type MangoV4 = {
|
|||
{
|
||||
"name": "hasOpenOrders",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "trustedMarket",
|
||||
"type": "bool"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -4544,7 +4548,7 @@ export type MangoV4 = {
|
|||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
8
|
||||
16
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -4556,11 +4560,11 @@ export type MangoV4 = {
|
|||
},
|
||||
{
|
||||
"name": "cumulativeDepositInterest",
|
||||
"type": "i64"
|
||||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "cumulativeBorrowInterest",
|
||||
"type": "i64"
|
||||
"type": "f32"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -6070,12 +6074,12 @@ export type MangoV4 = {
|
|||
},
|
||||
{
|
||||
"name": "cumulativeDepositInterest",
|
||||
"type": "i64",
|
||||
"type": "f32",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "cumulativeBorrowInterest",
|
||||
"type": "i64",
|
||||
"type": "f32",
|
||||
"index": false
|
||||
}
|
||||
]
|
||||
|
@ -10664,6 +10668,10 @@ export const IDL: MangoV4 = {
|
|||
{
|
||||
"name": "hasOpenOrders",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "trustedMarket",
|
||||
"type": "bool"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -10746,7 +10754,7 @@ export const IDL: MangoV4 = {
|
|||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
8
|
||||
16
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -10758,11 +10766,11 @@ export const IDL: MangoV4 = {
|
|||
},
|
||||
{
|
||||
"name": "cumulativeDepositInterest",
|
||||
"type": "i64"
|
||||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "cumulativeBorrowInterest",
|
||||
"type": "i64"
|
||||
"type": "f32"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -12272,12 +12280,12 @@ export const IDL: MangoV4 = {
|
|||
},
|
||||
{
|
||||
"name": "cumulativeDepositInterest",
|
||||
"type": "i64",
|
||||
"type": "f32",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "cumulativeBorrowInterest",
|
||||
"type": "i64",
|
||||
"type": "f32",
|
||||
"index": false
|
||||
}
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue