Bank: track deposits in serum oo accounts (#769)
This commit is contained in:
parent
2910fd1cfa
commit
26549ffd92
|
@ -1454,6 +1454,7 @@ mod tests {
|
|||
borrows: u64,
|
||||
deposit_weight_scale_start_quote: u64,
|
||||
borrow_weight_scale_start_quote: u64,
|
||||
deposits_in_serum: i64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -1509,6 +1510,7 @@ mod tests {
|
|||
let bank = bank.data();
|
||||
bank.indexed_deposits = I80F48::from(settings.deposits) / bank.deposit_index;
|
||||
bank.indexed_borrows = I80F48::from(settings.borrows) / bank.borrow_index;
|
||||
bank.deposits_in_serum = settings.deposits_in_serum;
|
||||
if settings.deposit_weight_scale_start_quote > 0 {
|
||||
bank.deposit_weight_scale_start_quote =
|
||||
settings.deposit_weight_scale_start_quote as f64;
|
||||
|
@ -1807,6 +1809,55 @@ mod tests {
|
|||
}),
|
||||
..Default::default()
|
||||
},
|
||||
TestHealth1Case {
|
||||
// 16, base case for 17
|
||||
token1: 100,
|
||||
token2: 100,
|
||||
token3: 100,
|
||||
oo_1_2: (0, 100),
|
||||
oo_1_3: (0, 100),
|
||||
expected_health:
|
||||
// tokens
|
||||
100.0 * 0.8 + 100.0 * 5.0 * 0.5 + 100.0 * 10.0 * 0.5
|
||||
// oo_1_2 (-> token2)
|
||||
+ 100.0 * 5.0 * 0.5
|
||||
// oo_1_3 (-> token1)
|
||||
+ 100.0 * 10.0 * 0.5,
|
||||
..Default::default()
|
||||
},
|
||||
TestHealth1Case {
|
||||
// 17, deposits_in_serum counts for deposit weight scaling
|
||||
token1: 100,
|
||||
token2: 100,
|
||||
token3: 100,
|
||||
oo_1_2: (0, 100),
|
||||
oo_1_3: (0, 100),
|
||||
bank_settings: [
|
||||
BankSettings {
|
||||
..BankSettings::default()
|
||||
},
|
||||
BankSettings {
|
||||
deposits: 100,
|
||||
deposit_weight_scale_start_quote: 100 * 5,
|
||||
deposits_in_serum: 100,
|
||||
..BankSettings::default()
|
||||
},
|
||||
BankSettings {
|
||||
deposits: 600,
|
||||
deposit_weight_scale_start_quote: 500 * 10,
|
||||
deposits_in_serum: 100,
|
||||
..BankSettings::default()
|
||||
},
|
||||
],
|
||||
expected_health:
|
||||
// tokens
|
||||
100.0 * 0.8 + 100.0 * 5.0 * 0.5 * (100.0 / 200.0) + 100.0 * 10.0 * 0.5 * (500.0 / 700.0)
|
||||
// oo_1_2 (-> token2)
|
||||
+ 100.0 * 5.0 * 0.5 * (100.0 / 200.0)
|
||||
// oo_1_3 (-> token1)
|
||||
+ 100.0 * 10.0 * 0.5 * (500.0 / 700.0),
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
|
||||
for (i, testcase) in testcases.iter().enumerate() {
|
||||
|
|
|
@ -372,27 +372,39 @@ fn apply_vault_difference(
|
|||
}
|
||||
let native_after = position.native(bank);
|
||||
let native_change = native_after - native_before;
|
||||
// amount of tokens transfered to serum3 reserved that were borrowed
|
||||
let new_borrows = native_change
|
||||
.max(native_after)
|
||||
.min(I80F48::ZERO)
|
||||
.abs()
|
||||
.to_num::<u64>();
|
||||
// amount of tokens transferred to serum3 reserved that were taken from deposits
|
||||
let used_deposits = native_before
|
||||
.min(-needed_change)
|
||||
.max(I80F48::ZERO)
|
||||
.to_num::<u64>();
|
||||
|
||||
let indexed_position = position.indexed_position;
|
||||
let market = account.serum3_orders_mut(serum_market_index).unwrap();
|
||||
let borrows_without_fee = if bank.token_index == market.base_token_index {
|
||||
&mut market.base_borrows_without_fee
|
||||
let borrows_without_fee;
|
||||
let deposits_reserved;
|
||||
if bank.token_index == market.base_token_index {
|
||||
borrows_without_fee = &mut market.base_borrows_without_fee;
|
||||
deposits_reserved = &mut market.base_deposits_reserved;
|
||||
} else if bank.token_index == market.quote_token_index {
|
||||
&mut market.quote_borrows_without_fee
|
||||
borrows_without_fee = &mut market.quote_borrows_without_fee;
|
||||
deposits_reserved = &mut market.quote_deposits_reserved;
|
||||
} else {
|
||||
return Err(error_msg!(
|
||||
"assert failed: apply_vault_difference called with bad token index"
|
||||
));
|
||||
};
|
||||
|
||||
// Only for place: Add to potential borrow amount
|
||||
let old_value = *borrows_without_fee;
|
||||
*borrows_without_fee = old_value + new_borrows;
|
||||
// Only for place: Add to potential borrow amount and reserved deposits
|
||||
*borrows_without_fee += new_borrows;
|
||||
*deposits_reserved += used_deposits;
|
||||
let used_deposits_signed: i64 = used_deposits.try_into().unwrap();
|
||||
bank.deposits_in_serum += used_deposits_signed;
|
||||
|
||||
// Only for settle/liq_force_cancel: Reduce the potential borrow amounts
|
||||
if needed_change > 0 {
|
||||
|
@ -485,6 +497,28 @@ pub fn apply_settle_changes(
|
|||
before_quote_vault,
|
||||
)?;
|
||||
|
||||
// Tokens were moved from open orders into banks again: also update the tracking
|
||||
// for deposits_in_serum on the banks.
|
||||
{
|
||||
let serum_orders = account.serum3_orders_mut(serum_market.market_index)?;
|
||||
|
||||
let after_base_reserved = after_oo.native_base_reserved();
|
||||
if after_base_reserved < serum_orders.base_deposits_reserved {
|
||||
let diff = serum_orders.base_deposits_reserved - after_base_reserved;
|
||||
serum_orders.base_deposits_reserved = after_base_reserved;
|
||||
let diff_signed: i64 = diff.try_into().unwrap();
|
||||
base_bank.deposits_in_serum -= diff_signed;
|
||||
}
|
||||
|
||||
let after_quote_reserved = after_oo.native_quote_reserved();
|
||||
if after_quote_reserved < serum_orders.quote_deposits_reserved {
|
||||
let diff = serum_orders.quote_deposits_reserved - after_quote_reserved;
|
||||
serum_orders.quote_deposits_reserved = after_quote_reserved;
|
||||
let diff_signed: i64 = diff.try_into().unwrap();
|
||||
quote_bank.deposits_in_serum -= diff_signed;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(health_cache) = health_cache {
|
||||
base_difference.adjust_health_cache_token_balance(health_cache, &base_bank)?;
|
||||
quote_difference.adjust_health_cache_token_balance(health_cache, "e_bank)?;
|
||||
|
|
|
@ -112,7 +112,8 @@ pub fn token_register(
|
|||
flash_loan_swap_fee_rate: flash_loan_swap_fee_rate,
|
||||
interest_target_utilization,
|
||||
interest_curve_scaling: interest_curve_scaling.into(),
|
||||
reserved: [0; 2080],
|
||||
deposits_in_serum: 0,
|
||||
reserved: [0; 2072],
|
||||
};
|
||||
|
||||
if let Ok(oracle_price) =
|
||||
|
|
|
@ -96,7 +96,8 @@ pub fn token_register_trustless(
|
|||
flash_loan_swap_fee_rate: 0.0005,
|
||||
interest_target_utilization: 0.5,
|
||||
interest_curve_scaling: 4.0,
|
||||
reserved: [0; 2080],
|
||||
deposits_in_serum: 0,
|
||||
reserved: [0; 2072],
|
||||
};
|
||||
|
||||
if let Ok(oracle_price) =
|
||||
|
|
|
@ -135,6 +135,7 @@ pub struct Bank {
|
|||
pub reduce_only: u8,
|
||||
pub force_close: u8,
|
||||
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub padding: [u8; 6],
|
||||
|
||||
// Do separate bookkeping for how many tokens were withdrawn
|
||||
|
@ -156,8 +157,12 @@ pub struct Bank {
|
|||
/// Except when first migrating to having this field, then 0.0
|
||||
pub interest_curve_scaling: f64,
|
||||
|
||||
// user deposits that were moved into serum open orders
|
||||
// can be negative due to multibank, then it'd need to be balanced in the keeper
|
||||
pub deposits_in_serum: i64,
|
||||
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub reserved: [u8; 2080],
|
||||
pub reserved: [u8; 2072],
|
||||
}
|
||||
const_assert_eq!(
|
||||
size_of::<Bank>(),
|
||||
|
@ -189,8 +194,8 @@ const_assert_eq!(
|
|||
+ 6
|
||||
+ 8
|
||||
+ 4 * 4
|
||||
+ 8
|
||||
+ 2080
|
||||
+ 8 * 2
|
||||
+ 2072
|
||||
);
|
||||
const_assert_eq!(size_of::<Bank>(), 3064);
|
||||
const_assert_eq!(size_of::<Bank>() % 8, 0);
|
||||
|
@ -225,6 +230,7 @@ impl Bank {
|
|||
flash_loan_approved_amount: 0,
|
||||
flash_loan_token_account_initial: u64::MAX,
|
||||
net_borrows_in_window: 0,
|
||||
deposits_in_serum: 0,
|
||||
bump,
|
||||
bank_num,
|
||||
|
||||
|
@ -273,7 +279,7 @@ impl Bank {
|
|||
flash_loan_swap_fee_rate: existing_bank.flash_loan_swap_fee_rate,
|
||||
interest_target_utilization: existing_bank.interest_target_utilization,
|
||||
interest_curve_scaling: existing_bank.interest_curve_scaling,
|
||||
reserved: [0; 2080],
|
||||
reserved: [0; 2072],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -927,8 +933,8 @@ impl Bank {
|
|||
if self.deposit_weight_scale_start_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>();
|
||||
let all_deposits = self.native_deposits().to_num::<f64>() + self.deposits_in_serum as f64;
|
||||
let deposits_quote = all_deposits * price.to_num::<f64>();
|
||||
if deposits_quote <= self.deposit_weight_scale_start_quote {
|
||||
self.init_asset_weight
|
||||
} else {
|
||||
|
@ -943,7 +949,6 @@ impl Bank {
|
|||
if self.borrow_weight_scale_start_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_weight_scale_start_quote {
|
||||
self.init_liab_weight
|
||||
|
|
|
@ -145,12 +145,20 @@ pub struct Serum3Orders {
|
|||
pub highest_placed_bid_inv: f64,
|
||||
pub lowest_placed_ask: f64,
|
||||
|
||||
/// Tracks the amount of deposits that flowed into the serum open orders account.
|
||||
///
|
||||
/// The bank still considers these amounts user deposits (see deposits_in_serum)
|
||||
/// and they need to be deducted from there when they flow back into the bank
|
||||
/// as real tokens.
|
||||
pub base_deposits_reserved: u64,
|
||||
pub quote_deposits_reserved: u64,
|
||||
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub reserved: [u8; 48],
|
||||
pub reserved: [u8; 32],
|
||||
}
|
||||
const_assert_eq!(
|
||||
size_of::<Serum3Orders>(),
|
||||
32 + 8 * 2 + 2 * 3 + 2 + 2 * 8 + 48
|
||||
32 + 8 * 2 + 2 * 3 + 2 + 4 * 8 + 32
|
||||
);
|
||||
const_assert_eq!(size_of::<Serum3Orders>(), 120);
|
||||
const_assert_eq!(size_of::<Serum3Orders>() % 8, 0);
|
||||
|
@ -177,7 +185,9 @@ impl Default for Serum3Orders {
|
|||
quote_borrows_without_fee: 0,
|
||||
highest_placed_bid_inv: 0.0,
|
||||
lowest_placed_ask: 0.0,
|
||||
reserved: [0; 48],
|
||||
base_deposits_reserved: 0,
|
||||
quote_deposits_reserved: 0,
|
||||
reserved: [0; 32],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -341,6 +341,13 @@ async fn test_serum_basics() -> Result<(), TransportError> {
|
|||
let serum_orders = account_data.serum3_orders_by_raw_index(0).unwrap();
|
||||
assert_eq!(serum_orders.base_borrows_without_fee, 0);
|
||||
assert_eq!(serum_orders.quote_borrows_without_fee, 0);
|
||||
assert_eq!(serum_orders.base_deposits_reserved, 0);
|
||||
assert_eq!(serum_orders.quote_deposits_reserved, 100);
|
||||
|
||||
let base_bank = solana.get_account::<Bank>(base_token.bank).await;
|
||||
assert_eq!(base_bank.deposits_in_serum, 0);
|
||||
let quote_bank = solana.get_account::<Bank>(quote_token.bank).await;
|
||||
assert_eq!(quote_bank.deposits_in_serum, 100);
|
||||
|
||||
assert!(order_id != 0);
|
||||
|
||||
|
@ -359,6 +366,18 @@ async fn test_serum_basics() -> Result<(), TransportError> {
|
|||
assert_eq!(native0, 1000);
|
||||
assert_eq!(native1, 1000);
|
||||
|
||||
let account_data = get_mango_account(solana, account).await;
|
||||
let serum_orders = account_data.serum3_orders_by_raw_index(0).unwrap();
|
||||
assert_eq!(serum_orders.base_borrows_without_fee, 0);
|
||||
assert_eq!(serum_orders.quote_borrows_without_fee, 0);
|
||||
assert_eq!(serum_orders.base_deposits_reserved, 0);
|
||||
assert_eq!(serum_orders.quote_deposits_reserved, 0);
|
||||
|
||||
let base_bank = solana.get_account::<Bank>(base_token.bank).await;
|
||||
assert_eq!(base_bank.deposits_in_serum, 0);
|
||||
let quote_bank = solana.get_account::<Bank>(quote_token.bank).await;
|
||||
assert_eq!(quote_bank.deposits_in_serum, 0);
|
||||
|
||||
// Process events such that the OutEvent deactivates the closed order on open_orders
|
||||
context
|
||||
.serum
|
||||
|
@ -1256,6 +1275,89 @@ async fn test_serum_track_bid_ask() -> Result<(), TransportError> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_serum_track_reserved_deposits() -> Result<(), TransportError> {
|
||||
let mut test_builder = TestContextBuilder::new();
|
||||
test_builder.test().set_compute_max_units(150_000); // Serum3PlaceOrder needs lots
|
||||
let context = test_builder.start_default().await;
|
||||
|
||||
//
|
||||
// SETUP: Create a group, accounts, market etc
|
||||
//
|
||||
let deposit_amount = 100000;
|
||||
let CommonSetup {
|
||||
serum_market_cookie,
|
||||
quote_token,
|
||||
base_token,
|
||||
mut order_placer,
|
||||
mut order_placer2,
|
||||
..
|
||||
} = common_setup(&context, deposit_amount).await;
|
||||
let solana = &context.solana.clone();
|
||||
let quote_bank = quote_token.bank;
|
||||
let base_bank = base_token.bank;
|
||||
let account = order_placer.account;
|
||||
|
||||
let get_vals = |solana| async move {
|
||||
let account_data = get_mango_account(solana, account).await;
|
||||
let orders = account_data.all_serum3_orders().next().unwrap();
|
||||
let base_bank = solana.get_account::<Bank>(base_bank).await;
|
||||
let quote_bank = solana.get_account::<Bank>(quote_bank).await;
|
||||
(
|
||||
orders.base_deposits_reserved,
|
||||
base_bank.deposits_in_serum,
|
||||
orders.quote_deposits_reserved,
|
||||
quote_bank.deposits_in_serum,
|
||||
)
|
||||
};
|
||||
|
||||
//
|
||||
// TEST: place a bid and ask and observe tracking
|
||||
//
|
||||
|
||||
order_placer.bid_maker(0.8, 2000).await.unwrap();
|
||||
order_placer.ask(1.2, 2000).await.unwrap();
|
||||
assert_eq!(get_vals(solana).await, (2000, 2000, 1600, 1600));
|
||||
|
||||
//
|
||||
// TEST: match partially on both sides, increasing the on-bank reserved amounts
|
||||
// because order_placer2 puts funds into the serum oo
|
||||
//
|
||||
|
||||
order_placer2.bid_taker(1.2, 1000).await.unwrap();
|
||||
context
|
||||
.serum
|
||||
.consume_spot_events(
|
||||
&serum_market_cookie,
|
||||
&[order_placer.open_orders, order_placer2.open_orders],
|
||||
)
|
||||
.await;
|
||||
assert_eq!(get_vals(solana).await, (2000, 2000, 1600, 2801));
|
||||
order_placer2.settle_v2(false).await;
|
||||
assert_eq!(get_vals(solana).await, (2000, 2000, 1600, 1600));
|
||||
|
||||
order_placer2.ask(0.8, 1000).await.unwrap();
|
||||
context
|
||||
.serum
|
||||
.consume_spot_events(
|
||||
&serum_market_cookie,
|
||||
&[order_placer.open_orders, order_placer2.open_orders],
|
||||
)
|
||||
.await;
|
||||
assert_eq!(get_vals(solana).await, (2000, 3000, 1600, 1600));
|
||||
order_placer2.settle_v2(false).await;
|
||||
assert_eq!(get_vals(solana).await, (2000, 2000, 1600, 1600));
|
||||
|
||||
//
|
||||
// TEST: Settlement updates the values
|
||||
//
|
||||
|
||||
order_placer.settle_v2(false).await;
|
||||
assert_eq!(get_vals(solana).await, (1000, 1000, 800, 800));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct CommonSetup {
|
||||
group_with_tokens: GroupWithTokens,
|
||||
serum_market_cookie: SpotMarketCookie,
|
||||
|
|
Loading…
Reference in New Issue