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
|
||||
}
|
||||
|
||||
#[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<()> {
|
||||
// 101000
|
||||
// 477
|
||||
|
@ -98,6 +131,8 @@ pub fn benchmark(_ctx: Context<Benchmark>) -> Result<()> {
|
|||
division_i128(a.to_bits(), b.to_bits()); // 100 - 2000 CU
|
||||
division_i80f48_30bit(a, b); // 300 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
|
||||
}
|
||||
|
||||
{
|
||||
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
|
||||
msg!("msg!"); // 100079+101 -> 203
|
||||
sol_log_compute_units(); // 100117
|
||||
|
|
|
@ -218,7 +218,7 @@ pub fn serum3_liq_force_cancel_orders(
|
|||
after_base_vault,
|
||||
before_base_vault,
|
||||
)?
|
||||
.adjust_health_cache(&mut health_cache)?;
|
||||
.adjust_health_cache(&mut health_cache, &base_bank)?;
|
||||
apply_vault_difference(
|
||||
ctx.accounts.account.key(),
|
||||
&mut account.borrow_mut(),
|
||||
|
@ -227,7 +227,7 @@ pub fn serum3_liq_force_cancel_orders(
|
|||
after_quote_vault,
|
||||
before_quote_vault,
|
||||
)?
|
||||
.adjust_health_cache(&mut health_cache)?;
|
||||
.adjust_health_cache(&mut health_cache, "e_bank)?;
|
||||
|
||||
//
|
||||
// Health check at the end
|
||||
|
|
|
@ -340,8 +340,8 @@ pub fn serum3_place_order(
|
|||
require_gte!(before_vault, after_vault);
|
||||
|
||||
// Charge the difference in vault balance to the user's account
|
||||
let vault_difference = {
|
||||
let mut payer_bank = ctx.accounts.payer_bank.load_mut()?;
|
||||
let vault_difference = {
|
||||
apply_vault_difference(
|
||||
ctx.accounts.account.key(),
|
||||
&mut account.borrow_mut(),
|
||||
|
@ -356,7 +356,7 @@ pub fn serum3_place_order(
|
|||
// Health check
|
||||
//
|
||||
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)?;
|
||||
account.check_health_post(&health_cache, pre_health)?;
|
||||
}
|
||||
|
@ -408,8 +408,9 @@ pub struct VaultDifference {
|
|||
}
|
||||
|
||||
impl VaultDifference {
|
||||
pub fn adjust_health_cache(&self, health_cache: &mut HealthCache) -> Result<()> {
|
||||
health_cache.adjust_token_balance(self.token_index, self.native_change)?;
|
||||
pub fn adjust_health_cache(&self, health_cache: &mut HealthCache, bank: &Bank) -> Result<()> {
|
||||
assert_eq!(bank.token_index, self.token_index);
|
||||
health_cache.adjust_token_balance(bank, self.native_change)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -297,7 +297,7 @@ pub fn token_liq_bankruptcy(
|
|||
let liab_bank = bank_ais[0].load::<Bank>()?;
|
||||
let end_liab_native = liqee_liab.native(&liab_bank);
|
||||
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
|
||||
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);
|
||||
|
||||
// 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(
|
||||
liab_token_index,
|
||||
cm!(liqee_liab_native_after - liqee_liab_native),
|
||||
)?;
|
||||
liqee_health_cache.adjust_token_balance(
|
||||
asset_token_index,
|
||||
&asset_bank,
|
||||
cm!(liqee_assets_native_after - liqee_asset_native),
|
||||
)?;
|
||||
|
||||
|
|
|
@ -151,7 +151,9 @@ pub fn token_register(
|
|||
* net_borrows_window_size_ts,
|
||||
net_borrows_limit_native,
|
||||
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);
|
||||
|
||||
|
|
|
@ -126,7 +126,9 @@ pub fn token_register_trustless(
|
|||
* net_borrows_window_size_ts,
|
||||
net_borrows_limit_native: 1_000_000,
|
||||
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);
|
||||
|
||||
|
|
|
@ -146,8 +146,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
|
|||
// Health check
|
||||
//
|
||||
if let Some((mut health_cache, pre_health)) = pre_health_opt {
|
||||
health_cache
|
||||
.adjust_token_balance(token_index, cm!(native_position_after - native_position))?;
|
||||
health_cache.adjust_token_balance(&bank, cm!(native_position_after - native_position))?;
|
||||
account.check_health_post(&health_cache, pre_health)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -113,8 +113,25 @@ pub struct Bank {
|
|||
pub net_borrows_limit_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")]
|
||||
pub reserved: [u8; 2136],
|
||||
pub reserved: [u8; 2120],
|
||||
}
|
||||
const_assert_eq!(size_of::<Bank>(), 3112);
|
||||
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,
|
||||
last_net_borrows_window_start_ts: existing_bank.last_net_borrows_window_start_ts,
|
||||
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))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
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 {
|
||||
self.deposit_index * self.indexed_deposits
|
||||
cm!(self.deposit_index * self.indexed_deposits)
|
||||
}
|
||||
|
||||
/// Deposits `native_amount`.
|
||||
|
@ -692,6 +713,47 @@ impl Bank {
|
|||
pub fn stable_price(&self) -> I80F48 {
|
||||
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]
|
||||
|
|
|
@ -5,7 +5,7 @@ use fixed_macro::types::I80F48;
|
|||
|
||||
use crate::error::*;
|
||||
use crate::state::{
|
||||
MangoAccountFixed, MangoAccountRef, PerpMarket, PerpMarketIndex, PerpPosition,
|
||||
Bank, MangoAccountFixed, MangoAccountRef, PerpMarket, PerpMarketIndex, PerpPosition,
|
||||
Serum3MarketIndex, TokenIndex,
|
||||
};
|
||||
use crate::util::checked_math as cm;
|
||||
|
@ -365,10 +365,15 @@ impl HealthCache {
|
|||
.ok_or_else(|| error_msg!("token index {} not found", token_index))
|
||||
}
|
||||
|
||||
pub fn adjust_token_balance(&mut self, token_index: TokenIndex, change: I80F48) -> Result<()> {
|
||||
let entry_index = self.token_info_index(token_index)?;
|
||||
/// Changes the cached user account token balance.
|
||||
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];
|
||||
|
||||
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:
|
||||
// We need to make sure that if balance is before * price, then change = -before
|
||||
// brings it to exactly zero.
|
||||
|
@ -377,6 +382,20 @@ impl HealthCache {
|
|||
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(
|
||||
&mut self,
|
||||
market_index: Serum3MarketIndex,
|
||||
|
@ -604,16 +623,17 @@ pub fn new_health_cache(
|
|||
retriever.bank_and_oracle(&account.fixed.group, i, position.token_index)?;
|
||||
|
||||
let native = position.native(bank);
|
||||
let prices = Prices {
|
||||
oracle: oracle_price,
|
||||
stable: bank.stable_price(),
|
||||
};
|
||||
token_infos.push(TokenInfo {
|
||||
token_index: bank.token_index,
|
||||
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,
|
||||
init_liab_weight: bank.init_liab_weight,
|
||||
prices: Prices {
|
||||
oracle: oracle_price,
|
||||
stable: bank.stable_price(),
|
||||
},
|
||||
init_liab_weight: bank.scaled_init_liab_weight(prices.liab(HealthType::Init)),
|
||||
prices,
|
||||
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)]
|
||||
struct TestHealth1Case {
|
||||
token1: i64,
|
||||
|
@ -796,6 +824,7 @@ mod tests {
|
|||
oo_1_3: (u64, u64),
|
||||
perp1: (i64, i64, i64, i64),
|
||||
expected_health: f64,
|
||||
bank_settings: [BankSettings; 3],
|
||||
}
|
||||
fn test_health1_runner(testcase: &TestHealth1Case) {
|
||||
let buffer = MangoAccount::default_for_tests().try_to_vec().unwrap();
|
||||
|
@ -830,6 +859,21 @@ mod tests {
|
|||
DUMMY_NOW_TS,
|
||||
)
|
||||
.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 serum3account1 = account.create_serum3_orders(2).unwrap();
|
||||
|
@ -995,6 +1039,66 @@ mod tests {
|
|||
+ 20.0 * 0.8,
|
||||
..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() {
|
||||
|
|
|
@ -6,7 +6,7 @@ use fixed::types::I80F48;
|
|||
use fixed_macro::types::I80F48;
|
||||
|
||||
use crate::error::*;
|
||||
use crate::state::{PerpMarketIndex, TokenIndex};
|
||||
use crate::state::{Bank, MangoAccountValue, PerpMarketIndex};
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
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
|
||||
/// above the min_ratio health ratio.
|
||||
///
|
||||
|
@ -54,8 +92,9 @@ impl HealthCache {
|
|||
/// NOTE: keep getMaxSourceForTokenSwap in ts/client in sync with changes here
|
||||
pub fn max_swap_source_for_health_ratio(
|
||||
&self,
|
||||
source: TokenIndex,
|
||||
target: TokenIndex,
|
||||
account: &MangoAccountValue,
|
||||
source_bank: &Bank,
|
||||
target_bank: &Bank,
|
||||
price: I80F48,
|
||||
min_ratio: I80F48,
|
||||
) -> Result<I80F48> {
|
||||
|
@ -74,8 +113,8 @@ impl HealthCache {
|
|||
return Ok(I80F48::ZERO);
|
||||
}
|
||||
|
||||
let source_index = find_token_info_index(&self.token_infos, source)?;
|
||||
let target_index = find_token_info_index(&self.token_infos, target)?;
|
||||
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_bank.token_index)?;
|
||||
let source = &self.token_infos[source_index];
|
||||
let target = &self.token_infos[target_index];
|
||||
|
||||
|
@ -89,10 +128,7 @@ impl HealthCache {
|
|||
}
|
||||
|
||||
let cache_after_swap = |amount: I80F48| {
|
||||
let mut adjusted_cache = self.clone();
|
||||
adjusted_cache.token_infos[source_index].balance_native -= amount;
|
||||
adjusted_cache.token_infos[target_index].balance_native += cm!(amount * price);
|
||||
adjusted_cache
|
||||
self.cache_after_swap(account, source_bank, target_bank, amount, price)
|
||||
};
|
||||
let health_ratio_after_swap =
|
||||
|amount| cache_after_swap(amount).health_ratio(HealthType::Init);
|
||||
|
@ -379,6 +415,15 @@ mod tests {
|
|||
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 {
|
||||
token_infos: vec![
|
||||
TokenInfo {
|
||||
|
@ -407,8 +452,9 @@ mod tests {
|
|||
assert_eq!(
|
||||
health_cache
|
||||
.max_swap_source_for_health_ratio(
|
||||
0,
|
||||
1,
|
||||
&account,
|
||||
banks[0],
|
||||
banks[1],
|
||||
I80F48::from_num(2.0 / 3.0),
|
||||
I80F48::from_num(50.0)
|
||||
)
|
||||
|
@ -425,15 +471,17 @@ mod tests {
|
|||
target: TokenIndex,
|
||||
ratio: f64,
|
||||
price_factor: f64| {
|
||||
let mut c = c.clone();
|
||||
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_bank = &banks[target as usize];
|
||||
let swap_price =
|
||||
I80F48::from_num(price_factor) * source_price.oracle / target_price.oracle;
|
||||
let source_amount = c
|
||||
.max_swap_source_for_health_ratio(
|
||||
source,
|
||||
target,
|
||||
&account,
|
||||
source_bank,
|
||||
target_bank,
|
||||
swap_price,
|
||||
I80F48::from_num(ratio),
|
||||
)
|
||||
|
@ -441,12 +489,16 @@ mod tests {
|
|||
if source_amount == I80F48::MAX {
|
||||
return (f64::MAX, f64::MAX);
|
||||
}
|
||||
c.adjust_token_balance(source, -source_amount).unwrap();
|
||||
c.adjust_token_balance(target, source_amount * swap_price)
|
||||
.unwrap();
|
||||
let after_swap = c.cache_after_swap(
|
||||
&account,
|
||||
source_bank,
|
||||
target_bank,
|
||||
source_amount,
|
||||
swap_price,
|
||||
);
|
||||
(
|
||||
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,
|
||||
|
|
|
@ -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_liab_weight = I80F48::from_num(1.0 + maint_weights);
|
||||
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_limit_native = i64::MAX; // max since we don't want this to interfere
|
||||
(bank, oracle)
|
||||
|
|
|
@ -13,7 +13,9 @@ mod program_test;
|
|||
|
||||
#[tokio::test]
|
||||
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 admin = TestKeypair::new();
|
||||
|
@ -275,7 +277,9 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
|
|||
|
||||
#[tokio::test]
|
||||
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 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;
|
||||
|
||||
// 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(())
|
||||
}
|
||||
|
@ -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
|
||||
#[tokio::test]
|
||||
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 admin = TestKeypair::new();
|
||||
|
@ -156,7 +158,7 @@ async fn test_health_compute_serum() -> Result<(), TransportError> {
|
|||
}
|
||||
|
||||
// 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(())
|
||||
}
|
||||
|
@ -272,7 +274,7 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
|
|||
}
|
||||
|
||||
// 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(())
|
||||
}
|
||||
|
|
|
@ -173,7 +173,9 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> {
|
|||
|
||||
#[tokio::test]
|
||||
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 admin = TestKeypair::new();
|
||||
|
|
Loading…
Reference in New Issue