Net borrow limits: Use correct price for check (#527)

This commit is contained in:
Christian Kamm 2023-03-30 17:25:29 +02:00 committed by GitHub
parent b7189696d8
commit 312a60fe4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 78 additions and 18 deletions

View File

@ -136,7 +136,7 @@ pub fn account_buyback_fees_with_mngo(
dao_fees_token_position,
max_buyback_fees,
now_ts,
mngo_oracle_price,
fees_oracle_price,
)?;
if !in_use {
dao_account.deactivate_token_position_and_log(

View File

@ -215,6 +215,10 @@ pub(crate) fn liquidation_action(
let liqor_perp_position = liqor.perp_position_mut(perp_market_index)?;
let perp_info = liqee_health_cache.perp_info(perp_market_index)?;
let settle_token_oracle_price = liqee_health_cache
.token_info(settle_token_index)?
.prices
.oracle;
let oracle_price = perp_info.prices.oracle;
let base_lot_size = I80F48::from(perp_market.base_lot_size);
let oracle_price_per_lot = base_lot_size * oracle_price;
@ -451,7 +455,7 @@ pub(crate) fn liquidation_action(
liqor_token_position,
token_transfer,
now_ts,
oracle_price,
settle_token_oracle_price,
)?;
liqee_health_cache.adjust_token_balance(&settle_bank, token_transfer)?;
}

View File

@ -4,7 +4,6 @@ use anchor_spl::token;
use fixed::types::I80F48;
use crate::accounts_ix::*;
use crate::accounts_zerocopy::*;
use crate::error::*;
use crate::health::{compute_health, new_health_cache, HealthType, ScanningAccountRetriever};
use crate::logs::{
@ -72,10 +71,14 @@ pub fn perp_liq_negative_pnl_or_bankruptcy(
// Get oracle price for market. Price is validated inside
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
let oracle_price = perp_market.oracle_price(
&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?,
None, // staleness checked in health
)?;
let oracle_price = liqee_health_cache
.perp_info(perp_market_index)?
.prices
.oracle;
let settle_token_oracle_price = liqee_health_cache
.token_info(settle_token_index)?
.prices
.oracle;
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
@ -126,7 +129,7 @@ pub fn perp_liq_negative_pnl_or_bankruptcy(
liqee_token_position,
settlement,
now_ts,
oracle_price,
settle_token_oracle_price,
)?;
liqee_health_cache.adjust_token_balance(&settle_bank, -settlement)?;

View File

@ -28,11 +28,15 @@ pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: u64) ->
MangoError::InvalidBank
);
// Get oracle price for market. Price is validated inside
// Get oracle prices
let oracle_price = perp_market.oracle_price(
&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?,
None, // staleness checked in health
)?;
let settle_token_oracle_price = settle_bank.oracle_price(
&AccountInfoRef::borrow(ctx.accounts.settle_oracle.as_ref())?,
None, // staleness checked in health
)?;
// Fetch perp positions for accounts
let perp_position = account.perp_position_mut(perp_market.perp_market_index)?;
@ -92,7 +96,7 @@ pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: u64) ->
token_position,
settlement,
Clock::get()?.unix_timestamp.try_into().unwrap(),
oracle_price,
settle_token_oracle_price,
)?;
// Update the settled balance on the market itself
perp_market.fees_settled += settlement;

View File

@ -58,11 +58,15 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
MangoError::InvalidBank
);
// Get oracle price for market. Price is validated inside
// Get oracle prices
let oracle_price = perp_market.oracle_price(
&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?,
None, // staleness checked in health
)?;
let settle_token_oracle_price = settle_bank.oracle_price(
&AccountInfoRef::borrow(ctx.accounts.settle_oracle.as_ref())?,
None, // staleness checked in health
)?;
// Fetch perp position and pnl
let a_perp_position = account_a.perp_position_mut(perp_market_index)?;
@ -172,7 +176,12 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
// Don't charge loan origination fees on borrows created via settling:
// Even small loan origination fees could accumulate if a perp position is
// settled back and forth repeatedly.
settle_bank.withdraw_without_fee(b_token_position, settlement, now_ts, oracle_price)?;
settle_bank.withdraw_without_fee(
b_token_position,
settlement,
now_ts,
settle_token_oracle_price,
)?;
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),

View File

@ -612,8 +612,8 @@ impl Bank {
.unwrap();
if net_borrows_quote > self.net_borrow_limit_per_window_quote {
return Err(error_msg_typed!(MangoError::BankNetBorrowsLimitReached,
"net_borrows_in_window ({:?}) exceeds net_borrow_limit_per_window_quote ({:?}) for last_net_borrows_window_start_ts ({:?}) ",
self.net_borrows_in_window, self.net_borrow_limit_per_window_quote, self.last_net_borrows_window_start_ts
"net_borrows_in_window ({:?}) valued at ({:?} exceed net_borrow_limit_per_window_quote ({:?}) for last_net_borrows_window_start_ts ({:?}) ",
self.net_borrows_in_window, net_borrows_quote, self.net_borrow_limit_per_window_quote, self.last_net_borrows_window_start_ts
));
}

View File

@ -153,6 +153,11 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError
}
};
let bank_borrow_used = || {
let bank = tokens[0].bank;
async move { solana.get_account::<Bank>(bank).await.net_borrows_in_window }
};
reset_net_borrows().await;
//
@ -198,6 +203,7 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError
)
.await
.unwrap();
assert_eq!(bank_borrow_used().await, 5003); // loan fees & ceil
// fails because borrow is greater than remaining margin in net borrow limit
// (requires the test to be quick enough to avoid accidentally going to the next borrow limit window!)
@ -229,6 +235,38 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError
)
.await
.unwrap();
// depositing reduces usage, but only the repayment part
send_tx(
solana,
TokenDepositInstruction {
amount: 7000,
account: account_1,
owner,
token_authority: payer,
token_account: payer_mint_accounts[0],
bank_index: 0,
reduce_only: false,
},
)
.await
.unwrap();
assert_eq!(bank_borrow_used().await, 1); // due to rounding
// give account1 a negative token0 balance again
send_tx(
solana,
TokenWithdrawInstruction {
amount: 5000,
allow_borrow: true,
account: account_1,
owner,
token_account: payer_mint_accounts[0],
bank_index: 0,
},
)
.await
.unwrap();
}
reset_net_borrows().await;
@ -242,7 +280,7 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError
send_tx(
solana,
TokenWithdrawInstruction {
amount: 1000,
amount: 999, // borrow limit increases more due to loan fees + ceil
allow_borrow: true,
account: account_1,
owner,
@ -252,11 +290,12 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError
)
.await
.unwrap();
assert_eq!(bank_borrow_used().await, 1000);
}
set_bank_stub_oracle_price(solana, group, &tokens[0], admin, 10.0).await;
// cannot borrow anything: net borrowed 1000 * price 10.0 > limit 6000
// cannot borrow anything: net borrowed 1002 * price 10.0 > limit 6000
let res = send_tx(
solana,
TokenWithdrawInstruction {
@ -277,7 +316,7 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError
let res = send_tx(
solana,
TokenWithdrawInstruction {
amount: 201,
amount: 200,
allow_borrow: true,
account: account_1,
owner,
@ -292,7 +331,7 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError
send_tx(
solana,
TokenWithdrawInstruction {
amount: 199,
amount: 198,
allow_borrow: true,
account: account_1,
owner,
@ -302,6 +341,7 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError
)
.await
.unwrap();
assert_eq!(bank_borrow_used().await, 1199);
}
Ok(())