Serum: more health computation

Now the open orders are actually read
This commit is contained in:
Christian Kamm 2022-03-16 18:32:53 +01:00
parent 6aa4724b45
commit 0593aa81f7
1 changed files with 76 additions and 32 deletions

View File

@ -1,9 +1,10 @@
use anchor_lang::prelude::*;
use anchor_spl::dex::serum_dex;
use fixed::types::I80F48;
use std::cell::Ref;
use crate::error::MangoError;
use crate::state::{oracle_price, Bank, MangoAccount, TokenIndex};
use crate::state::{oracle_price, Bank, MangoAccount};
use crate::util;
use crate::util::checked_math as cm;
use crate::util::LoadZeroCopy;
@ -21,17 +22,26 @@ pub fn compute_health(account: &MangoAccount, ais: &[AccountInfo]) -> Result<I80
compute_health_detail(account, banks, oracles, serum_oos)
}
struct BankAndPrice<'a> {
struct TokenInfo<'a> {
bank: Ref<'a, Bank>,
price: I80F48,
oracle_price: I80F48, // native/native
// in native tokens, summing token deposits/borrows and serum open orders
balance: I80F48,
}
fn find_price(token_index: TokenIndex, banks_and_prices: &[BankAndPrice]) -> Result<I80F48> {
Ok(banks_and_prices
.iter()
.find(|b| b.bank.token_index == token_index)
.ok_or(error!(MangoError::SomeError))?
.price)
fn strip_dex_padding<'a>(acc: &'a AccountInfo) -> Result<Ref<'a, [u8]>> {
require!(acc.data_len() >= 12, MangoError::SomeError);
let unpadded_data: Ref<[u8]> = Ref::map(acc.try_borrow_data()?, |data| {
let data_len = data.len() - 12;
let (_, rest) = data.split_at(5);
let (mid, _) = rest.split_at(data_len);
mid
});
Ok(unpadded_data)
}
pub fn load_open_orders<'a>(acc: &'a AccountInfo) -> Result<Ref<'a, serum_dex::state::OpenOrders>> {
Ok(Ref::map(strip_dex_padding(acc)?, bytemuck::from_bytes))
}
fn compute_health_detail(
@ -40,24 +50,26 @@ fn compute_health_detail(
oracles: &[AccountInfo],
serum_oos: &[AccountInfo],
) -> Result<I80F48> {
let mut assets = I80F48::ZERO;
let mut liabilities = I80F48::ZERO; // absolute value
// collect the bank and oracle data once
let banks_and_prices = util::zip!(banks.iter(), oracles.iter())
let mut token_infos = util::zip!(banks.iter(), oracles.iter())
.map(|(bank_ai, oracle_ai)| {
let bank = bank_ai.load::<Bank>()?;
require!(bank.oracle == oracle_ai.key(), MangoError::UnexpectedOracle);
let price = oracle_price(oracle_ai)?;
Ok(BankAndPrice { bank, price })
let oracle_price = oracle_price(oracle_ai)?;
Ok(TokenInfo {
bank,
oracle_price,
balance: I80F48::ZERO,
})
})
.collect::<Result<Vec<BankAndPrice>>>()?;
.collect::<Result<Vec<_>>>()?;
// health contribution from token accounts
for (position, BankAndPrice { bank, price }) in util::zip!(
// token contribution from token accounts
for (position, token_info) in util::zip!(
account.token_account_map.iter_active(),
banks_and_prices.iter()
token_infos.iter_mut()
) {
let bank = &token_info.bank;
// This assumes banks are passed in order
require!(
bank.token_index == position.token_index,
@ -66,17 +78,11 @@ fn compute_health_detail(
// converts the token value to the basis token value for health computations
// TODO: health basis token == USDC?
let price = *price;
let native_position = position.native(&bank);
let native_basis = cm!(native_position * price);
if native_basis.is_positive() {
assets = cm!(assets + bank.init_asset_weight * native_basis);
} else {
liabilities = cm!(liabilities - bank.init_liab_weight * native_basis);
}
let native = position.native(&bank);
token_info.balance = cm!(token_info.balance + native);
}
// health contribution from serum accounts
// token contribution from serum accounts
for (serum_account, oo_ai) in
util::zip!(account.serum_account_map.iter_active(), serum_oos.iter())
{
@ -87,10 +93,48 @@ fn compute_health_detail(
);
// find the prices for the market
// TODO: each of these is a linear scan through banks_and_prices - is that too expensive?
let _base_price = find_price(serum_account.base_token_index, &banks_and_prices)?;
let _quote_price = find_price(serum_account.quote_token_index, &banks_and_prices)?;
// TODO: each of these is a linear scan - is that too expensive?
let base_index = token_infos
.iter()
.position(|ti| ti.bank.token_index == serum_account.base_token_index)
.ok_or(error!(MangoError::SomeError))?;
let quote_index = token_infos
.iter()
.position(|ti| ti.bank.token_index == serum_account.quote_token_index)
.ok_or(error!(MangoError::SomeError))?;
let oo = load_open_orders(oo_ai)?;
// add the amounts that are freely settleable
token_infos[base_index].balance += I80F48::from_num(oo.native_coin_free);
token_infos[quote_index].balance +=
I80F48::from_num(oo.native_pc_free + oo.referrer_rebates_accrued);
// for the amounts that are reserved for orders, compute the worst case for health
// by checking if everything-is-base or everything-is-quote produces worse
// outcomes
// TODO: that kind of approach may no longer be possible with each
// market potentially having two different tokens involved?
let reserved_base = oo.native_coin_total - oo.native_coin_free;
let reserved_quote = oo.native_pc_total - oo.native_pc_free;
// TODO: do it, this is just a stub
token_infos[base_index].balance += I80F48::from_num(reserved_base);
token_infos[quote_index].balance += I80F48::from_num(reserved_quote);
}
Ok(cm!(assets - liabilities))
// convert the token balance to health
let mut asset_health = I80F48::ZERO;
let mut liability_health = I80F48::ZERO; // positive
for token_info in token_infos.iter() {
let bank = &token_info.bank;
if token_info.balance.is_negative() {
liability_health = cm!(liability_health
- bank.init_liab_weight * token_info.balance * token_info.oracle_price);
} else {
asset_health = cm!(asset_health
+ bank.init_asset_weight * token_info.balance * token_info.oracle_price);
}
}
Ok(cm!(asset_health - liability_health))
}