tcs: Change low-health-closure to absolute $1

Health ratio was too expensive to compute on-chain
This commit is contained in:
Christian Kamm 2023-08-11 17:40:33 +02:00
parent 7e804b228f
commit 727f9a2400
2 changed files with 61 additions and 37 deletions

View File

@ -12,6 +12,12 @@ use crate::logs::{
};
use crate::state::*;
/// If init health is reduced below this number, the tcs is considered done.
///
/// This avoids a situation where the same tcs can be triggered again and again
/// for small amounts every time the init health increases by small amounts.
const TCS_TRIGGER_INIT_HEALTH_THRESHOLD: u64 = 1_000_000;
#[allow(clippy::too_many_arguments)]
pub fn token_conditional_swap_trigger(
ctx: Context<TokenConditionalSwapTrigger>,
@ -76,8 +82,6 @@ pub fn token_conditional_swap_trigger(
return Ok(());
}
let liqee_pre_init_health = liqee.check_health_pre(&liqee_health_cache)?;
let (liqee_buy_change, liqee_sell_change) = action(
&mut liqor.borrow_mut(),
liqor_key,
@ -94,8 +98,7 @@ pub fn token_conditional_swap_trigger(
now_ts,
)?;
// Check liqee and liqor health
liqee.check_health_post(&liqee_health_cache, liqee_pre_init_health)?;
// Check liqor health, liqee health is checked inside (has to be, since tcs closure depends on it)
let liqor_health = compute_health(&liqor.borrow(), HealthType::Init, &account_retriever)
.context("compute liqor health")?;
require!(liqor_health >= 0, MangoError::HealthMustBePositive);
@ -187,6 +190,8 @@ fn action(
max_sell_token_to_liqor: u64,
now_ts: u64,
) -> Result<(I80F48, I80F48)> {
let liqee_pre_init_health = liqee.check_health_pre(&liqee_health_cache)?;
let tcs = liqee
.token_conditional_swap_by_index(token_conditional_swap_index)?
.clone();
@ -372,6 +377,9 @@ fn action(
liqee_health_cache.adjust_token_balance(&buy_bank, liqee_buy_change)?;
liqee_health_cache.adjust_token_balance(&sell_bank, liqee_sell_change)?;
let liqee_post_init_health =
liqee.check_health_post(&liqee_health_cache, liqee_pre_init_health)?;
// update tcs information on the account
let closed = {
// record amount
@ -403,14 +411,9 @@ fn action(
sell_bank,
);
// The health check depends on the account's health _ratio_ because it needs to work
// with liquidators trying to trigger tcs maximally: they can't bring the health exactly
// to 0 or even very close to it, because oracles will change before the transaction
// is executed. So instead, they will target a certain health ratio.
// This says, that as long as they bring the account's health ratio below 1%, we will
// consider the tcs as fully executed.
let liqee_health_is_low =
liqee_health_cache.health_ratio(HealthType::Init) < I80F48::from(1);
// If the health is low enough, close the trigger. Otherwise it'd trigger repeatedly
// as oracle prices fluctuate.
let liqee_health_is_low = liqee_post_init_health < TCS_TRIGGER_INIT_HEALTH_THRESHOLD;
if future_buy == 0 || future_sell == 0 || liqee_health_is_low {
*tcs = TokenConditionalSwap::default();
@ -762,12 +765,14 @@ mod tests {
fn test_token_conditional_swap_trigger() {
let mut setup = TestSetup::new();
let asset_pos = 100_000_000;
setup
.asset_bank
.data()
.deposit(
&mut setup.liqee.token_position_mut(0).unwrap().0,
I80F48::from(1000),
I80F48::from(asset_pos),
0,
)
.unwrap();
@ -805,7 +810,7 @@ mod tests {
assert_eq!(tcs.sold, 88);
assert_eq!(setup.liqee_liab_pos().round(), 40);
assert_eq!(setup.liqee_asset_pos().round(), 1000 - 88);
assert_eq!(setup.liqee_asset_pos().round(), asset_pos - 88);
assert_eq!(setup.liqor_liab_pos().round(), -40);
assert_eq!(setup.liqor_asset_pos().round(), 88);
@ -816,7 +821,7 @@ mod tests {
assert_eq!(setup.liqee.active_token_conditional_swaps().count(), 0);
assert_eq!(setup.liqee_liab_pos().round(), 45);
assert_eq!(setup.liqee_asset_pos().round(), 1000 - 99);
assert_eq!(setup.liqee_asset_pos().round(), asset_pos - 99);
assert_eq!(setup.liqor_liab_pos().round(), -45);
assert_eq!(setup.liqor_asset_pos().round(), 99);
}
@ -830,14 +835,14 @@ mod tests {
.data()
.deposit(
&mut setup.liqee.token_position_mut(0).unwrap().0,
I80F48::from(100),
I80F48::from(100_000_000),
0,
)
.unwrap();
let tcs = TokenConditionalSwap {
max_buy: 10000,
max_sell: 10000,
max_buy: 10_000_000_000,
max_sell: 10_000_000_000,
price_lower_limit: 1.0,
price_upper_limit: 3.0,
price_premium_rate: 0.0,
@ -849,21 +854,40 @@ mod tests {
..Default::default()
};
*setup.liqee.free_token_conditional_swap_mut().unwrap() = tcs.clone();
let (buy_change, sell_change) = setup.trigger(2.0, 1000, 1.0, 1000).unwrap();
assert_eq!(buy_change.round(), 500);
assert_eq!(sell_change.round(), -1000);
// Overall health went negative, causing the tcs to close (even though max_buy/max_sell aren't reached)
let (buy_change, sell_change) = setup.trigger(2.0, 50_000_000, 1.0, 50_000_000).unwrap();
assert_eq!(buy_change.round(), 25_000_000);
assert_eq!(sell_change.round(), -50_000_000);
// Not closed yet, health still good
assert_eq!(setup.liqee.active_token_conditional_swaps().count(), 1);
let (buy_change, sell_change) = setup.trigger(2.0, 150_000_000, 1.0, 150_000_000).unwrap();
assert_eq!(buy_change.round(), 75_000_000);
assert_eq!(sell_change.round(), -150_000_000);
// Health is 0
assert_eq!(setup.liqee.active_token_conditional_swaps().count(), 0);
assert_eq!(setup.liqee_liab_pos().round(), 500);
assert_eq!(setup.liqee_asset_pos().round(), -900);
assert_eq!(setup.liqee_liab_pos().round(), 100_000_000);
assert_eq!(setup.liqee_asset_pos().round(), -100_000_000);
}
#[test]
fn test_token_conditional_swap_trigger_fees() {
let mut setup = TestSetup::new();
let asset_pos = 100_000_000;
setup
.asset_bank
.data()
.deposit(
&mut setup.liqee.token_position_mut(0).unwrap().0,
I80F48::from(asset_pos),
0,
)
.unwrap();
let tcs = TokenConditionalSwap {
max_buy: 1000,
max_sell: 1000,
@ -887,7 +911,7 @@ mod tests {
assert_eq!(sell_change.round(), -1000);
assert_eq!(setup.liqee_liab_pos().round(), 952);
assert_eq!(setup.liqee_asset_pos().round(), -1000);
assert_eq!(setup.liqee_asset_pos().round(), asset_pos - 1000);
assert_eq!(setup.liqor_liab_pos().round(), -952);
assert_eq!(setup.liqor_asset_pos().round(), 923); // floor(952*1.02*0.95)

View File

@ -27,7 +27,7 @@ async fn test_token_conditional_swap() -> Result<(), TransportError> {
let quote_token = &tokens[0];
let base_token = &tokens[1];
let deposit_amount = 1000;
let deposit_amount = 1_000_000_000f64;
let account = create_funded_account(
&solana,
group,
@ -35,7 +35,7 @@ async fn test_token_conditional_swap() -> Result<(), TransportError> {
0,
&context.users[1],
mints,
deposit_amount,
deposit_amount as u64,
0,
)
.await;
@ -46,7 +46,7 @@ async fn test_token_conditional_swap() -> Result<(), TransportError> {
1,
&context.users[1],
mints,
deposit_amount,
deposit_amount as u64,
0,
)
.await;
@ -113,7 +113,7 @@ async fn test_token_conditional_swap() -> Result<(), TransportError> {
assert_eq!(account_data.header.token_conditional_swap_count, 2);
//
// TEST: Can create tsls until all slots are filled
// TEST: Can create tcs until all slots are filled
//
let tcs_ix = TokenConditionalSwapCreateInstruction {
account,
@ -246,15 +246,15 @@ async fn test_token_conditional_swap() -> Result<(), TransportError> {
let liqee_base = account_position_f64(solana, account, base_token.bank).await;
assert!(assert_equal_f_f(
liqee_quote,
1000.0 + 42.0, // roughly 50 / (1.1 * 1.1)
deposit_amount + 42.0, // roughly 50 / (1.1 * 1.1)
0.01
));
assert!(assert_equal_f_f(liqee_base, 1000.0 - 50.0, 0.01));
assert!(assert_equal_f_f(liqee_base, deposit_amount - 50.0, 0.01));
let liqor_quote = account_position_f64(solana, liqor, quote_token.bank).await;
let liqor_base = account_position_f64(solana, liqor, base_token.bank).await;
assert!(assert_equal_f_f(liqor_quote, 1000.0 - 42.0, 0.01));
assert!(assert_equal_f_f(liqor_base, 1000.0 + 44.0, 0.01)); // roughly 42*1.1*0.95
assert!(assert_equal_f_f(liqor_quote, deposit_amount - 42.0, 0.01));
assert!(assert_equal_f_f(liqor_base, deposit_amount + 44.0, 0.01)); // roughly 42*1.1*0.95
//
// TEST: trigger fully
@ -275,13 +275,13 @@ async fn test_token_conditional_swap() -> Result<(), TransportError> {
let liqee_quote = account_position_f64(solana, account, quote_token.bank).await;
let liqee_base = account_position_f64(solana, account, base_token.bank).await;
assert!(assert_equal_f_f(liqee_quote, 1000.0 + 84.0, 0.01));
assert!(assert_equal_f_f(liqee_base, 1000.0 - 100.0, 0.01));
assert!(assert_equal_f_f(liqee_quote, deposit_amount + 84.0, 0.01));
assert!(assert_equal_f_f(liqee_base, deposit_amount - 100.0, 0.01));
let liqor_quote = account_position_f64(solana, liqor, quote_token.bank).await;
let liqor_base = account_position_f64(solana, liqor, base_token.bank).await;
assert!(assert_equal_f_f(liqor_quote, 1000.0 - 84.0, 0.01));
assert!(assert_equal_f_f(liqor_base, 1000.0 + 88.0, 0.01));
assert!(assert_equal_f_f(liqor_quote, deposit_amount - 84.0, 0.01));
assert!(assert_equal_f_f(liqor_base, deposit_amount + 88.0, 0.01));
let account_data = get_mango_account(solana, account).await;
assert!(!account_data