Health: Add soft deposit and borrow limits
This commit is contained in:
parent
512eef96ea
commit
cf34a5b4b7
|
@ -81,6 +81,39 @@ pub fn division_i80f48_f64(a: I80F48, b: I80F48) -> I80F48 {
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
pub fn mul_f64(a: f64, b: f64) -> f64 {
|
||||||
|
msg!("mul_f64");
|
||||||
|
sol_log_compute_units();
|
||||||
|
let r = a * b;
|
||||||
|
if r.is_nan() {
|
||||||
|
panic!("nan"); // here as a side-effect to avoid reordering
|
||||||
|
}
|
||||||
|
sol_log_compute_units();
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
pub fn mul_i80f48(a: I80F48, b: I80F48) -> I80F48 {
|
||||||
|
msg!("mul_i80f48");
|
||||||
|
sol_log_compute_units();
|
||||||
|
let r = a.checked_mul(b).unwrap();
|
||||||
|
sol_log_compute_units();
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
pub fn i80f48_to_f64(a: I80F48) -> f64 {
|
||||||
|
msg!("i80f48_to_f64");
|
||||||
|
sol_log_compute_units();
|
||||||
|
let r = a.to_num::<f64>();
|
||||||
|
if r.is_nan() {
|
||||||
|
panic!("nan"); // here as a side-effect to avoid reordering
|
||||||
|
}
|
||||||
|
sol_log_compute_units();
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
pub fn benchmark(_ctx: Context<Benchmark>) -> Result<()> {
|
pub fn benchmark(_ctx: Context<Benchmark>) -> Result<()> {
|
||||||
// 101000
|
// 101000
|
||||||
// 477
|
// 477
|
||||||
|
@ -98,6 +131,8 @@ pub fn benchmark(_ctx: Context<Benchmark>) -> Result<()> {
|
||||||
division_i128(a.to_bits(), b.to_bits()); // 100 - 2000 CU
|
division_i128(a.to_bits(), b.to_bits()); // 100 - 2000 CU
|
||||||
division_i80f48_30bit(a, b); // 300 CU
|
division_i80f48_30bit(a, b); // 300 CU
|
||||||
division_i80f48_f64(a, b); // 500 CU
|
division_i80f48_f64(a, b); // 500 CU
|
||||||
|
mul_i80f48(a >> 64, b >> 64); // 100 CU
|
||||||
|
i80f48_to_f64(a); // 50 CU
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -126,6 +161,12 @@ pub fn benchmark(_ctx: Context<Benchmark>) -> Result<()> {
|
||||||
division_u32(a, b); // 20 CU
|
division_u32(a, b); // 20 CU
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let a = clock.slot as f64;
|
||||||
|
let b = clock.unix_timestamp as f64;
|
||||||
|
mul_f64(a, b); // 0 CU??
|
||||||
|
}
|
||||||
|
|
||||||
sol_log_compute_units(); // 100321 -> 101
|
sol_log_compute_units(); // 100321 -> 101
|
||||||
msg!("msg!"); // 100079+101 -> 203
|
msg!("msg!"); // 100079+101 -> 203
|
||||||
sol_log_compute_units(); // 100117
|
sol_log_compute_units(); // 100117
|
||||||
|
|
|
@ -218,7 +218,7 @@ pub fn serum3_liq_force_cancel_orders(
|
||||||
after_base_vault,
|
after_base_vault,
|
||||||
before_base_vault,
|
before_base_vault,
|
||||||
)?
|
)?
|
||||||
.adjust_health_cache(&mut health_cache)?;
|
.adjust_health_cache(&mut health_cache, &base_bank)?;
|
||||||
apply_vault_difference(
|
apply_vault_difference(
|
||||||
ctx.accounts.account.key(),
|
ctx.accounts.account.key(),
|
||||||
&mut account.borrow_mut(),
|
&mut account.borrow_mut(),
|
||||||
|
@ -227,7 +227,7 @@ pub fn serum3_liq_force_cancel_orders(
|
||||||
after_quote_vault,
|
after_quote_vault,
|
||||||
before_quote_vault,
|
before_quote_vault,
|
||||||
)?
|
)?
|
||||||
.adjust_health_cache(&mut health_cache)?;
|
.adjust_health_cache(&mut health_cache, "e_bank)?;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Health check at the end
|
// Health check at the end
|
||||||
|
|
|
@ -340,8 +340,8 @@ pub fn serum3_place_order(
|
||||||
require_gte!(before_vault, after_vault);
|
require_gte!(before_vault, after_vault);
|
||||||
|
|
||||||
// Charge the difference in vault balance to the user's account
|
// Charge the difference in vault balance to the user's account
|
||||||
|
let mut payer_bank = ctx.accounts.payer_bank.load_mut()?;
|
||||||
let vault_difference = {
|
let vault_difference = {
|
||||||
let mut payer_bank = ctx.accounts.payer_bank.load_mut()?;
|
|
||||||
apply_vault_difference(
|
apply_vault_difference(
|
||||||
ctx.accounts.account.key(),
|
ctx.accounts.account.key(),
|
||||||
&mut account.borrow_mut(),
|
&mut account.borrow_mut(),
|
||||||
|
@ -356,7 +356,7 @@ pub fn serum3_place_order(
|
||||||
// Health check
|
// Health check
|
||||||
//
|
//
|
||||||
if let Some((mut health_cache, pre_health)) = pre_health_opt {
|
if let Some((mut health_cache, pre_health)) = pre_health_opt {
|
||||||
vault_difference.adjust_health_cache(&mut health_cache)?;
|
vault_difference.adjust_health_cache(&mut health_cache, &payer_bank)?;
|
||||||
oo_difference.adjust_health_cache(&mut health_cache, &serum_market)?;
|
oo_difference.adjust_health_cache(&mut health_cache, &serum_market)?;
|
||||||
account.check_health_post(&health_cache, pre_health)?;
|
account.check_health_post(&health_cache, pre_health)?;
|
||||||
}
|
}
|
||||||
|
@ -408,8 +408,9 @@ pub struct VaultDifference {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VaultDifference {
|
impl VaultDifference {
|
||||||
pub fn adjust_health_cache(&self, health_cache: &mut HealthCache) -> Result<()> {
|
pub fn adjust_health_cache(&self, health_cache: &mut HealthCache, bank: &Bank) -> Result<()> {
|
||||||
health_cache.adjust_token_balance(self.token_index, self.native_change)?;
|
assert_eq!(bank.token_index, self.token_index);
|
||||||
|
health_cache.adjust_token_balance(bank, self.native_change)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -297,7 +297,7 @@ pub fn token_liq_bankruptcy(
|
||||||
let liab_bank = bank_ais[0].load::<Bank>()?;
|
let liab_bank = bank_ais[0].load::<Bank>()?;
|
||||||
let end_liab_native = liqee_liab.native(&liab_bank);
|
let end_liab_native = liqee_liab.native(&liab_bank);
|
||||||
liqee_health_cache
|
liqee_health_cache
|
||||||
.adjust_token_balance(liab_token_index, cm!(end_liab_native - initial_liab_native))?;
|
.adjust_token_balance(&liab_bank, cm!(end_liab_native - initial_liab_native))?;
|
||||||
|
|
||||||
// Check liqee health again
|
// Check liqee health again
|
||||||
let liqee_init_health = liqee_health_cache.health(HealthType::Init);
|
let liqee_init_health = liqee_health_cache.health(HealthType::Init);
|
||||||
|
|
|
@ -187,12 +187,10 @@ pub fn token_liq_with_token(
|
||||||
let liqee_assets_native_after = liqee_asset_position.native(asset_bank);
|
let liqee_assets_native_after = liqee_asset_position.native(asset_bank);
|
||||||
|
|
||||||
// Update the health cache
|
// Update the health cache
|
||||||
|
liqee_health_cache
|
||||||
|
.adjust_token_balance(&liab_bank, cm!(liqee_liab_native_after - liqee_liab_native))?;
|
||||||
liqee_health_cache.adjust_token_balance(
|
liqee_health_cache.adjust_token_balance(
|
||||||
liab_token_index,
|
&asset_bank,
|
||||||
cm!(liqee_liab_native_after - liqee_liab_native),
|
|
||||||
)?;
|
|
||||||
liqee_health_cache.adjust_token_balance(
|
|
||||||
asset_token_index,
|
|
||||||
cm!(liqee_assets_native_after - liqee_asset_native),
|
cm!(liqee_assets_native_after - liqee_asset_native),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
|
@ -151,7 +151,9 @@ pub fn token_register(
|
||||||
* net_borrows_window_size_ts,
|
* net_borrows_window_size_ts,
|
||||||
net_borrows_limit_native,
|
net_borrows_limit_native,
|
||||||
net_borrows_window_native: 0,
|
net_borrows_window_native: 0,
|
||||||
reserved: [0; 2136],
|
borrow_limit_quote: f64::MAX,
|
||||||
|
collateral_limit_quote: f64::MAX,
|
||||||
|
reserved: [0; 2120],
|
||||||
};
|
};
|
||||||
require_gt!(bank.max_rate, MINIMUM_MAX_RATE);
|
require_gt!(bank.max_rate, MINIMUM_MAX_RATE);
|
||||||
|
|
||||||
|
|
|
@ -126,7 +126,9 @@ pub fn token_register_trustless(
|
||||||
* net_borrows_window_size_ts,
|
* net_borrows_window_size_ts,
|
||||||
net_borrows_limit_native: 1_000_000,
|
net_borrows_limit_native: 1_000_000,
|
||||||
net_borrows_window_native: 0,
|
net_borrows_window_native: 0,
|
||||||
reserved: [0; 2136],
|
borrow_limit_quote: f64::MAX,
|
||||||
|
collateral_limit_quote: f64::MAX,
|
||||||
|
reserved: [0; 2120],
|
||||||
};
|
};
|
||||||
require_gt!(bank.max_rate, MINIMUM_MAX_RATE);
|
require_gt!(bank.max_rate, MINIMUM_MAX_RATE);
|
||||||
|
|
||||||
|
|
|
@ -146,8 +146,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
|
||||||
// Health check
|
// Health check
|
||||||
//
|
//
|
||||||
if let Some((mut health_cache, pre_health)) = pre_health_opt {
|
if let Some((mut health_cache, pre_health)) = pre_health_opt {
|
||||||
health_cache
|
health_cache.adjust_token_balance(&bank, cm!(native_position_after - native_position))?;
|
||||||
.adjust_token_balance(token_index, cm!(native_position_after - native_position))?;
|
|
||||||
account.check_health_post(&health_cache, pre_health)?;
|
account.check_health_post(&health_cache, pre_health)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,8 +113,25 @@ pub struct Bank {
|
||||||
pub net_borrows_limit_native: i64,
|
pub net_borrows_limit_native: i64,
|
||||||
pub net_borrows_window_native: i64,
|
pub net_borrows_window_native: i64,
|
||||||
|
|
||||||
|
/// Soft borrow limit in native quote
|
||||||
|
///
|
||||||
|
/// Once the borrows on the bank exceed this quote value, init_liab_weight is scaled up.
|
||||||
|
/// Set to f64::MAX to disable.
|
||||||
|
///
|
||||||
|
/// See scaled_init_liab_weight().
|
||||||
|
pub borrow_limit_quote: f64,
|
||||||
|
|
||||||
|
/// Limit for collateral of deposits
|
||||||
|
///
|
||||||
|
/// Once the deposits in the bank exceed this quote value, init_asset_weight is scaled
|
||||||
|
/// down to keep the total collateral value constant.
|
||||||
|
/// Set to f64::MAX to disable.
|
||||||
|
///
|
||||||
|
/// See scaled_init_asset_weight().
|
||||||
|
pub collateral_limit_quote: f64,
|
||||||
|
|
||||||
#[derivative(Debug = "ignore")]
|
#[derivative(Debug = "ignore")]
|
||||||
pub reserved: [u8; 2136],
|
pub reserved: [u8; 2120],
|
||||||
}
|
}
|
||||||
const_assert_eq!(size_of::<Bank>(), 3112);
|
const_assert_eq!(size_of::<Bank>(), 3112);
|
||||||
const_assert_eq!(size_of::<Bank>() % 8, 0);
|
const_assert_eq!(size_of::<Bank>() % 8, 0);
|
||||||
|
@ -175,7 +192,9 @@ impl Bank {
|
||||||
net_borrows_window_size_ts: existing_bank.net_borrows_window_size_ts,
|
net_borrows_window_size_ts: existing_bank.net_borrows_window_size_ts,
|
||||||
last_net_borrows_window_start_ts: existing_bank.last_net_borrows_window_start_ts,
|
last_net_borrows_window_start_ts: existing_bank.last_net_borrows_window_start_ts,
|
||||||
net_borrows_window_native: 0,
|
net_borrows_window_native: 0,
|
||||||
reserved: [0; 2136],
|
borrow_limit_quote: f64::MAX,
|
||||||
|
collateral_limit_quote: f64::MAX,
|
||||||
|
reserved: [0; 2120],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,12 +204,14 @@ impl Bank {
|
||||||
.trim_matches(char::from(0))
|
.trim_matches(char::from(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
pub fn native_borrows(&self) -> I80F48 {
|
pub fn native_borrows(&self) -> I80F48 {
|
||||||
self.borrow_index * self.indexed_borrows
|
cm!(self.borrow_index * self.indexed_borrows)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
pub fn native_deposits(&self) -> I80F48 {
|
pub fn native_deposits(&self) -> I80F48 {
|
||||||
self.deposit_index * self.indexed_deposits
|
cm!(self.deposit_index * self.indexed_deposits)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deposits `native_amount`.
|
/// Deposits `native_amount`.
|
||||||
|
@ -692,6 +713,47 @@ impl Bank {
|
||||||
pub fn stable_price(&self) -> I80F48 {
|
pub fn stable_price(&self) -> I80F48 {
|
||||||
I80F48::from_num(self.stable_price_model.stable_price)
|
I80F48::from_num(self.stable_price_model.stable_price)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the init asset weight, adjusted for the number of deposits on the bank.
|
||||||
|
///
|
||||||
|
/// If max_collateral is 0, then the scaled init weight will be 0.
|
||||||
|
/// Otherwise the weight is unadjusted until max_collateral and then scaled down
|
||||||
|
/// such that scaled_init_weight * deposits remains constant.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn scaled_init_asset_weight(&self, price: I80F48) -> I80F48 {
|
||||||
|
if self.collateral_limit_quote == f64::MAX {
|
||||||
|
return self.init_asset_weight;
|
||||||
|
}
|
||||||
|
// The next line is around 500 CU
|
||||||
|
let deposits_quote = self.native_deposits().to_num::<f64>() * price.to_num::<f64>();
|
||||||
|
if deposits_quote <= self.collateral_limit_quote {
|
||||||
|
self.init_asset_weight
|
||||||
|
} else {
|
||||||
|
// The next line is around 500 CU
|
||||||
|
let scale = self.collateral_limit_quote / deposits_quote;
|
||||||
|
cm!(self.init_asset_weight * I80F48::from_num(scale))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn scaled_init_liab_weight(&self, price: I80F48) -> I80F48 {
|
||||||
|
if self.borrow_limit_quote == f64::MAX {
|
||||||
|
return self.init_liab_weight;
|
||||||
|
}
|
||||||
|
// The next line is around 500 CU
|
||||||
|
let borrows_quote = self.native_borrows().to_num::<f64>() * price.to_num::<f64>();
|
||||||
|
if borrows_quote <= self.borrow_limit_quote {
|
||||||
|
self.init_liab_weight
|
||||||
|
} else if self.borrow_limit_quote == 0.0 {
|
||||||
|
// TODO: will certainly cause overflow, so it's not exactly what is needed; health should be -MAX?
|
||||||
|
// maybe handling this case isn't super helpful?
|
||||||
|
I80F48::MAX
|
||||||
|
} else {
|
||||||
|
// The next line is around 500 CU
|
||||||
|
let scale = borrows_quote / self.borrow_limit_quote;
|
||||||
|
cm!(self.init_liab_weight * I80F48::from_num(scale))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|
|
@ -5,7 +5,7 @@ use fixed_macro::types::I80F48;
|
||||||
|
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::state::{
|
use crate::state::{
|
||||||
MangoAccountFixed, MangoAccountRef, PerpMarket, PerpMarketIndex, PerpPosition,
|
Bank, MangoAccountFixed, MangoAccountRef, PerpMarket, PerpMarketIndex, PerpPosition,
|
||||||
Serum3MarketIndex, TokenIndex,
|
Serum3MarketIndex, TokenIndex,
|
||||||
};
|
};
|
||||||
use crate::util::checked_math as cm;
|
use crate::util::checked_math as cm;
|
||||||
|
@ -365,10 +365,15 @@ impl HealthCache {
|
||||||
.ok_or_else(|| error_msg!("token index {} not found", token_index))
|
.ok_or_else(|| error_msg!("token index {} not found", token_index))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn adjust_token_balance(&mut self, token_index: TokenIndex, change: I80F48) -> Result<()> {
|
/// Changes the cached user account token balance.
|
||||||
let entry_index = self.token_info_index(token_index)?;
|
pub fn adjust_token_balance(&mut self, bank: &Bank, change: I80F48) -> Result<()> {
|
||||||
|
let entry_index = self.token_info_index(bank.token_index)?;
|
||||||
let mut entry = &mut self.token_infos[entry_index];
|
let mut entry = &mut self.token_infos[entry_index];
|
||||||
|
|
||||||
|
entry.init_asset_weight =
|
||||||
|
bank.scaled_init_asset_weight(entry.prices.asset(HealthType::Init));
|
||||||
|
entry.init_liab_weight = bank.scaled_init_liab_weight(entry.prices.liab(HealthType::Init));
|
||||||
|
|
||||||
// Work around the fact that -((-x) * y) == x * y does not hold for I80F48:
|
// Work around the fact that -((-x) * y) == x * y does not hold for I80F48:
|
||||||
// We need to make sure that if balance is before * price, then change = -before
|
// We need to make sure that if balance is before * price, then change = -before
|
||||||
// brings it to exactly zero.
|
// brings it to exactly zero.
|
||||||
|
@ -377,6 +382,20 @@ impl HealthCache {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Recomputes the dynamic init weights for the bank's current deposits/borrows.
|
||||||
|
pub fn recompute_token_weights(&mut self, bank: &Bank) -> Result<()> {
|
||||||
|
let entry_index = self.token_info_index(bank.token_index)?;
|
||||||
|
let mut entry = &mut self.token_infos[entry_index];
|
||||||
|
entry.init_asset_weight =
|
||||||
|
bank.scaled_init_asset_weight(entry.prices.asset(HealthType::Init));
|
||||||
|
entry.init_liab_weight = bank.scaled_init_liab_weight(entry.prices.liab(HealthType::Init));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes the cached user account token and serum balances.
|
||||||
|
///
|
||||||
|
/// WARNING: You must also call recompute_token_weights() after all bank
|
||||||
|
/// deposit/withdraw changes!
|
||||||
pub fn adjust_serum3_reserved(
|
pub fn adjust_serum3_reserved(
|
||||||
&mut self,
|
&mut self,
|
||||||
market_index: Serum3MarketIndex,
|
market_index: Serum3MarketIndex,
|
||||||
|
@ -604,16 +623,17 @@ pub fn new_health_cache(
|
||||||
retriever.bank_and_oracle(&account.fixed.group, i, position.token_index)?;
|
retriever.bank_and_oracle(&account.fixed.group, i, position.token_index)?;
|
||||||
|
|
||||||
let native = position.native(bank);
|
let native = position.native(bank);
|
||||||
|
let prices = Prices {
|
||||||
|
oracle: oracle_price,
|
||||||
|
stable: bank.stable_price(),
|
||||||
|
};
|
||||||
token_infos.push(TokenInfo {
|
token_infos.push(TokenInfo {
|
||||||
token_index: bank.token_index,
|
token_index: bank.token_index,
|
||||||
maint_asset_weight: bank.maint_asset_weight,
|
maint_asset_weight: bank.maint_asset_weight,
|
||||||
init_asset_weight: bank.init_asset_weight,
|
init_asset_weight: bank.scaled_init_asset_weight(prices.asset(HealthType::Init)),
|
||||||
maint_liab_weight: bank.maint_liab_weight,
|
maint_liab_weight: bank.maint_liab_weight,
|
||||||
init_liab_weight: bank.init_liab_weight,
|
init_liab_weight: bank.scaled_init_liab_weight(prices.liab(HealthType::Init)),
|
||||||
prices: Prices {
|
prices,
|
||||||
oracle: oracle_price,
|
|
||||||
stable: bank.stable_price(),
|
|
||||||
},
|
|
||||||
balance_native: native,
|
balance_native: native,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -787,6 +807,14 @@ mod tests {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct BankSettings {
|
||||||
|
deposits: u64,
|
||||||
|
borrows: u64,
|
||||||
|
collateral_limit_quote: u64,
|
||||||
|
borrow_limit_quote: u64,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct TestHealth1Case {
|
struct TestHealth1Case {
|
||||||
token1: i64,
|
token1: i64,
|
||||||
|
@ -796,6 +824,7 @@ mod tests {
|
||||||
oo_1_3: (u64, u64),
|
oo_1_3: (u64, u64),
|
||||||
perp1: (i64, i64, i64, i64),
|
perp1: (i64, i64, i64, i64),
|
||||||
expected_health: f64,
|
expected_health: f64,
|
||||||
|
bank_settings: [BankSettings; 3],
|
||||||
}
|
}
|
||||||
fn test_health1_runner(testcase: &TestHealth1Case) {
|
fn test_health1_runner(testcase: &TestHealth1Case) {
|
||||||
let buffer = MangoAccount::default_for_tests().try_to_vec().unwrap();
|
let buffer = MangoAccount::default_for_tests().try_to_vec().unwrap();
|
||||||
|
@ -830,6 +859,21 @@ mod tests {
|
||||||
DUMMY_NOW_TS,
|
DUMMY_NOW_TS,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
for (settings, bank) in testcase
|
||||||
|
.bank_settings
|
||||||
|
.iter()
|
||||||
|
.zip([&mut bank1, &mut bank2, &mut bank3].iter_mut())
|
||||||
|
{
|
||||||
|
let bank = bank.data();
|
||||||
|
bank.indexed_deposits = I80F48::from(settings.deposits) / bank.deposit_index;
|
||||||
|
bank.indexed_borrows = I80F48::from(settings.borrows) / bank.borrow_index;
|
||||||
|
if settings.collateral_limit_quote > 0 {
|
||||||
|
bank.collateral_limit_quote = settings.collateral_limit_quote as f64;
|
||||||
|
}
|
||||||
|
if settings.borrow_limit_quote > 0 {
|
||||||
|
bank.borrow_limit_quote = settings.borrow_limit_quote as f64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut oo1 = TestAccount::<OpenOrders>::new_zeroed();
|
let mut oo1 = TestAccount::<OpenOrders>::new_zeroed();
|
||||||
let serum3account1 = account.create_serum3_orders(2).unwrap();
|
let serum3account1 = account.create_serum3_orders(2).unwrap();
|
||||||
|
@ -995,6 +1039,66 @@ mod tests {
|
||||||
+ 20.0 * 0.8,
|
+ 20.0 * 0.8,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
TestHealth1Case { // 10, checking collateral limit
|
||||||
|
token1: 100,
|
||||||
|
token2: 100,
|
||||||
|
token3: 100,
|
||||||
|
bank_settings: [
|
||||||
|
BankSettings {
|
||||||
|
deposits: 100,
|
||||||
|
collateral_limit_quote: 1000,
|
||||||
|
..BankSettings::default()
|
||||||
|
},
|
||||||
|
BankSettings {
|
||||||
|
deposits: 1500,
|
||||||
|
collateral_limit_quote: 1000 * 5,
|
||||||
|
..BankSettings::default()
|
||||||
|
},
|
||||||
|
BankSettings {
|
||||||
|
deposits: 10000,
|
||||||
|
collateral_limit_quote: 1000 * 10,
|
||||||
|
..BankSettings::default()
|
||||||
|
},
|
||||||
|
],
|
||||||
|
expected_health:
|
||||||
|
// token1
|
||||||
|
0.8 * 100.0
|
||||||
|
// token2
|
||||||
|
+ 0.5 * 100.0 * 5.0 * (5000.0 / (1500.0 * 5.0))
|
||||||
|
// token3
|
||||||
|
+ 0.5 * 100.0 * 10.0 * (10000.0 / (10000.0 * 10.0)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
TestHealth1Case { // 11, checking borrow limit
|
||||||
|
token1: -100,
|
||||||
|
token2: -100,
|
||||||
|
token3: -100,
|
||||||
|
bank_settings: [
|
||||||
|
BankSettings {
|
||||||
|
borrows: 100,
|
||||||
|
borrow_limit_quote: 1000,
|
||||||
|
..BankSettings::default()
|
||||||
|
},
|
||||||
|
BankSettings {
|
||||||
|
borrows: 1500,
|
||||||
|
borrow_limit_quote: 1000 * 5,
|
||||||
|
..BankSettings::default()
|
||||||
|
},
|
||||||
|
BankSettings {
|
||||||
|
borrows: 10000,
|
||||||
|
borrow_limit_quote: 1000 * 10,
|
||||||
|
..BankSettings::default()
|
||||||
|
},
|
||||||
|
],
|
||||||
|
expected_health:
|
||||||
|
// token1
|
||||||
|
-1.2 * 100.0
|
||||||
|
// token2
|
||||||
|
- 1.5 * 100.0 * 5.0 * (1500.0 * 5.0 / 5000.0)
|
||||||
|
// token3
|
||||||
|
- 1.5 * 100.0 * 10.0 * (10000.0 * 10.0 / 10000.0),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
for (i, testcase) in testcases.iter().enumerate() {
|
for (i, testcase) in testcases.iter().enumerate() {
|
||||||
|
|
|
@ -6,7 +6,7 @@ use fixed::types::I80F48;
|
||||||
use fixed_macro::types::I80F48;
|
use fixed_macro::types::I80F48;
|
||||||
|
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::state::{PerpMarketIndex, TokenIndex};
|
use crate::state::{Bank, MangoAccountValue, PerpMarketIndex};
|
||||||
use crate::util::checked_math as cm;
|
use crate::util::checked_math as cm;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -43,6 +43,44 @@ impl HealthCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cache_after_swap(
|
||||||
|
&self,
|
||||||
|
account: &MangoAccountValue,
|
||||||
|
source_bank: &Bank,
|
||||||
|
target_bank: &Bank,
|
||||||
|
amount: I80F48,
|
||||||
|
price: I80F48,
|
||||||
|
) -> Self {
|
||||||
|
let mut source_position = account
|
||||||
|
.token_position(source_bank.token_index)
|
||||||
|
.map(|v| v.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let mut target_position = account
|
||||||
|
.token_position(target_bank.token_index)
|
||||||
|
.map(|v| v.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let target_amount = cm!(amount * price);
|
||||||
|
|
||||||
|
let mut source_bank = source_bank.clone();
|
||||||
|
source_bank
|
||||||
|
.withdraw_with_fee(&mut source_position, amount, 0)
|
||||||
|
.unwrap();
|
||||||
|
let mut target_bank = target_bank.clone();
|
||||||
|
target_bank
|
||||||
|
.deposit(&mut target_position, target_amount, 0)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut resulting_cache = self.clone();
|
||||||
|
resulting_cache
|
||||||
|
.adjust_token_balance(&source_bank, -amount)
|
||||||
|
.unwrap();
|
||||||
|
resulting_cache
|
||||||
|
.adjust_token_balance(&target_bank, target_amount)
|
||||||
|
.unwrap();
|
||||||
|
resulting_cache
|
||||||
|
}
|
||||||
|
|
||||||
/// How much source native tokens may be swapped for target tokens while staying
|
/// How much source native tokens may be swapped for target tokens while staying
|
||||||
/// above the min_ratio health ratio.
|
/// above the min_ratio health ratio.
|
||||||
///
|
///
|
||||||
|
@ -54,8 +92,9 @@ impl HealthCache {
|
||||||
/// NOTE: keep getMaxSourceForTokenSwap in ts/client in sync with changes here
|
/// NOTE: keep getMaxSourceForTokenSwap in ts/client in sync with changes here
|
||||||
pub fn max_swap_source_for_health_ratio(
|
pub fn max_swap_source_for_health_ratio(
|
||||||
&self,
|
&self,
|
||||||
source: TokenIndex,
|
account: &MangoAccountValue,
|
||||||
target: TokenIndex,
|
source_bank: &Bank,
|
||||||
|
target_bank: &Bank,
|
||||||
price: I80F48,
|
price: I80F48,
|
||||||
min_ratio: I80F48,
|
min_ratio: I80F48,
|
||||||
) -> Result<I80F48> {
|
) -> Result<I80F48> {
|
||||||
|
@ -74,8 +113,8 @@ impl HealthCache {
|
||||||
return Ok(I80F48::ZERO);
|
return Ok(I80F48::ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
let source_index = find_token_info_index(&self.token_infos, source)?;
|
let source_index = find_token_info_index(&self.token_infos, source_bank.token_index)?;
|
||||||
let target_index = find_token_info_index(&self.token_infos, target)?;
|
let target_index = find_token_info_index(&self.token_infos, target_bank.token_index)?;
|
||||||
let source = &self.token_infos[source_index];
|
let source = &self.token_infos[source_index];
|
||||||
let target = &self.token_infos[target_index];
|
let target = &self.token_infos[target_index];
|
||||||
|
|
||||||
|
@ -89,10 +128,7 @@ impl HealthCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
let cache_after_swap = |amount: I80F48| {
|
let cache_after_swap = |amount: I80F48| {
|
||||||
let mut adjusted_cache = self.clone();
|
self.cache_after_swap(account, source_bank, target_bank, amount, price)
|
||||||
adjusted_cache.token_infos[source_index].balance_native -= amount;
|
|
||||||
adjusted_cache.token_infos[target_index].balance_native += cm!(amount * price);
|
|
||||||
adjusted_cache
|
|
||||||
};
|
};
|
||||||
let health_ratio_after_swap =
|
let health_ratio_after_swap =
|
||||||
|amount| cache_after_swap(amount).health_ratio(HealthType::Init);
|
|amount| cache_after_swap(amount).health_ratio(HealthType::Init);
|
||||||
|
@ -379,6 +415,15 @@ mod tests {
|
||||||
balance_native: I80F48::ZERO,
|
balance_native: I80F48::ZERO,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let buffer = MangoAccount::default_for_tests().try_to_vec().unwrap();
|
||||||
|
let account = MangoAccountValue::from_bytes(&buffer).unwrap();
|
||||||
|
|
||||||
|
let group = Pubkey::new_unique();
|
||||||
|
let (mut bank0, _) = mock_bank_and_oracle(group, 0, 1.0, 0.1, 0.1);
|
||||||
|
let (mut bank1, _) = mock_bank_and_oracle(group, 1, 5.0, 0.2, 0.2);
|
||||||
|
let (mut bank2, _) = mock_bank_and_oracle(group, 2, 5.0, 0.3, 0.3);
|
||||||
|
let banks = [bank0.data(), bank1.data(), bank2.data()];
|
||||||
|
|
||||||
let health_cache = HealthCache {
|
let health_cache = HealthCache {
|
||||||
token_infos: vec![
|
token_infos: vec![
|
||||||
TokenInfo {
|
TokenInfo {
|
||||||
|
@ -407,8 +452,9 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
health_cache
|
health_cache
|
||||||
.max_swap_source_for_health_ratio(
|
.max_swap_source_for_health_ratio(
|
||||||
0,
|
&account,
|
||||||
1,
|
banks[0],
|
||||||
|
banks[1],
|
||||||
I80F48::from_num(2.0 / 3.0),
|
I80F48::from_num(2.0 / 3.0),
|
||||||
I80F48::from_num(50.0)
|
I80F48::from_num(50.0)
|
||||||
)
|
)
|
||||||
|
@ -425,15 +471,17 @@ mod tests {
|
||||||
target: TokenIndex,
|
target: TokenIndex,
|
||||||
ratio: f64,
|
ratio: f64,
|
||||||
price_factor: f64| {
|
price_factor: f64| {
|
||||||
let mut c = c.clone();
|
|
||||||
let source_price = &c.token_infos[source as usize].prices;
|
let source_price = &c.token_infos[source as usize].prices;
|
||||||
|
let source_bank = &banks[source as usize];
|
||||||
let target_price = &c.token_infos[target as usize].prices;
|
let target_price = &c.token_infos[target as usize].prices;
|
||||||
|
let target_bank = &banks[target as usize];
|
||||||
let swap_price =
|
let swap_price =
|
||||||
I80F48::from_num(price_factor) * source_price.oracle / target_price.oracle;
|
I80F48::from_num(price_factor) * source_price.oracle / target_price.oracle;
|
||||||
let source_amount = c
|
let source_amount = c
|
||||||
.max_swap_source_for_health_ratio(
|
.max_swap_source_for_health_ratio(
|
||||||
source,
|
&account,
|
||||||
target,
|
source_bank,
|
||||||
|
target_bank,
|
||||||
swap_price,
|
swap_price,
|
||||||
I80F48::from_num(ratio),
|
I80F48::from_num(ratio),
|
||||||
)
|
)
|
||||||
|
@ -441,12 +489,16 @@ mod tests {
|
||||||
if source_amount == I80F48::MAX {
|
if source_amount == I80F48::MAX {
|
||||||
return (f64::MAX, f64::MAX);
|
return (f64::MAX, f64::MAX);
|
||||||
}
|
}
|
||||||
c.adjust_token_balance(source, -source_amount).unwrap();
|
let after_swap = c.cache_after_swap(
|
||||||
c.adjust_token_balance(target, source_amount * swap_price)
|
&account,
|
||||||
.unwrap();
|
source_bank,
|
||||||
|
target_bank,
|
||||||
|
source_amount,
|
||||||
|
swap_price,
|
||||||
|
);
|
||||||
(
|
(
|
||||||
source_amount.to_num::<f64>(),
|
source_amount.to_num::<f64>(),
|
||||||
c.health_ratio(HealthType::Init).to_num::<f64>(),
|
after_swap.health_ratio(HealthType::Init).to_num::<f64>(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let check_max_swap_result = |c: &HealthCache,
|
let check_max_swap_result = |c: &HealthCache,
|
||||||
|
|
|
@ -95,6 +95,8 @@ pub fn mock_bank_and_oracle(
|
||||||
bank.data().maint_asset_weight = I80F48::from_num(1.0 - maint_weights);
|
bank.data().maint_asset_weight = I80F48::from_num(1.0 - maint_weights);
|
||||||
bank.data().maint_liab_weight = I80F48::from_num(1.0 + maint_weights);
|
bank.data().maint_liab_weight = I80F48::from_num(1.0 + maint_weights);
|
||||||
bank.data().stable_price_model.reset_to_price(price, 0);
|
bank.data().stable_price_model.reset_to_price(price, 0);
|
||||||
|
bank.data().collateral_limit_quote = f64::MAX;
|
||||||
|
bank.data().borrow_limit_quote = f64::MAX;
|
||||||
bank.data().net_borrows_window_size_ts = 1; // dummy
|
bank.data().net_borrows_window_size_ts = 1; // dummy
|
||||||
bank.data().net_borrows_limit_native = i64::MAX; // max since we don't want this to interfere
|
bank.data().net_borrows_limit_native = i64::MAX; // max since we don't want this to interfere
|
||||||
(bank, oracle)
|
(bank, oracle)
|
||||||
|
|
|
@ -13,7 +13,9 @@ mod program_test;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
|
async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
|
||||||
let context = TestContext::new().await;
|
let mut test_builder = TestContextBuilder::new();
|
||||||
|
test_builder.test().set_compute_max_units(85_000); // TokenLiqWithToken needs 84k
|
||||||
|
let context = test_builder.start_default().await;
|
||||||
let solana = &context.solana.clone();
|
let solana = &context.solana.clone();
|
||||||
|
|
||||||
let admin = TestKeypair::new();
|
let admin = TestKeypair::new();
|
||||||
|
@ -275,7 +277,9 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
||||||
let context = TestContext::new().await;
|
let mut test_builder = TestContextBuilder::new();
|
||||||
|
test_builder.test().set_compute_max_units(85_000); // TokenLiqWithToken needs 84k
|
||||||
|
let context = test_builder.start_default().await;
|
||||||
let solana = &context.solana.clone();
|
let solana = &context.solana.clone();
|
||||||
|
|
||||||
let admin = TestKeypair::new();
|
let admin = TestKeypair::new();
|
||||||
|
|
|
@ -39,7 +39,7 @@ async fn test_health_compute_tokens() -> Result<(), TransportError> {
|
||||||
create_funded_account(&solana, group, owner, 0, &context.users[1], mints, 1000, 0).await;
|
create_funded_account(&solana, group, owner, 0, &context.users[1], mints, 1000, 0).await;
|
||||||
|
|
||||||
// TODO: actual explicit CU comparisons.
|
// TODO: actual explicit CU comparisons.
|
||||||
// On 2022-11-18 the final deposit costs 45495 CU and each new token increases it by roughly 1729 CU
|
// On 2022-11-29 the final deposit costs 61568 CU and each new token increases it by roughly 3125 CU
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,9 @@ async fn test_health_compute_tokens() -> Result<(), TransportError> {
|
||||||
// Try to reach compute limits in health checks by having many serum markets in an account
|
// Try to reach compute limits in health checks by having many serum markets in an account
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_health_compute_serum() -> Result<(), TransportError> {
|
async fn test_health_compute_serum() -> Result<(), TransportError> {
|
||||||
let context = TestContext::new().await;
|
let mut test_builder = TestContextBuilder::new();
|
||||||
|
test_builder.test().set_compute_max_units(80_000);
|
||||||
|
let context = test_builder.start_default().await;
|
||||||
let solana = &context.solana.clone();
|
let solana = &context.solana.clone();
|
||||||
|
|
||||||
let admin = TestKeypair::new();
|
let admin = TestKeypair::new();
|
||||||
|
@ -156,7 +158,7 @@ async fn test_health_compute_serum() -> Result<(), TransportError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: actual explicit CU comparisons.
|
// TODO: actual explicit CU comparisons.
|
||||||
// On 2022-11-18 the final deposit costs 62920 CU and each new market increases it by roughly 4820 CU
|
// On 2022-11-29 the final deposit costs 76029 CU and each new market increases it by roughly 6191 CU
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -272,7 +274,7 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: actual explicit CU comparisons.
|
// TODO: actual explicit CU comparisons.
|
||||||
// On 2022-11-18 the final deposit costs 50502 CU and each new market increases it by roughly 3037 CU
|
// On 2022-11-29 the final deposit costs 54954 CU and each new market increases it by roughly 3171 CU
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,7 +173,9 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
||||||
let context = TestContext::new().await;
|
let mut test_builder = TestContextBuilder::new();
|
||||||
|
test_builder.test().set_compute_max_units(85_000); // LiqTokenWithToken needs 79k
|
||||||
|
let context = test_builder.start_default().await;
|
||||||
let solana = &context.solana.clone();
|
let solana = &context.solana.clone();
|
||||||
|
|
||||||
let admin = TestKeypair::new();
|
let admin = TestKeypair::new();
|
||||||
|
|
Loading…
Reference in New Issue