Net borrow limits: Use correct price for check (#527)
This commit is contained in:
parent
b7189696d8
commit
312a60fe4f
|
@ -136,7 +136,7 @@ pub fn account_buyback_fees_with_mngo(
|
||||||
dao_fees_token_position,
|
dao_fees_token_position,
|
||||||
max_buyback_fees,
|
max_buyback_fees,
|
||||||
now_ts,
|
now_ts,
|
||||||
mngo_oracle_price,
|
fees_oracle_price,
|
||||||
)?;
|
)?;
|
||||||
if !in_use {
|
if !in_use {
|
||||||
dao_account.deactivate_token_position_and_log(
|
dao_account.deactivate_token_position_and_log(
|
||||||
|
|
|
@ -215,6 +215,10 @@ pub(crate) fn liquidation_action(
|
||||||
let liqor_perp_position = liqor.perp_position_mut(perp_market_index)?;
|
let liqor_perp_position = liqor.perp_position_mut(perp_market_index)?;
|
||||||
|
|
||||||
let perp_info = liqee_health_cache.perp_info(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 oracle_price = perp_info.prices.oracle;
|
||||||
let base_lot_size = I80F48::from(perp_market.base_lot_size);
|
let base_lot_size = I80F48::from(perp_market.base_lot_size);
|
||||||
let oracle_price_per_lot = base_lot_size * oracle_price;
|
let oracle_price_per_lot = base_lot_size * oracle_price;
|
||||||
|
@ -451,7 +455,7 @@ pub(crate) fn liquidation_action(
|
||||||
liqor_token_position,
|
liqor_token_position,
|
||||||
token_transfer,
|
token_transfer,
|
||||||
now_ts,
|
now_ts,
|
||||||
oracle_price,
|
settle_token_oracle_price,
|
||||||
)?;
|
)?;
|
||||||
liqee_health_cache.adjust_token_balance(&settle_bank, token_transfer)?;
|
liqee_health_cache.adjust_token_balance(&settle_bank, token_transfer)?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ use anchor_spl::token;
|
||||||
use fixed::types::I80F48;
|
use fixed::types::I80F48;
|
||||||
|
|
||||||
use crate::accounts_ix::*;
|
use crate::accounts_ix::*;
|
||||||
use crate::accounts_zerocopy::*;
|
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::health::{compute_health, new_health_cache, HealthType, ScanningAccountRetriever};
|
use crate::health::{compute_health, new_health_cache, HealthType, ScanningAccountRetriever};
|
||||||
use crate::logs::{
|
use crate::logs::{
|
||||||
|
@ -72,10 +71,14 @@ pub fn perp_liq_negative_pnl_or_bankruptcy(
|
||||||
|
|
||||||
// Get oracle price for market. Price is validated inside
|
// Get oracle price for market. Price is validated inside
|
||||||
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||||
let oracle_price = perp_market.oracle_price(
|
let oracle_price = liqee_health_cache
|
||||||
&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?,
|
.perp_info(perp_market_index)?
|
||||||
None, // staleness checked in health
|
.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();
|
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,
|
liqee_token_position,
|
||||||
settlement,
|
settlement,
|
||||||
now_ts,
|
now_ts,
|
||||||
oracle_price,
|
settle_token_oracle_price,
|
||||||
)?;
|
)?;
|
||||||
liqee_health_cache.adjust_token_balance(&settle_bank, -settlement)?;
|
liqee_health_cache.adjust_token_balance(&settle_bank, -settlement)?;
|
||||||
|
|
||||||
|
|
|
@ -28,11 +28,15 @@ pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: u64) ->
|
||||||
MangoError::InvalidBank
|
MangoError::InvalidBank
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get oracle price for market. Price is validated inside
|
// Get oracle prices
|
||||||
let oracle_price = perp_market.oracle_price(
|
let oracle_price = perp_market.oracle_price(
|
||||||
&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?,
|
&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?,
|
||||||
None, // staleness checked in health
|
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
|
// Fetch perp positions for accounts
|
||||||
let perp_position = account.perp_position_mut(perp_market.perp_market_index)?;
|
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,
|
token_position,
|
||||||
settlement,
|
settlement,
|
||||||
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||||
oracle_price,
|
settle_token_oracle_price,
|
||||||
)?;
|
)?;
|
||||||
// Update the settled balance on the market itself
|
// Update the settled balance on the market itself
|
||||||
perp_market.fees_settled += settlement;
|
perp_market.fees_settled += settlement;
|
||||||
|
|
|
@ -58,11 +58,15 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
|
||||||
MangoError::InvalidBank
|
MangoError::InvalidBank
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get oracle price for market. Price is validated inside
|
// Get oracle prices
|
||||||
let oracle_price = perp_market.oracle_price(
|
let oracle_price = perp_market.oracle_price(
|
||||||
&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?,
|
&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?,
|
||||||
None, // staleness checked in health
|
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
|
// Fetch perp position and pnl
|
||||||
let a_perp_position = account_a.perp_position_mut(perp_market_index)?;
|
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:
|
// Don't charge loan origination fees on borrows created via settling:
|
||||||
// Even small loan origination fees could accumulate if a perp position is
|
// Even small loan origination fees could accumulate if a perp position is
|
||||||
// settled back and forth repeatedly.
|
// 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 {
|
emit!(TokenBalanceLog {
|
||||||
mango_group: ctx.accounts.group.key(),
|
mango_group: ctx.accounts.group.key(),
|
||||||
|
|
|
@ -612,8 +612,8 @@ impl Bank {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if net_borrows_quote > self.net_borrow_limit_per_window_quote {
|
if net_borrows_quote > self.net_borrow_limit_per_window_quote {
|
||||||
return Err(error_msg_typed!(MangoError::BankNetBorrowsLimitReached,
|
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 ({:?}) ",
|
"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, self.net_borrow_limit_per_window_quote, self.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
|
||||||
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
reset_net_borrows().await;
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -198,6 +203,7 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
assert_eq!(bank_borrow_used().await, 5003); // loan fees & ceil
|
||||||
|
|
||||||
// fails because borrow is greater than remaining margin in net borrow limit
|
// 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!)
|
// (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
|
.await
|
||||||
.unwrap();
|
.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;
|
reset_net_borrows().await;
|
||||||
|
@ -242,7 +280,7 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError
|
||||||
send_tx(
|
send_tx(
|
||||||
solana,
|
solana,
|
||||||
TokenWithdrawInstruction {
|
TokenWithdrawInstruction {
|
||||||
amount: 1000,
|
amount: 999, // borrow limit increases more due to loan fees + ceil
|
||||||
allow_borrow: true,
|
allow_borrow: true,
|
||||||
account: account_1,
|
account: account_1,
|
||||||
owner,
|
owner,
|
||||||
|
@ -252,11 +290,12 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
assert_eq!(bank_borrow_used().await, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
set_bank_stub_oracle_price(solana, group, &tokens[0], admin, 10.0).await;
|
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(
|
let res = send_tx(
|
||||||
solana,
|
solana,
|
||||||
TokenWithdrawInstruction {
|
TokenWithdrawInstruction {
|
||||||
|
@ -277,7 +316,7 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError
|
||||||
let res = send_tx(
|
let res = send_tx(
|
||||||
solana,
|
solana,
|
||||||
TokenWithdrawInstruction {
|
TokenWithdrawInstruction {
|
||||||
amount: 201,
|
amount: 200,
|
||||||
allow_borrow: true,
|
allow_borrow: true,
|
||||||
account: account_1,
|
account: account_1,
|
||||||
owner,
|
owner,
|
||||||
|
@ -292,7 +331,7 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError
|
||||||
send_tx(
|
send_tx(
|
||||||
solana,
|
solana,
|
||||||
TokenWithdrawInstruction {
|
TokenWithdrawInstruction {
|
||||||
amount: 199,
|
amount: 198,
|
||||||
allow_borrow: true,
|
allow_borrow: true,
|
||||||
account: account_1,
|
account: account_1,
|
||||||
owner,
|
owner,
|
||||||
|
@ -302,6 +341,7 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
assert_eq!(bank_borrow_used().await, 1199);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Reference in New Issue