Bank: track deposits in serum oo accounts (#769)

This commit is contained in:
Christian Kamm 2023-11-02 10:40:31 +01:00 committed by GitHub
parent 2910fd1cfa
commit 26549ffd92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 222 additions and 18 deletions

View File

@ -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() {

View File

@ -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, &quote_bank)?;

View File

@ -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) =

View File

@ -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) =

View File

@ -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

View File

@ -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],
}
}
}

View File

@ -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,