token_liq_bankruptcy: Use oracle for valuing insurance fund tokens (#503)
Previously a token from the insurance fund was valued at 1 USD. Now it uses the oracle associated with it (USDC oracle).
This commit is contained in:
parent
a7ee8fb2c0
commit
b6bfb01879
|
@ -47,7 +47,7 @@ pub fn perp_create_market(
|
|||
// - In perp bankruptcy: fix the assumption that the insurance fund has the same mint as
|
||||
// the settlement token.
|
||||
require_msg!(
|
||||
settle_token_index == QUOTE_TOKEN_INDEX,
|
||||
settle_token_index == PERP_SETTLE_TOKEN_INDEX,
|
||||
"settlement tokens != USDC are not fully implemented"
|
||||
);
|
||||
|
||||
|
|
|
@ -50,8 +50,11 @@ pub fn token_liq_bankruptcy(
|
|||
liqee_health_cache.require_after_phase2_liquidation()?;
|
||||
liqee.fixed.set_being_liquidated(true);
|
||||
|
||||
let liab_is_insurance_token = liab_token_index == INSURANCE_TOKEN_INDEX;
|
||||
let (liab_bank, liab_oracle_price, opt_quote_bank_and_price) =
|
||||
account_retriever.banks_mut_and_oracles(liab_token_index, QUOTE_TOKEN_INDEX)?;
|
||||
account_retriever.banks_mut_and_oracles(liab_token_index, INSURANCE_TOKEN_INDEX)?;
|
||||
assert!(liab_is_insurance_token == opt_quote_bank_and_price.is_none());
|
||||
|
||||
let mut liab_deposit_index = liab_bank.deposit_index;
|
||||
let liab_borrow_index = liab_bank.borrow_index;
|
||||
let (liqee_liab, liqee_raw_token_index) = liqee.token_position_mut(liab_token_index)?;
|
||||
|
@ -59,13 +62,14 @@ pub fn token_liq_bankruptcy(
|
|||
let mut remaining_liab_loss = -initial_liab_native;
|
||||
require_gt!(remaining_liab_loss, I80F48::ZERO);
|
||||
|
||||
// find insurance transfer amount
|
||||
let liab_fee_factor = if liab_token_index == QUOTE_TOKEN_INDEX {
|
||||
I80F48::ONE
|
||||
// We pay for the liab token in quote. Example: SOL is at $20 and USDC is at $2, then for a liab
|
||||
// of 3 SOL, we'd pay 3 * 20 / 2 * (1+fee) = 30 * (1+fee) USDC.
|
||||
let liab_to_quote_with_fee =
|
||||
if let Some((_quote_bank, quote_price)) = opt_quote_bank_and_price.as_ref() {
|
||||
liab_oracle_price * (I80F48::ONE + liab_bank.liquidation_fee) / quote_price
|
||||
} else {
|
||||
I80F48::ONE + liab_bank.liquidation_fee
|
||||
I80F48::ONE
|
||||
};
|
||||
let liab_price_adjusted = liab_oracle_price * liab_fee_factor;
|
||||
|
||||
let liab_transfer_unrounded = remaining_liab_loss.min(max_liab_transfer);
|
||||
|
||||
|
@ -75,7 +79,7 @@ pub fn token_liq_bankruptcy(
|
|||
0
|
||||
};
|
||||
|
||||
let insurance_transfer = (liab_transfer_unrounded * liab_price_adjusted)
|
||||
let insurance_transfer = (liab_transfer_unrounded * liab_to_quote_with_fee)
|
||||
.ceil()
|
||||
.to_num::<u64>()
|
||||
.min(insurance_vault_amount);
|
||||
|
@ -87,7 +91,7 @@ pub fn token_liq_bankruptcy(
|
|||
// AUDIT: v3 does this, but it seems bad, because it can make liab_transfer
|
||||
// exceed max_liab_transfer due to the ceil() above! Otoh, not doing it would allow
|
||||
// liquidators to exploit the insurance fund for 1 native token each call.
|
||||
let liab_transfer = insurance_transfer_i80f48 / liab_price_adjusted;
|
||||
let liab_transfer = insurance_transfer_i80f48 / liab_to_quote_with_fee;
|
||||
|
||||
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
|
||||
|
||||
|
@ -116,7 +120,7 @@ pub fn token_liq_bankruptcy(
|
|||
|
||||
// credit the liqor
|
||||
let (liqor_quote, liqor_quote_raw_token_index, _) =
|
||||
liqor.ensure_token_position(QUOTE_TOKEN_INDEX)?;
|
||||
liqor.ensure_token_position(INSURANCE_TOKEN_INDEX)?;
|
||||
let liqor_quote_active =
|
||||
quote_bank.deposit(liqor_quote, insurance_transfer_i80f48, now_ts)?;
|
||||
|
||||
|
@ -124,7 +128,7 @@ pub fn token_liq_bankruptcy(
|
|||
emit!(TokenBalanceLog {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
mango_account: ctx.accounts.liqor.key(),
|
||||
token_index: QUOTE_TOKEN_INDEX,
|
||||
token_index: INSURANCE_TOKEN_INDEX,
|
||||
indexed_position: liqor_quote.indexed_position.to_bits(),
|
||||
deposit_index: quote_deposit_index.to_bits(),
|
||||
borrow_index: quote_borrow_index.to_bits(),
|
||||
|
@ -180,12 +184,12 @@ pub fn token_liq_bankruptcy(
|
|||
);
|
||||
}
|
||||
} else {
|
||||
// For liab_token_index == QUOTE_TOKEN_INDEX: the insurance fund deposits directly into liqee,
|
||||
// For liab_token_index == INSURANCE_TOKEN_INDEX: the insurance fund deposits directly into liqee,
|
||||
// without a fee or the liqor being involved
|
||||
// account constraint #2 b)
|
||||
require_keys_eq!(liab_bank.vault, ctx.accounts.quote_vault.key());
|
||||
require_eq!(liab_token_index, QUOTE_TOKEN_INDEX);
|
||||
require_eq!(liab_price_adjusted, I80F48::ONE);
|
||||
require_eq!(liab_token_index, INSURANCE_TOKEN_INDEX);
|
||||
require_eq!(liab_to_quote_with_fee, I80F48::ONE);
|
||||
require_eq!(insurance_transfer_i80f48, liab_transfer);
|
||||
}
|
||||
}
|
||||
|
@ -265,7 +269,7 @@ pub fn token_liq_bankruptcy(
|
|||
liab_token_index,
|
||||
initial_liab_native: initial_liab_native.to_bits(),
|
||||
liab_price: liab_oracle_price.to_bits(),
|
||||
insurance_token_index: QUOTE_TOKEN_INDEX,
|
||||
insurance_token_index: INSURANCE_TOKEN_INDEX,
|
||||
insurance_transfer: insurance_transfer_i80f48.to_bits(),
|
||||
socialized_loss: socialized_loss.to_bits(),
|
||||
starting_liab_deposit_index: starting_deposit_index.to_bits(),
|
||||
|
|
|
@ -31,7 +31,7 @@ pub fn token_register(
|
|||
net_borrow_limit_per_window_quote: i64,
|
||||
) -> Result<()> {
|
||||
// Require token 0 to be in the insurance token
|
||||
if token_index == QUOTE_TOKEN_INDEX {
|
||||
if token_index == INSURANCE_TOKEN_INDEX {
|
||||
require_keys_eq!(
|
||||
ctx.accounts.group.load()?.insurance_mint,
|
||||
ctx.accounts.mint.key()
|
||||
|
|
|
@ -4,8 +4,25 @@ use std::mem::size_of;
|
|||
|
||||
// TODO: Assuming we allow up to 65536 different tokens
|
||||
pub type TokenIndex = u16;
|
||||
|
||||
/// This token index is supposed to be the token that oracles quote in.
|
||||
///
|
||||
/// In practice this is set to the USDC token index, and that is wrong: actually
|
||||
/// oracles quote in USD. Any use of this constant points to a potentially
|
||||
/// incorrect assumption.
|
||||
pub const QUOTE_TOKEN_INDEX: TokenIndex = 0;
|
||||
|
||||
/// The token index used for the insurance fund.
|
||||
///
|
||||
/// We should eventually generalize insurance funds.
|
||||
pub const INSURANCE_TOKEN_INDEX: TokenIndex = 0;
|
||||
|
||||
/// The token index used for settling perp markets.
|
||||
///
|
||||
/// We should eventually generalize to make the whole perp quote (and settle) token
|
||||
/// configurable.
|
||||
pub const PERP_SETTLE_TOKEN_INDEX: TokenIndex = 0;
|
||||
|
||||
#[account(zero_copy(safe_bytemuck_derives))]
|
||||
#[derive(Debug)]
|
||||
pub struct Group {
|
||||
|
|
|
@ -519,6 +519,10 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
|||
// TEST: use the insurance fund to liquidate borrow1 and borrow2
|
||||
//
|
||||
|
||||
// Change value of token that the insurance fund is in, to check that bankruptcy amounts
|
||||
// are correct if it depegs
|
||||
set_bank_stub_oracle_price(solana, group, borrow_token1, admin, 2.0).await;
|
||||
|
||||
// bankruptcy of an USDC liability: just transfers funds from insurance vault to liqee,
|
||||
// the liqor is uninvolved
|
||||
let insurance_vault_before = solana.token_account_balance(insurance_vault).await;
|
||||
|
@ -553,7 +557,8 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
|||
let liab_before = account_position_f64(solana, account, borrow_token2.bank).await;
|
||||
let insurance_vault_before = solana.token_account_balance(insurance_vault).await;
|
||||
let liqor_before = account_position(solana, vault_account, borrow_token1.bank).await;
|
||||
let liab_transfer: f64 = 500.0 / 20.0;
|
||||
let usdc_to_liab = 2.0 / 20.0;
|
||||
let liab_transfer: f64 = 500.0 * usdc_to_liab;
|
||||
send_tx(
|
||||
solana,
|
||||
TokenLiqBankruptcyInstruction {
|
||||
|
@ -573,7 +578,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
|||
account_position(solana, account, borrow_token2.bank).await,
|
||||
(liab_before + liab_transfer) as i64
|
||||
);
|
||||
let usdc_amount = (liab_transfer * 20.0 * 1.02).ceil() as u64;
|
||||
let usdc_amount = (liab_transfer / usdc_to_liab * 1.02).ceil() as u64;
|
||||
assert_eq!(
|
||||
solana.token_account_balance(insurance_vault).await,
|
||||
insurance_vault_before - usdc_amount
|
||||
|
|
Loading…
Reference in New Issue