Net borrow limits: Limit in quote, not native (#312)
* Net borrow limits: Limit in quote, not native * make perp settle limit disableable
This commit is contained in:
parent
cf34a5b4b7
commit
c8f1f3c821
|
@ -602,6 +602,7 @@ impl MangoClient {
|
|||
open_orders,
|
||||
payer_bank: payer_mint_info.first_bank(),
|
||||
payer_vault: payer_mint_info.first_vault(),
|
||||
payer_oracle: payer_mint_info.oracle,
|
||||
serum_market: s3.market.address,
|
||||
serum_program: s3.market.market.serum_program,
|
||||
serum_market_external: s3.market.market.serum_market_external,
|
||||
|
|
|
@ -360,8 +360,8 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
let health_cache = new_health_cache(&account.borrow(), &retriever)?;
|
||||
let pre_health = account.check_health_pre(&health_cache)?;
|
||||
|
||||
// Prices for logging
|
||||
let mut prices = vec![];
|
||||
// Prices for logging and net borrow checks
|
||||
let mut oracle_prices = vec![];
|
||||
for change in &changes {
|
||||
let (_, oracle_price) = retriever.bank_and_oracle(
|
||||
&account.fixed.group,
|
||||
|
@ -369,7 +369,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
change.token_index,
|
||||
)?;
|
||||
|
||||
prices.push(oracle_price);
|
||||
oracle_prices.push(oracle_price);
|
||||
}
|
||||
// Drop retriever as mut bank below uses health_ais
|
||||
drop(retriever);
|
||||
|
@ -377,7 +377,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
// Apply the vault diffs to the bank positions
|
||||
let mut deactivated_token_positions = vec![];
|
||||
let mut token_loan_details = Vec::with_capacity(changes.len());
|
||||
for (change, price) in changes.iter().zip(prices.iter()) {
|
||||
for (change, oracle_price) in changes.iter().zip(oracle_prices.iter()) {
|
||||
let mut bank = health_ais[change.bank_index].load_mut::<Bank>()?;
|
||||
let position = account.token_position_mut_by_raw_index(change.raw_token_index);
|
||||
let native = position.native(&bank);
|
||||
|
@ -396,6 +396,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
position,
|
||||
cm!(change.amount - loan_origination_fee),
|
||||
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||
*oracle_price,
|
||||
)?;
|
||||
if !is_active {
|
||||
deactivated_token_positions.push(change.raw_token_index);
|
||||
|
@ -411,7 +412,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
loan_origination_fee: loan_origination_fee.to_bits(),
|
||||
deposit_index: bank.deposit_index.to_bits(),
|
||||
borrow_index: bank.borrow_index.to_bits(),
|
||||
price: price.to_bits(),
|
||||
price: oracle_price.to_bits(),
|
||||
});
|
||||
|
||||
emit!(TokenBalanceLog {
|
||||
|
|
|
@ -70,7 +70,7 @@ pub fn perp_create_market(
|
|||
settle_fee_amount_threshold: f32,
|
||||
settle_fee_fraction_low_health: f32,
|
||||
settle_pnl_limit_factor: f32,
|
||||
settle_pnl_limit_factor_window_size_ts: u64,
|
||||
settle_pnl_limit_window_size_ts: u64,
|
||||
) -> Result<()> {
|
||||
// Settlement tokens that aren't USDC aren't fully implemented, the main missing steps are:
|
||||
// - In health: the perp health needs to be adjusted by the settlement token weights.
|
||||
|
@ -127,7 +127,7 @@ pub fn perp_create_market(
|
|||
settle_fee_fraction_low_health,
|
||||
stable_price_model: StablePriceModel::default(),
|
||||
settle_pnl_limit_factor,
|
||||
settle_pnl_limit_factor_window_size_ts,
|
||||
settle_pnl_limit_window_size_ts: settle_pnl_limit_window_size_ts,
|
||||
reserved: [0; 1944],
|
||||
};
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ pub fn perp_edit_market(
|
|||
stable_price_delay_growth_limit_opt: Option<f32>,
|
||||
stable_price_growth_limit_opt: Option<f32>,
|
||||
settle_pnl_limit_factor_opt: Option<f32>,
|
||||
settle_pnl_limit_factor_window_size_ts_opt: Option<u64>,
|
||||
settle_pnl_limit_window_size_ts_opt: Option<u64>,
|
||||
) -> Result<()> {
|
||||
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||
|
||||
|
@ -167,9 +167,8 @@ pub fn perp_edit_market(
|
|||
if let Some(settle_pnl_limit_factor_opt) = settle_pnl_limit_factor_opt {
|
||||
perp_market.settle_pnl_limit_factor = settle_pnl_limit_factor_opt;
|
||||
}
|
||||
if let Some(settle_pnl_limit_factor_window_size_ts) = settle_pnl_limit_factor_window_size_ts_opt
|
||||
{
|
||||
perp_market.settle_pnl_limit_factor_window_size_ts = settle_pnl_limit_factor_window_size_ts;
|
||||
if let Some(settle_pnl_limit_window_size_ts) = settle_pnl_limit_window_size_ts_opt {
|
||||
perp_market.settle_pnl_limit_window_size_ts = settle_pnl_limit_window_size_ts;
|
||||
}
|
||||
|
||||
emit!(PerpMarketMetaDataLog {
|
||||
|
|
|
@ -111,6 +111,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,
|
||||
)?;
|
||||
// Update the settled balance on the market itself
|
||||
perp_market.fees_settled = cm!(perp_market.fees_settled + settlement);
|
||||
|
|
|
@ -130,7 +130,7 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
|
|||
a_perp_position.update_and_get_used_settle_limit(&perp_market, now_ts);
|
||||
b_perp_position.update_and_get_used_settle_limit(&perp_market, now_ts);
|
||||
|
||||
let a_settleable_pnl = {
|
||||
let a_settleable_pnl = if perp_market.settle_pnl_limit_factor >= 0.0 {
|
||||
let realized_pnl = a_perp_position.realized_pnl_native;
|
||||
let unrealized_pnl = cm!(a_pnl - realized_pnl);
|
||||
let a_base_lots = I80F48::from(a_perp_position.base_position_lots());
|
||||
|
@ -146,6 +146,8 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
|
|||
a_pnl
|
||||
.min(cm!(realized_pnl + unrealized_pnl_capped_for_window))
|
||||
.max(I80F48::ZERO)
|
||||
} else {
|
||||
a_pnl
|
||||
};
|
||||
|
||||
require!(
|
||||
|
@ -219,7 +221,7 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
|
|||
let a_token_position = account_a.token_position_mut(settle_token_index)?.0;
|
||||
let b_token_position = account_b.token_position_mut(settle_token_index)?.0;
|
||||
bank.deposit(a_token_position, cm!(settlement - fee), now_ts)?;
|
||||
bank.withdraw_with_fee(b_token_position, settlement, now_ts)?;
|
||||
bank.withdraw_with_fee(b_token_position, settlement, now_ts, oracle_price)?;
|
||||
|
||||
emit!(TokenBalanceLog {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
|
|
|
@ -217,6 +217,7 @@ pub fn serum3_liq_force_cancel_orders(
|
|||
&mut base_bank,
|
||||
after_base_vault,
|
||||
before_base_vault,
|
||||
None, // guaranteed to deposit into bank
|
||||
)?
|
||||
.adjust_health_cache(&mut health_cache, &base_bank)?;
|
||||
apply_vault_difference(
|
||||
|
@ -226,6 +227,7 @@ pub fn serum3_liq_force_cancel_orders(
|
|||
&mut quote_bank,
|
||||
after_quote_vault,
|
||||
before_quote_vault,
|
||||
None, // guaranteed to deposit into bank
|
||||
)?
|
||||
.adjust_health_cache(&mut health_cache, "e_bank)?;
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::accounts_zerocopy::AccountInfoRef;
|
||||
use crate::error::*;
|
||||
|
||||
use crate::logs::{Serum3OpenOrdersBalanceLog, TokenBalanceLog};
|
||||
|
@ -190,6 +191,9 @@ pub struct Serum3PlaceOrder<'info> {
|
|||
/// The bank vault that pays for the order, if necessary
|
||||
#[account(mut)]
|
||||
pub payer_vault: Box<Account<'info, TokenAccount>>,
|
||||
/// CHECK: The oracle can be one of several different account types
|
||||
#[account(address = payer_bank.load()?.oracle)]
|
||||
pub payer_oracle: UncheckedAccount<'info>,
|
||||
|
||||
pub token_program: Program<'info, Token>,
|
||||
}
|
||||
|
@ -342,6 +346,8 @@ pub fn serum3_place_order(
|
|||
// Charge the difference in vault balance to the user's account
|
||||
let mut payer_bank = ctx.accounts.payer_bank.load_mut()?;
|
||||
let vault_difference = {
|
||||
let oracle_price =
|
||||
payer_bank.oracle_price(&AccountInfoRef::borrow(&ctx.accounts.payer_oracle)?, None)?;
|
||||
apply_vault_difference(
|
||||
ctx.accounts.account.key(),
|
||||
&mut account.borrow_mut(),
|
||||
|
@ -349,6 +355,7 @@ pub fn serum3_place_order(
|
|||
&mut payer_bank,
|
||||
after_vault,
|
||||
before_vault,
|
||||
Some(oracle_price),
|
||||
)?
|
||||
};
|
||||
|
||||
|
@ -425,16 +432,23 @@ pub fn apply_vault_difference(
|
|||
bank: &mut Bank,
|
||||
vault_after: u64,
|
||||
vault_before: u64,
|
||||
oracle_price: Option<I80F48>,
|
||||
) -> Result<VaultDifference> {
|
||||
let needed_change = cm!(I80F48::from(vault_after) - I80F48::from(vault_before));
|
||||
|
||||
let (position, _) = account.token_position_mut(bank.token_index)?;
|
||||
let native_before = position.native(bank);
|
||||
bank.change_without_fee(
|
||||
position,
|
||||
needed_change,
|
||||
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||
)?;
|
||||
let now_ts = Clock::get()?.unix_timestamp.try_into().unwrap();
|
||||
if needed_change >= 0 {
|
||||
bank.deposit(position, needed_change, now_ts)?;
|
||||
} else {
|
||||
bank.withdraw_without_fee(
|
||||
position,
|
||||
-needed_change,
|
||||
now_ts,
|
||||
oracle_price.unwrap(), // required for withdraws
|
||||
)?;
|
||||
}
|
||||
let native_after = position.native(bank);
|
||||
let native_change = cm!(native_after - native_before);
|
||||
let new_borrows = native_change
|
||||
|
|
|
@ -165,6 +165,10 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
|
|||
&mut base_bank,
|
||||
after_base_vault,
|
||||
before_base_vault,
|
||||
// Since after >= before, we know this can be a deposit
|
||||
// and no net borrow check will be necessary, meaning
|
||||
// we don't need an oracle price.
|
||||
None,
|
||||
)?;
|
||||
apply_vault_difference(
|
||||
ctx.accounts.account.key(),
|
||||
|
@ -173,6 +177,7 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
|
|||
&mut quote_bank,
|
||||
after_quote_vault,
|
||||
before_quote_vault,
|
||||
None,
|
||||
)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -50,8 +50,10 @@ pub fn token_edit(
|
|||
stable_price_delay_growth_limit_opt: Option<f32>,
|
||||
stable_price_growth_limit_opt: Option<f32>,
|
||||
min_vault_to_deposits_ratio_opt: Option<f64>,
|
||||
net_borrows_limit_native_opt: Option<i64>,
|
||||
net_borrows_limit_quote_opt: Option<i64>,
|
||||
net_borrows_window_size_ts_opt: Option<u64>,
|
||||
reset_stable_price: bool,
|
||||
reset_net_borrow_limit: bool,
|
||||
) -> Result<()> {
|
||||
let mut mint_info = ctx.accounts.mint_info.load_mut()?;
|
||||
mint_info.verify_banks_ais(ctx.remaining_accounts)?;
|
||||
|
@ -75,8 +77,9 @@ pub fn token_edit(
|
|||
if let Some(oracle) = oracle_opt {
|
||||
bank.oracle = oracle;
|
||||
mint_info.oracle = oracle;
|
||||
|
||||
require_keys_eq!(oracle, ctx.accounts.oracle.key());
|
||||
}
|
||||
if reset_stable_price {
|
||||
require_keys_eq!(bank.oracle, ctx.accounts.oracle.key());
|
||||
let oracle_price =
|
||||
bank.oracle_price(&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?, None)?;
|
||||
bank.stable_price_model.reset_to_price(
|
||||
|
@ -148,12 +151,16 @@ pub fn token_edit(
|
|||
if let Some(min_vault_to_deposits_ratio) = min_vault_to_deposits_ratio_opt {
|
||||
bank.min_vault_to_deposits_ratio = min_vault_to_deposits_ratio;
|
||||
}
|
||||
if let Some(net_borrows_limit_native) = net_borrows_limit_native_opt {
|
||||
bank.net_borrows_limit_native = net_borrows_limit_native;
|
||||
if let Some(net_borrows_limit_quote) = net_borrows_limit_quote_opt {
|
||||
bank.net_borrows_limit_quote = net_borrows_limit_quote;
|
||||
}
|
||||
if let Some(net_borrows_window_size_ts) = net_borrows_window_size_ts_opt {
|
||||
bank.net_borrows_window_size_ts = net_borrows_window_size_ts;
|
||||
}
|
||||
if reset_net_borrow_limit {
|
||||
bank.net_borrows_in_window = 0;
|
||||
bank.last_net_borrows_window_start_ts = 0;
|
||||
}
|
||||
|
||||
// unchanged -
|
||||
// dust
|
||||
|
|
|
@ -188,7 +188,7 @@ pub fn token_liq_bankruptcy(
|
|||
let (liqor_liab, liqor_liab_raw_token_index, _) =
|
||||
liqor.ensure_token_position(liab_token_index)?;
|
||||
let (liqor_liab_active, loan_origination_fee) =
|
||||
liab_bank.withdraw_with_fee(liqor_liab, liab_transfer, now_ts)?;
|
||||
liab_bank.withdraw_with_fee(liqor_liab, liab_transfer, now_ts, liab_price)?;
|
||||
|
||||
// liqor liab
|
||||
emit!(TokenBalanceLog {
|
||||
|
|
|
@ -164,6 +164,7 @@ pub fn token_liq_with_token(
|
|||
liqor_liab_position,
|
||||
liab_transfer,
|
||||
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||
liab_price,
|
||||
)?;
|
||||
let liqor_liab_indexed_position = liqor_liab_position.indexed_position;
|
||||
let liqee_liab_native_after = liqee_liab_position.native(liab_bank);
|
||||
|
@ -182,6 +183,7 @@ pub fn token_liq_with_token(
|
|||
liqee_asset_position,
|
||||
asset_transfer,
|
||||
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||
asset_price,
|
||||
)?;
|
||||
let liqee_asset_indexed_position = liqee_asset_position.indexed_position;
|
||||
let liqee_assets_native_after = liqee_asset_position.native(asset_bank);
|
||||
|
|
|
@ -92,7 +92,7 @@ pub fn token_register(
|
|||
liquidation_fee: f32,
|
||||
min_vault_to_deposits_ratio: f64,
|
||||
net_borrows_window_size_ts: u64,
|
||||
net_borrows_limit_native: i64,
|
||||
net_borrows_limit_quote: i64,
|
||||
) -> Result<()> {
|
||||
// Require token 0 to be in the insurance token
|
||||
if token_index == QUOTE_TOKEN_INDEX {
|
||||
|
@ -149,8 +149,8 @@ pub fn token_register(
|
|||
net_borrows_window_size_ts,
|
||||
last_net_borrows_window_start_ts: now_ts / net_borrows_window_size_ts
|
||||
* net_borrows_window_size_ts,
|
||||
net_borrows_limit_native,
|
||||
net_borrows_window_native: 0,
|
||||
net_borrows_limit_quote,
|
||||
net_borrows_in_window: 0,
|
||||
borrow_limit_quote: f64::MAX,
|
||||
collateral_limit_quote: f64::MAX,
|
||||
reserved: [0; 2120],
|
||||
|
|
|
@ -124,8 +124,8 @@ pub fn token_register_trustless(
|
|||
net_borrows_window_size_ts,
|
||||
last_net_borrows_window_start_ts: now_ts / net_borrows_window_size_ts
|
||||
* net_borrows_window_size_ts,
|
||||
net_borrows_limit_native: 1_000_000,
|
||||
net_borrows_window_native: 0,
|
||||
net_borrows_limit_quote: 1_000_000_000_000, // 1M USD
|
||||
net_borrows_in_window: 0,
|
||||
borrow_limit_quote: f64::MAX,
|
||||
collateral_limit_quote: f64::MAX,
|
||||
reserved: [0; 2120],
|
||||
|
|
|
@ -98,11 +98,18 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
|
|||
|
||||
let amount_i80f48 = I80F48::from(amount);
|
||||
|
||||
let now_slot = Clock::get()?.slot;
|
||||
let oracle_price = bank.oracle_price(
|
||||
&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?,
|
||||
Some(now_slot),
|
||||
)?;
|
||||
|
||||
// Update the bank and position
|
||||
let (position_is_active, loan_origination_fee) = bank.withdraw_with_fee(
|
||||
position,
|
||||
amount_i80f48,
|
||||
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||
oracle_price,
|
||||
)?;
|
||||
|
||||
// Provide a readable error message in case the vault doesn't have enough tokens
|
||||
|
@ -123,11 +130,6 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
|
|||
)?;
|
||||
|
||||
let native_position_after = position.native(&bank);
|
||||
let now_slot = Clock::get()?.slot;
|
||||
let oracle_price = bank.oracle_price(
|
||||
&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?,
|
||||
Some(now_slot),
|
||||
)?;
|
||||
|
||||
emit!(TokenBalanceLog {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
|
|
|
@ -76,7 +76,7 @@ pub mod mango_v4 {
|
|||
liquidation_fee: f32,
|
||||
min_vault_to_deposits_ratio: f64,
|
||||
net_borrows_window_size_ts: u64,
|
||||
net_borrows_limit_native: i64,
|
||||
net_borrows_limit_quote: i64,
|
||||
) -> Result<()> {
|
||||
instructions::token_register(
|
||||
ctx,
|
||||
|
@ -93,7 +93,7 @@ pub mod mango_v4 {
|
|||
liquidation_fee,
|
||||
min_vault_to_deposits_ratio,
|
||||
net_borrows_window_size_ts,
|
||||
net_borrows_limit_native,
|
||||
net_borrows_limit_quote,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -123,8 +123,10 @@ pub mod mango_v4 {
|
|||
stable_price_delay_growth_limit_opt: Option<f32>,
|
||||
stable_price_growth_limit_opt: Option<f32>,
|
||||
min_vault_to_deposits_ratio_opt: Option<f64>,
|
||||
net_borrows_limit_native_opt: Option<i64>,
|
||||
net_borrows_limit_quote_opt: Option<i64>,
|
||||
net_borrows_window_size_ts_opt: Option<u64>,
|
||||
reset_stable_price: bool,
|
||||
reset_net_borrow_limit: bool,
|
||||
) -> Result<()> {
|
||||
instructions::token_edit(
|
||||
ctx,
|
||||
|
@ -143,8 +145,10 @@ pub mod mango_v4 {
|
|||
stable_price_delay_growth_limit_opt,
|
||||
stable_price_growth_limit_opt,
|
||||
min_vault_to_deposits_ratio_opt,
|
||||
net_borrows_limit_native_opt,
|
||||
net_borrows_limit_quote_opt,
|
||||
net_borrows_window_size_ts_opt,
|
||||
reset_stable_price,
|
||||
reset_net_borrow_limit,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -425,7 +429,7 @@ pub mod mango_v4 {
|
|||
settle_fee_fraction_low_health: f32,
|
||||
settle_token_index: TokenIndex,
|
||||
settle_pnl_limit_factor: f32,
|
||||
settle_pnl_limit_factor_window_size_ts: u64,
|
||||
settle_pnl_limit_window_size_ts: u64,
|
||||
) -> Result<()> {
|
||||
instructions::perp_create_market(
|
||||
ctx,
|
||||
|
@ -453,7 +457,7 @@ pub mod mango_v4 {
|
|||
settle_fee_amount_threshold,
|
||||
settle_fee_fraction_low_health,
|
||||
settle_pnl_limit_factor,
|
||||
settle_pnl_limit_factor_window_size_ts,
|
||||
settle_pnl_limit_window_size_ts,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -483,7 +487,7 @@ pub mod mango_v4 {
|
|||
stable_price_delay_growth_limit_opt: Option<f32>,
|
||||
stable_price_growth_limit_opt: Option<f32>,
|
||||
settle_pnl_limit_factor_opt: Option<f32>,
|
||||
settle_pnl_limit_factor_window_size_ts: Option<u64>,
|
||||
settle_pnl_limit_window_size_ts: Option<u64>,
|
||||
) -> Result<()> {
|
||||
instructions::perp_edit_market(
|
||||
ctx,
|
||||
|
@ -510,7 +514,7 @@ pub mod mango_v4 {
|
|||
stable_price_delay_growth_limit_opt,
|
||||
stable_price_growth_limit_opt,
|
||||
settle_pnl_limit_factor_opt,
|
||||
settle_pnl_limit_factor_window_size_ts,
|
||||
settle_pnl_limit_window_size_ts,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -107,11 +107,17 @@ pub struct Bank {
|
|||
|
||||
pub stable_price_model: StablePriceModel,
|
||||
|
||||
/// Min fraction of deposits that must remain in the vault when borrowing.
|
||||
pub min_vault_to_deposits_ratio: f64,
|
||||
|
||||
/// Size in seconds of a net borrows window
|
||||
pub net_borrows_window_size_ts: u64,
|
||||
/// Timestamp at which the last net borrows window started
|
||||
pub last_net_borrows_window_start_ts: u64,
|
||||
pub net_borrows_limit_native: i64,
|
||||
pub net_borrows_window_native: i64,
|
||||
/// Net borrow limit per window in quote native; set to -1 to disable.
|
||||
pub net_borrows_limit_quote: i64,
|
||||
/// Sum of all deposits and borrows in the last window, in native units.
|
||||
pub net_borrows_in_window: i64,
|
||||
|
||||
/// Soft borrow limit in native quote
|
||||
///
|
||||
|
@ -188,10 +194,10 @@ impl Bank {
|
|||
oracle_config: existing_bank.oracle_config.clone(),
|
||||
stable_price_model: StablePriceModel::default(),
|
||||
min_vault_to_deposits_ratio: existing_bank.min_vault_to_deposits_ratio,
|
||||
net_borrows_limit_native: existing_bank.net_borrows_limit_native,
|
||||
net_borrows_limit_quote: existing_bank.net_borrows_limit_quote,
|
||||
net_borrows_window_size_ts: existing_bank.net_borrows_window_size_ts,
|
||||
last_net_borrows_window_start_ts: existing_bank.last_net_borrows_window_start_ts,
|
||||
net_borrows_window_native: 0,
|
||||
net_borrows_in_window: 0,
|
||||
borrow_limit_quote: f64::MAX,
|
||||
collateral_limit_quote: f64::MAX,
|
||||
reserved: [0; 2120],
|
||||
|
@ -251,7 +257,7 @@ impl Bank {
|
|||
allow_dusting: bool,
|
||||
now_ts: u64,
|
||||
) -> Result<bool> {
|
||||
self.update_net_borrows(-native_amount, now_ts)?;
|
||||
self.update_net_borrows(-native_amount, now_ts, None)?;
|
||||
let opening_indexed_position = position.indexed_position;
|
||||
let result = self.deposit_internal(position, native_amount, allow_dusting)?;
|
||||
self.update_cumulative_interest(position, opening_indexed_position);
|
||||
|
@ -331,6 +337,7 @@ impl Bank {
|
|||
position: &mut TokenPosition,
|
||||
native_amount: I80F48,
|
||||
now_ts: u64,
|
||||
oracle_price: I80F48,
|
||||
) -> Result<bool> {
|
||||
let (position_is_active, _) = self.withdraw_internal_wrapper(
|
||||
position,
|
||||
|
@ -338,6 +345,7 @@ impl Bank {
|
|||
false,
|
||||
!position.is_in_use(),
|
||||
now_ts,
|
||||
Some(oracle_price),
|
||||
)?;
|
||||
|
||||
Ok(position_is_active)
|
||||
|
@ -351,9 +359,17 @@ impl Bank {
|
|||
position: &mut TokenPosition,
|
||||
native_amount: I80F48,
|
||||
now_ts: u64,
|
||||
oracle_price: I80F48,
|
||||
) -> Result<bool> {
|
||||
self.withdraw_internal_wrapper(position, native_amount, false, true, now_ts)
|
||||
.map(|(not_dusted, _)| not_dusted || position.is_in_use())
|
||||
self.withdraw_internal_wrapper(
|
||||
position,
|
||||
native_amount,
|
||||
false,
|
||||
true,
|
||||
now_ts,
|
||||
Some(oracle_price),
|
||||
)
|
||||
.map(|(not_dusted, _)| not_dusted || position.is_in_use())
|
||||
}
|
||||
|
||||
/// Withdraws `native_amount` while applying the loan origination fee if a borrow is created.
|
||||
|
@ -369,8 +385,16 @@ impl Bank {
|
|||
position: &mut TokenPosition,
|
||||
native_amount: I80F48,
|
||||
now_ts: u64,
|
||||
oracle_price: I80F48,
|
||||
) -> Result<(bool, I80F48)> {
|
||||
self.withdraw_internal_wrapper(position, native_amount, true, !position.is_in_use(), now_ts)
|
||||
self.withdraw_internal_wrapper(
|
||||
position,
|
||||
native_amount,
|
||||
true,
|
||||
!position.is_in_use(),
|
||||
now_ts,
|
||||
Some(oracle_price),
|
||||
)
|
||||
}
|
||||
|
||||
/// Internal function to withdraw funds
|
||||
|
@ -381,6 +405,7 @@ impl Bank {
|
|||
with_loan_origination_fee: bool,
|
||||
allow_dusting: bool,
|
||||
now_ts: u64,
|
||||
oracle_price: Option<I80F48>,
|
||||
) -> Result<(bool, I80F48)> {
|
||||
let opening_indexed_position = position.indexed_position;
|
||||
let res = self.withdraw_internal(
|
||||
|
@ -389,6 +414,7 @@ impl Bank {
|
|||
with_loan_origination_fee,
|
||||
allow_dusting,
|
||||
now_ts,
|
||||
oracle_price,
|
||||
);
|
||||
self.update_cumulative_interest(position, opening_indexed_position);
|
||||
res
|
||||
|
@ -402,6 +428,7 @@ impl Bank {
|
|||
with_loan_origination_fee: bool,
|
||||
allow_dusting: bool,
|
||||
now_ts: u64,
|
||||
oracle_price: Option<I80F48>,
|
||||
) -> Result<(bool, I80F48)> {
|
||||
require_gte!(native_amount, 0);
|
||||
let native_position = position.native(self);
|
||||
|
@ -446,7 +473,7 @@ impl Bank {
|
|||
|
||||
// net borrows requires updating in only this case, since other branches of the method deal with
|
||||
// withdraws and not borrows
|
||||
self.update_net_borrows(native_amount, now_ts)?;
|
||||
self.update_net_borrows(native_amount, now_ts, oracle_price)?;
|
||||
|
||||
Ok((true, loan_origination_fee))
|
||||
}
|
||||
|
@ -468,6 +495,7 @@ impl Bank {
|
|||
false,
|
||||
!position.is_in_use(),
|
||||
now_ts,
|
||||
None,
|
||||
)?;
|
||||
|
||||
Ok((position_is_active, loan_origination_fee))
|
||||
|
@ -479,11 +507,12 @@ impl Bank {
|
|||
position: &mut TokenPosition,
|
||||
native_amount: I80F48,
|
||||
now_ts: u64,
|
||||
oracle_price: I80F48,
|
||||
) -> Result<bool> {
|
||||
if native_amount >= 0 {
|
||||
self.deposit(position, native_amount, now_ts)
|
||||
} else {
|
||||
self.withdraw_without_fee(position, -native_amount, now_ts)
|
||||
self.withdraw_without_fee(position, -native_amount, now_ts, oracle_price)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -493,34 +522,53 @@ impl Bank {
|
|||
position: &mut TokenPosition,
|
||||
native_amount: I80F48,
|
||||
now_ts: u64,
|
||||
oracle_price: I80F48,
|
||||
) -> Result<(bool, I80F48)> {
|
||||
if native_amount >= 0 {
|
||||
Ok((self.deposit(position, native_amount, now_ts)?, I80F48::ZERO))
|
||||
} else {
|
||||
self.withdraw_with_fee(position, -native_amount, now_ts)
|
||||
self.withdraw_with_fee(position, -native_amount, now_ts, oracle_price)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_net_borrows(&mut self, native_amount: I80F48, now_ts: u64) -> Result<()> {
|
||||
/// Update the bank's net_borrows fields.
|
||||
///
|
||||
/// If oracle_price is set, also do a net borrows check and error if the threshold is exceeded.
|
||||
pub fn update_net_borrows(
|
||||
&mut self,
|
||||
native_amount: I80F48,
|
||||
now_ts: u64,
|
||||
oracle_price: Option<I80F48>,
|
||||
) -> Result<()> {
|
||||
let in_new_window =
|
||||
now_ts >= self.last_net_borrows_window_start_ts + self.net_borrows_window_size_ts;
|
||||
|
||||
self.net_borrows_window_native = if in_new_window {
|
||||
self.net_borrows_in_window = if in_new_window {
|
||||
// reset to latest window
|
||||
self.last_net_borrows_window_start_ts =
|
||||
now_ts / self.net_borrows_window_size_ts * self.net_borrows_window_size_ts;
|
||||
native_amount.checked_to_num::<i64>().unwrap()
|
||||
} else {
|
||||
cm!(self.net_borrows_window_native + native_amount.checked_to_num().unwrap())
|
||||
cm!(self.net_borrows_in_window + native_amount.checked_to_num().unwrap())
|
||||
};
|
||||
|
||||
if self.net_borrows_window_native > self.net_borrows_limit_native {
|
||||
return err!(MangoError::BankNetBorrowsLimitReached).with_context(|| {
|
||||
format!(
|
||||
"net_borrows_window_native ({:?}) exceeds net_borrows_limit_native ({:?}) for last_net_borrows_window_start_ts ({:?}) ",
|
||||
self.net_borrows_window_native, self.net_borrows_limit_native, self.last_net_borrows_window_start_ts
|
||||
)
|
||||
});
|
||||
if native_amount < 0 || self.net_borrows_limit_quote < 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(oracle_price) = oracle_price {
|
||||
let price = oracle_price.max(self.stable_price());
|
||||
let net_borrows_quote = price
|
||||
.checked_mul_int(self.net_borrows_in_window.into())
|
||||
.unwrap();
|
||||
if net_borrows_quote > self.net_borrows_limit_quote {
|
||||
return err!(MangoError::BankNetBorrowsLimitReached).with_context(|| {
|
||||
format!(
|
||||
"net_borrows_in_window ({:?}) exceeds net_borrows_limit_quote ({:?}) for last_net_borrows_window_start_ts ({:?}) ",
|
||||
self.net_borrows_in_window, self.net_borrows_limit_quote, self.last_net_borrows_window_start_ts
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -819,7 +867,7 @@ mod tests {
|
|||
|
||||
let mut bank = Bank::zeroed();
|
||||
bank.net_borrows_window_size_ts = 1; // dummy
|
||||
bank.net_borrows_limit_native = i64::MAX; // max since we don't want this to interfere
|
||||
bank.net_borrows_limit_quote = i64::MAX; // max since we don't want this to interfere
|
||||
bank.deposit_index = I80F48::from_num(100.0);
|
||||
bank.borrow_index = I80F48::from_num(10.0);
|
||||
bank.loan_origination_fee_rate = I80F48::from_num(0.1);
|
||||
|
@ -858,7 +906,9 @@ mod tests {
|
|||
|
||||
let change = I80F48::from(change);
|
||||
let dummy_now_ts = 1 as u64;
|
||||
let (is_active, _) = bank.change_with_fee(&mut account, change, dummy_now_ts)?;
|
||||
let dummy_price = I80F48::ZERO;
|
||||
let (is_active, _) =
|
||||
bank.change_with_fee(&mut account, change, dummy_now_ts, dummy_price)?;
|
||||
|
||||
let mut expected_native = start_native + change;
|
||||
if expected_native >= 0.0 && expected_native < 1.0 && !is_in_use {
|
||||
|
|
|
@ -758,6 +758,7 @@ mod tests {
|
|||
account.ensure_token_position(4).unwrap().0,
|
||||
I80F48::from(10),
|
||||
DUMMY_NOW_TS,
|
||||
DUMMY_PRICE,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -841,6 +842,7 @@ mod tests {
|
|||
account.ensure_token_position(1).unwrap().0,
|
||||
I80F48::from(testcase.token1),
|
||||
DUMMY_NOW_TS,
|
||||
DUMMY_PRICE,
|
||||
)
|
||||
.unwrap();
|
||||
bank2
|
||||
|
@ -849,6 +851,7 @@ mod tests {
|
|||
account.ensure_token_position(4).unwrap().0,
|
||||
I80F48::from(testcase.token2),
|
||||
DUMMY_NOW_TS,
|
||||
DUMMY_PRICE,
|
||||
)
|
||||
.unwrap();
|
||||
bank3
|
||||
|
@ -857,6 +860,7 @@ mod tests {
|
|||
account.ensure_token_position(5).unwrap().0,
|
||||
I80F48::from(testcase.token3),
|
||||
DUMMY_NOW_TS,
|
||||
DUMMY_PRICE,
|
||||
)
|
||||
.unwrap();
|
||||
for (settings, bank) in testcase
|
||||
|
|
|
@ -64,7 +64,7 @@ impl HealthCache {
|
|||
|
||||
let mut source_bank = source_bank.clone();
|
||||
source_bank
|
||||
.withdraw_with_fee(&mut source_position, amount, 0)
|
||||
.withdraw_with_fee(&mut source_position, amount, 0, I80F48::ZERO)
|
||||
.unwrap();
|
||||
let mut target_bank = target_bank.clone();
|
||||
target_bank
|
||||
|
@ -756,6 +756,7 @@ mod tests {
|
|||
account.ensure_token_position(1).unwrap().0,
|
||||
I80F48::from(100),
|
||||
DUMMY_NOW_TS,
|
||||
DUMMY_PRICE,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -838,6 +839,7 @@ mod tests {
|
|||
account.ensure_token_position(1).unwrap().0,
|
||||
I80F48::from(100),
|
||||
DUMMY_NOW_TS,
|
||||
DUMMY_PRICE,
|
||||
)
|
||||
.unwrap();
|
||||
bank1
|
||||
|
@ -846,6 +848,7 @@ mod tests {
|
|||
account2.ensure_token_position(1).unwrap().0,
|
||||
I80F48::from(-100),
|
||||
DUMMY_NOW_TS,
|
||||
DUMMY_PRICE,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ use std::rc::Rc;
|
|||
use crate::state::*;
|
||||
|
||||
pub const DUMMY_NOW_TS: u64 = 0;
|
||||
pub const DUMMY_PRICE: I80F48 = I80F48::ZERO;
|
||||
|
||||
// Implementing TestAccount directly for ZeroCopy + Owner leads to a conflict
|
||||
// because OpenOrders may add impls for those in the future.
|
||||
|
@ -98,7 +99,7 @@ pub fn mock_bank_and_oracle(
|
|||
bank.data().collateral_limit_quote = f64::MAX;
|
||||
bank.data().borrow_limit_quote = f64::MAX;
|
||||
bank.data().net_borrows_window_size_ts = 1; // dummy
|
||||
bank.data().net_borrows_limit_native = i64::MAX; // max since we don't want this to interfere
|
||||
bank.data().net_borrows_limit_quote = i64::MAX; // max since we don't want this to interfere
|
||||
(bank, oracle)
|
||||
}
|
||||
|
||||
|
|
|
@ -441,7 +441,7 @@ impl PerpPosition {
|
|||
/// Side-effect: updates the windowing
|
||||
pub fn update_and_get_used_settle_limit(&mut self, market: &PerpMarket, now_ts: u64) -> i64 {
|
||||
assert_eq!(self.market_index, market.perp_market_index);
|
||||
let window_size = market.settle_pnl_limit_factor_window_size_ts;
|
||||
let window_size = market.settle_pnl_limit_window_size_ts;
|
||||
let new_window = now_ts >= cm!((self.settle_pnl_limit_window + 1) as u64 * window_size);
|
||||
if new_window {
|
||||
self.settle_pnl_limit_window = cm!(now_ts / window_size).try_into().unwrap();
|
||||
|
|
|
@ -111,8 +111,11 @@ pub struct PerpMarket {
|
|||
|
||||
pub stable_price_model: StablePriceModel,
|
||||
|
||||
/// Fraction of perp base value that can be settled each window.
|
||||
/// Set to a negative value to disable the limit.
|
||||
pub settle_pnl_limit_factor: f32,
|
||||
pub settle_pnl_limit_factor_window_size_ts: u64,
|
||||
/// Window size in seconds for the perp settlement limit
|
||||
pub settle_pnl_limit_window_size_ts: u64,
|
||||
|
||||
pub reserved: [u8; 1944],
|
||||
}
|
||||
|
@ -321,7 +324,7 @@ impl PerpMarket {
|
|||
settle_fee_fraction_low_health: 0.0,
|
||||
stable_price_model: StablePriceModel::default(),
|
||||
settle_pnl_limit_factor: 0.2,
|
||||
settle_pnl_limit_factor_window_size_ts: 24 * 60 * 60,
|
||||
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
|
||||
reserved: [0; 1944],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -773,7 +773,7 @@ pub struct TokenRegisterInstruction {
|
|||
pub liquidation_fee: f32,
|
||||
|
||||
pub min_vault_to_deposits_ratio: f64,
|
||||
pub net_borrows_limit_native: i64,
|
||||
pub net_borrows_limit_quote: i64,
|
||||
pub net_borrows_window_size_ts: u64,
|
||||
|
||||
pub group: Pubkey,
|
||||
|
@ -817,7 +817,7 @@ impl ClientInstruction for TokenRegisterInstruction {
|
|||
init_liab_weight: self.init_liab_weight,
|
||||
liquidation_fee: self.liquidation_fee,
|
||||
min_vault_to_deposits_ratio: self.min_vault_to_deposits_ratio,
|
||||
net_borrows_limit_native: self.net_borrows_limit_native,
|
||||
net_borrows_limit_quote: self.net_borrows_limit_quote,
|
||||
net_borrows_window_size_ts: self.net_borrows_window_size_ts,
|
||||
};
|
||||
|
||||
|
@ -1062,7 +1062,7 @@ impl ClientInstruction for TokenResetStablePriceModel {
|
|||
let mint_info: MintInfo = account_loader.load(&mint_info_key).await.unwrap();
|
||||
|
||||
let instruction = Self::Instruction {
|
||||
oracle_opt: Some(mint_info.oracle),
|
||||
oracle_opt: None,
|
||||
oracle_config_opt: None,
|
||||
group_insurance_fund_opt: None,
|
||||
interest_rate_params_opt: None,
|
||||
|
@ -1077,8 +1077,10 @@ impl ClientInstruction for TokenResetStablePriceModel {
|
|||
stable_price_delay_growth_limit_opt: None,
|
||||
stable_price_growth_limit_opt: None,
|
||||
min_vault_to_deposits_ratio_opt: None,
|
||||
net_borrows_limit_native_opt: None,
|
||||
net_borrows_limit_quote_opt: None,
|
||||
net_borrows_window_size_ts_opt: None,
|
||||
reset_stable_price: true,
|
||||
reset_net_borrow_limit: false,
|
||||
};
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
|
@ -1104,17 +1106,17 @@ impl ClientInstruction for TokenResetStablePriceModel {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct TokenEditNetBorrows {
|
||||
pub struct TokenResetNetBorrows {
|
||||
pub group: Pubkey,
|
||||
pub admin: TestKeypair,
|
||||
pub mint: Pubkey,
|
||||
pub min_vault_to_deposits_ratio_opt: Option<f64>,
|
||||
pub net_borrows_limit_native_opt: Option<i64>,
|
||||
pub net_borrows_limit_quote_opt: Option<i64>,
|
||||
pub net_borrows_window_size_ts_opt: Option<u64>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ClientInstruction for TokenEditNetBorrows {
|
||||
impl ClientInstruction for TokenResetNetBorrows {
|
||||
type Accounts = mango_v4::accounts::TokenEdit;
|
||||
type Instruction = mango_v4::instruction::TokenEdit;
|
||||
async fn to_instruction(
|
||||
|
@ -1150,8 +1152,10 @@ impl ClientInstruction for TokenEditNetBorrows {
|
|||
stable_price_delay_growth_limit_opt: None,
|
||||
stable_price_growth_limit_opt: None,
|
||||
min_vault_to_deposits_ratio_opt: self.min_vault_to_deposits_ratio_opt,
|
||||
net_borrows_limit_native_opt: self.net_borrows_limit_native_opt,
|
||||
net_borrows_limit_quote_opt: self.net_borrows_limit_quote_opt,
|
||||
net_borrows_window_size_ts_opt: self.net_borrows_window_size_ts_opt,
|
||||
reset_stable_price: false,
|
||||
reset_net_borrow_limit: true,
|
||||
};
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
|
@ -1907,17 +1911,18 @@ impl ClientInstruction for Serum3PlaceOrderInstruction {
|
|||
)
|
||||
.await;
|
||||
|
||||
let (payer_bank, payer_vault) = match self.side {
|
||||
Serum3Side::Bid => (quote_info.first_bank(), quote_info.first_vault()),
|
||||
Serum3Side::Ask => (base_info.first_bank(), base_info.first_vault()),
|
||||
let payer_info = &match self.side {
|
||||
Serum3Side::Bid => "e_info,
|
||||
Serum3Side::Ask => &base_info,
|
||||
};
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: account.fixed.group,
|
||||
account: self.account,
|
||||
open_orders,
|
||||
payer_bank,
|
||||
payer_vault,
|
||||
payer_bank: payer_info.first_bank(),
|
||||
payer_vault: payer_info.first_vault(),
|
||||
payer_oracle: payer_info.oracle,
|
||||
serum_market: self.serum_market,
|
||||
serum_program: serum_market.serum_program,
|
||||
serum_market_external: serum_market.serum_market_external,
|
||||
|
@ -2423,7 +2428,7 @@ pub struct PerpCreateMarketInstruction {
|
|||
pub settle_fee_amount_threshold: f32,
|
||||
pub settle_fee_fraction_low_health: f32,
|
||||
pub settle_pnl_limit_factor: f32,
|
||||
pub settle_pnl_limit_factor_window_size_ts: u64,
|
||||
pub settle_pnl_limit_window_size_ts: u64,
|
||||
}
|
||||
impl PerpCreateMarketInstruction {
|
||||
pub async fn with_new_book_and_queue(
|
||||
|
@ -2480,7 +2485,7 @@ impl ClientInstruction for PerpCreateMarketInstruction {
|
|||
settle_fee_amount_threshold: self.settle_fee_amount_threshold,
|
||||
settle_fee_fraction_low_health: self.settle_fee_fraction_low_health,
|
||||
settle_pnl_limit_factor: self.settle_pnl_limit_factor,
|
||||
settle_pnl_limit_factor_window_size_ts: self.settle_pnl_limit_factor_window_size_ts,
|
||||
settle_pnl_limit_window_size_ts: self.settle_pnl_limit_window_size_ts,
|
||||
};
|
||||
|
||||
let perp_market = Pubkey::find_program_address(
|
||||
|
@ -2555,7 +2560,7 @@ impl ClientInstruction for PerpResetStablePriceModel {
|
|||
stable_price_delay_growth_limit_opt: None,
|
||||
stable_price_growth_limit_opt: None,
|
||||
settle_pnl_limit_factor_opt: None,
|
||||
settle_pnl_limit_factor_window_size_ts: None,
|
||||
settle_pnl_limit_window_size_ts: None,
|
||||
};
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
|
|
|
@ -106,7 +106,7 @@ impl<'a> GroupWithTokensConfig {
|
|||
mint: mint.pubkey,
|
||||
payer,
|
||||
min_vault_to_deposits_ratio: 0.2,
|
||||
net_borrows_limit_native: 1_000_000_000_000,
|
||||
net_borrows_limit_quote: 1_000_000_000_000,
|
||||
net_borrows_window_size_ts: 24 * 60 * 60,
|
||||
},
|
||||
)
|
||||
|
|
|
@ -33,78 +33,31 @@ async fn test_bank_utilization_based_borrow_limit() -> Result<(), TransportError
|
|||
.create(solana)
|
||||
.await;
|
||||
|
||||
let account_0 = send_tx(
|
||||
solana,
|
||||
AccountCreateInstruction {
|
||||
account_num: 0,
|
||||
token_count: 2,
|
||||
serum3_count: 0,
|
||||
perp_count: 0,
|
||||
perp_oo_count: 0,
|
||||
group,
|
||||
owner,
|
||||
payer,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
let account_1 = send_tx(
|
||||
solana,
|
||||
AccountCreateInstruction {
|
||||
account_num: 1,
|
||||
token_count: 2,
|
||||
serum3_count: 0,
|
||||
perp_count: 0,
|
||||
perp_oo_count: 0,
|
||||
group,
|
||||
owner,
|
||||
payer,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
//
|
||||
// SETUP: Deposit user funds
|
||||
// SETUP: Prepare accounts
|
||||
//
|
||||
{
|
||||
let deposit_amount = initial_token_deposit;
|
||||
|
||||
// account_0 deposits mint_0
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit_amount,
|
||||
account: account_0,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
token_authority: payer,
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
solana.advance_clock().await;
|
||||
|
||||
// account_1 deposits mint_1
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit_amount * 10,
|
||||
account: account_1,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
token_authority: payer,
|
||||
bank_index: 1,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
solana.advance_clock().await;
|
||||
}
|
||||
let account_0 = create_funded_account(
|
||||
&solana,
|
||||
group,
|
||||
owner,
|
||||
0,
|
||||
&context.users[1],
|
||||
&mints[0..1],
|
||||
initial_token_deposit,
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
let account_1 = create_funded_account(
|
||||
&solana,
|
||||
group,
|
||||
owner,
|
||||
1,
|
||||
&context.users[1],
|
||||
&mints[1..2],
|
||||
initial_token_deposit * 10,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
|
||||
{
|
||||
let deposit_amount = initial_token_deposit;
|
||||
|
@ -187,94 +140,55 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError
|
|||
.create(solana)
|
||||
.await;
|
||||
|
||||
let account_0 = send_tx(
|
||||
solana,
|
||||
AccountCreateInstruction {
|
||||
account_num: 0,
|
||||
token_count: 2,
|
||||
serum3_count: 0,
|
||||
perp_count: 0,
|
||||
perp_oo_count: 0,
|
||||
group,
|
||||
owner,
|
||||
payer,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
let reset_net_borrows = || {
|
||||
let mint = tokens[0].mint.pubkey;
|
||||
async move {
|
||||
send_tx(
|
||||
solana,
|
||||
TokenResetNetBorrows {
|
||||
group,
|
||||
admin,
|
||||
mint,
|
||||
// we want to test net borrow limits in isolation
|
||||
min_vault_to_deposits_ratio_opt: Some(0.0),
|
||||
net_borrows_limit_quote_opt: Some(6000),
|
||||
net_borrows_window_size_ts_opt: Some(1000),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
let account_1 = send_tx(
|
||||
solana,
|
||||
AccountCreateInstruction {
|
||||
account_num: 1,
|
||||
token_count: 2,
|
||||
serum3_count: 0,
|
||||
perp_count: 0,
|
||||
perp_oo_count: 0,
|
||||
group,
|
||||
owner,
|
||||
payer,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
{
|
||||
send_tx(
|
||||
solana,
|
||||
TokenEditNetBorrows {
|
||||
group,
|
||||
admin,
|
||||
mint: tokens[0].mint.pubkey,
|
||||
// we want to test net borrow limits in isolation
|
||||
min_vault_to_deposits_ratio_opt: Some(0.0),
|
||||
net_borrows_limit_native_opt: Some(6000),
|
||||
net_borrows_window_size_ts_opt: Some(3),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
reset_net_borrows().await;
|
||||
|
||||
//
|
||||
// SETUP: Deposit user funds
|
||||
// SETUP: Prepare accounts
|
||||
//
|
||||
{
|
||||
// account_0 deposits mint_0
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: 10_000,
|
||||
account: account_0,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
token_authority: payer,
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let account_0 = create_funded_account(
|
||||
&solana,
|
||||
group,
|
||||
owner,
|
||||
0,
|
||||
&context.users[1],
|
||||
&mints[0..1],
|
||||
100_000,
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
let account_1 = create_funded_account(
|
||||
&solana,
|
||||
group,
|
||||
owner,
|
||||
1,
|
||||
&context.users[1],
|
||||
&mints[1..2],
|
||||
1_000_000,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
|
||||
// account_1 deposits mint_1
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: 10_000 * 10,
|
||||
account: account_1,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
token_authority: payer,
|
||||
bank_index: 1,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Go to the next net borrow limit window
|
||||
solana.advance_clock_to_next_multiple(3).await;
|
||||
reset_net_borrows().await;
|
||||
|
||||
{
|
||||
// succeeds because borrow is less than net borrow limit
|
||||
|
@ -324,15 +238,68 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
// Go to the next net borrow limit window
|
||||
solana.advance_clock_to_next_multiple(3).await;
|
||||
reset_net_borrows().await;
|
||||
|
||||
// succeeds because borrow is less than net borrow limit in a fresh window
|
||||
//
|
||||
// TEST: If the price goes up, the borrow limit is hit more quickly - it's in USD
|
||||
//
|
||||
{
|
||||
// succeeds because borrow is less than net borrow limit in a fresh window
|
||||
{
|
||||
send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: 1000,
|
||||
allow_borrow: true,
|
||||
account: account_1,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
set_bank_stub_oracle_price(solana, group, &tokens[0], admin, 10.0).await;
|
||||
|
||||
// cannot borrow anything: net borrowed 1000 * price 10.0 > limit 6000
|
||||
let res = send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: 1,
|
||||
allow_borrow: true,
|
||||
account: account_1,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
assert!(res.is_err());
|
||||
|
||||
set_bank_stub_oracle_price(solana, group, &tokens[0], admin, 5.0).await;
|
||||
|
||||
// cannot borrow this much: (net borrowed 1000 + new borrow 201) * price 5.0 > limit 6000
|
||||
let res = send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: 201,
|
||||
allow_borrow: true,
|
||||
account: account_1,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
assert!(res.is_err());
|
||||
|
||||
// can borrow smaller amounts: (net borrowed 1000 + new borrow 199) * price 5.0 < limit 6000
|
||||
send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: 1000,
|
||||
amount: 199,
|
||||
allow_borrow: true,
|
||||
account: account_1,
|
||||
owner,
|
||||
|
@ -342,7 +309,6 @@ async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError
|
|||
)
|
||||
.await
|
||||
.unwrap();
|
||||
solana.advance_clock().await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -222,7 +222,7 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
|
|||
maker_fee: 0.0002,
|
||||
taker_fee: 0.000,
|
||||
settle_pnl_limit_factor: 0.2,
|
||||
settle_pnl_limit_factor_window_size_ts: 24 * 60 * 60,
|
||||
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &token).await
|
||||
},
|
||||
)
|
||||
|
|
|
@ -63,7 +63,7 @@ async fn test_liq_perps_force_cancel() -> Result<(), TransportError> {
|
|||
maker_fee: 0.0,
|
||||
taker_fee: 0.0,
|
||||
settle_pnl_limit_factor: 0.2,
|
||||
settle_pnl_limit_factor_window_size_ts: 24 * 60 * 60,
|
||||
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, base_token).await
|
||||
},
|
||||
)
|
||||
|
@ -266,7 +266,7 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
|
|||
taker_fee: 0.0,
|
||||
group_insurance_fund: true,
|
||||
settle_pnl_limit_factor: 0.2,
|
||||
settle_pnl_limit_factor_window_size_ts: 24 * 60 * 60,
|
||||
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, base_token).await
|
||||
},
|
||||
)
|
||||
|
|
|
@ -86,7 +86,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
|||
maker_fee: -0.0001,
|
||||
taker_fee: 0.0002,
|
||||
settle_pnl_limit_factor: 0.2,
|
||||
settle_pnl_limit_factor_window_size_ts: 24 * 60 * 60,
|
||||
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &tokens[0]).await
|
||||
},
|
||||
)
|
||||
|
@ -524,7 +524,7 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
|
|||
maker_fee: -0.0001,
|
||||
taker_fee: 0.0002,
|
||||
settle_pnl_limit_factor: 0.2,
|
||||
settle_pnl_limit_factor_window_size_ts: 24 * 60 * 60,
|
||||
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &tokens[0]).await
|
||||
},
|
||||
)
|
||||
|
|
|
@ -81,7 +81,7 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
maker_fee: 0.0002,
|
||||
taker_fee: 0.000,
|
||||
settle_pnl_limit_factor: 0.2,
|
||||
settle_pnl_limit_factor_window_size_ts: 24 * 60 * 60,
|
||||
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &tokens[1]).await
|
||||
},
|
||||
)
|
||||
|
@ -111,7 +111,7 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
maker_fee: 0.0002,
|
||||
taker_fee: 0.000,
|
||||
settle_pnl_limit_factor: 0.2,
|
||||
settle_pnl_limit_factor_window_size_ts: 24 * 60 * 60,
|
||||
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &tokens[2]).await
|
||||
},
|
||||
)
|
||||
|
@ -585,7 +585,7 @@ async fn test_perp_settle_pnl_fees() -> Result<(), TransportError> {
|
|||
settle_fee_amount_threshold: 2000.0,
|
||||
settle_fee_fraction_low_health: fee_low_health,
|
||||
settle_pnl_limit_factor: 0.2,
|
||||
settle_pnl_limit_factor_window_size_ts: 24 * 60 * 60,
|
||||
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &tokens[1]).await
|
||||
},
|
||||
)
|
||||
|
@ -851,7 +851,7 @@ async fn test_perp_pnl_settle_limit() -> Result<(), TransportError> {
|
|||
maker_fee: 0.0002,
|
||||
taker_fee: 0.000,
|
||||
settle_pnl_limit_factor: 0.2,
|
||||
settle_pnl_limit_factor_window_size_ts: 24 * 60 * 60,
|
||||
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &tokens[1]).await
|
||||
},
|
||||
)
|
||||
|
|
|
@ -156,7 +156,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
maker_fee: 0.0002,
|
||||
taker_fee: 0.000,
|
||||
settle_pnl_limit_factor: 0.2,
|
||||
settle_pnl_limit_factor_window_size_ts: 24 * 60 * 60,
|
||||
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &tokens[0]).await
|
||||
},
|
||||
)
|
||||
|
@ -186,7 +186,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
maker_fee: 0.0002,
|
||||
taker_fee: 0.000,
|
||||
settle_pnl_limit_factor: 0.2,
|
||||
settle_pnl_limit_factor_window_size_ts: 24 * 60 * 60,
|
||||
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &tokens[1]).await
|
||||
},
|
||||
)
|
||||
|
|
|
@ -296,9 +296,14 @@ export class MangoClient {
|
|||
maintLiabWeight: number | null,
|
||||
initLiabWeight: number | null,
|
||||
liquidationFee: number | null,
|
||||
stablePriceDelayIntervalSeconds: number | null,
|
||||
stablePriceDelayGrowthLimit: number | null,
|
||||
stablePriceGrowthLimit: number | null,
|
||||
minVaultToDepositsRatio: number | null,
|
||||
netBorrowsLimitNative: number | null,
|
||||
netBorrowsLimitQuote: number | null,
|
||||
netBorrowsWindowSizeTs: number | null,
|
||||
resetStablePrice: boolean | null,
|
||||
resetNetBorrowLimit: boolean | null,
|
||||
): Promise<TransactionSignature> {
|
||||
const bank = group.getFirstBankByMint(mintPk);
|
||||
const mintInfo = group.mintInfosMapByTokenIndex.get(bank.tokenIndex)!;
|
||||
|
@ -316,9 +321,14 @@ export class MangoClient {
|
|||
maintLiabWeight,
|
||||
initLiabWeight,
|
||||
liquidationFee,
|
||||
stablePriceDelayIntervalSeconds,
|
||||
stablePriceDelayGrowthLimit,
|
||||
stablePriceGrowthLimit,
|
||||
minVaultToDepositsRatio,
|
||||
netBorrowsLimitNative,
|
||||
netBorrowsWindowSizeTs,
|
||||
netBorrowsLimitQuote !== null ? new BN(netBorrowsLimitQuote) : null,
|
||||
netBorrowsWindowSizeTs !== null ? new BN(netBorrowsWindowSizeTs) : null,
|
||||
resetStablePrice ?? false,
|
||||
resetNetBorrowLimit ?? false,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
@ -1377,6 +1387,8 @@ export class MangoClient {
|
|||
settleFeeAmountThreshold: number,
|
||||
settleFeeFractionLowHealth: number,
|
||||
settleTokenIndex: number,
|
||||
settlePnlLimitFactor: number,
|
||||
settlePnlLimitWindowSize: number,
|
||||
): Promise<TransactionSignature> {
|
||||
const orderbook = new Keypair();
|
||||
const eventQueue = new Keypair();
|
||||
|
@ -1413,6 +1425,8 @@ export class MangoClient {
|
|||
settleFeeAmountThreshold,
|
||||
settleFeeFractionLowHealth,
|
||||
settleTokenIndex,
|
||||
settlePnlLimitFactor,
|
||||
new BN(settlePnlLimitWindowSize),
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
@ -1477,6 +1491,8 @@ export class MangoClient {
|
|||
stablePriceDelayIntervalSeconds: number | null,
|
||||
stablePriceDelayGrowthLimit: number | null,
|
||||
stablePriceGrowthLimit: number | null,
|
||||
settlePnlLimitFactor: number | null,
|
||||
settlePnlLimitWindowSize: number | null,
|
||||
): Promise<TransactionSignature> {
|
||||
const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex);
|
||||
|
||||
|
@ -1494,7 +1510,7 @@ export class MangoClient {
|
|||
takerFee,
|
||||
minFunding,
|
||||
maxFunding,
|
||||
impactQuantity ? new BN(impactQuantity) : null,
|
||||
impactQuantity !== null ? new BN(impactQuantity) : null,
|
||||
groupInsuranceFund,
|
||||
trustedMarket,
|
||||
feePenalty,
|
||||
|
@ -1504,6 +1520,10 @@ export class MangoClient {
|
|||
stablePriceDelayIntervalSeconds,
|
||||
stablePriceDelayGrowthLimit,
|
||||
stablePriceGrowthLimit,
|
||||
settlePnlLimitFactor,
|
||||
settlePnlLimitWindowSize !== null
|
||||
? new BN(settlePnlLimitWindowSize)
|
||||
: null,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
|
|
@ -351,7 +351,7 @@ export type MangoV4 = {
|
|||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsLimitNative",
|
||||
"name": "netBorrowsLimitQuote",
|
||||
"type": "i64"
|
||||
}
|
||||
]
|
||||
|
@ -614,7 +614,7 @@ export type MangoV4 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsLimitNativeOpt",
|
||||
"name": "netBorrowsLimitQuoteOpt",
|
||||
"type": {
|
||||
"option": "i64"
|
||||
}
|
||||
|
@ -624,6 +624,14 @@ export type MangoV4 = {
|
|||
"type": {
|
||||
"option": "u64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "resetStablePrice",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "resetNetBorrowLimit",
|
||||
"type": "bool"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -1735,6 +1743,11 @@ export type MangoV4 = {
|
|||
"The bank vault that pays for the order, if necessary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "payerOracle",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "tokenProgram",
|
||||
"isMut": false,
|
||||
|
@ -2450,7 +2463,7 @@ export type MangoV4 = {
|
|||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "settlePnlLimitFactorWindowSizeTs",
|
||||
"name": "settlePnlLimitWindowSizeTs",
|
||||
"type": "u64"
|
||||
}
|
||||
]
|
||||
|
@ -2621,7 +2634,7 @@ export type MangoV4 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "settlePnlLimitFactorWindowSizeTs",
|
||||
"name": "settlePnlLimitWindowSizeTs",
|
||||
"type": {
|
||||
"option": "u64"
|
||||
}
|
||||
|
@ -3621,22 +3634,37 @@ export type MangoV4 = {
|
|||
},
|
||||
{
|
||||
"name": "minVaultToDepositsRatio",
|
||||
"docs": [
|
||||
"Min fraction of deposits that must remain in the vault when borrowing."
|
||||
],
|
||||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsWindowSizeTs",
|
||||
"docs": [
|
||||
"Size in seconds of a net borrows window"
|
||||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "lastNetBorrowsWindowStartTs",
|
||||
"docs": [
|
||||
"Timestamp at which the last net borrows window started"
|
||||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsLimitNative",
|
||||
"name": "netBorrowsLimitQuote",
|
||||
"docs": [
|
||||
"Net borrow limit per window in quote native; set to -1 to disable."
|
||||
],
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsWindowNative",
|
||||
"name": "netBorrowsInWindow",
|
||||
"docs": [
|
||||
"Sum of all deposits and borrows in the last window, in native units."
|
||||
],
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
|
@ -4294,7 +4322,7 @@ export type MangoV4 = {
|
|||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "settlePnlLimitFactorWindowSizeTs",
|
||||
"name": "settlePnlLimitWindowSizeTs",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
|
@ -7550,7 +7578,7 @@ export const IDL: MangoV4 = {
|
|||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsLimitNative",
|
||||
"name": "netBorrowsLimitQuote",
|
||||
"type": "i64"
|
||||
}
|
||||
]
|
||||
|
@ -7813,7 +7841,7 @@ export const IDL: MangoV4 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsLimitNativeOpt",
|
||||
"name": "netBorrowsLimitQuoteOpt",
|
||||
"type": {
|
||||
"option": "i64"
|
||||
}
|
||||
|
@ -7823,6 +7851,14 @@ export const IDL: MangoV4 = {
|
|||
"type": {
|
||||
"option": "u64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "resetStablePrice",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "resetNetBorrowLimit",
|
||||
"type": "bool"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -8934,6 +8970,11 @@ export const IDL: MangoV4 = {
|
|||
"The bank vault that pays for the order, if necessary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "payerOracle",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "tokenProgram",
|
||||
"isMut": false,
|
||||
|
@ -9649,7 +9690,7 @@ export const IDL: MangoV4 = {
|
|||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "settlePnlLimitFactorWindowSizeTs",
|
||||
"name": "settlePnlLimitWindowSizeTs",
|
||||
"type": "u64"
|
||||
}
|
||||
]
|
||||
|
@ -9820,7 +9861,7 @@ export const IDL: MangoV4 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "settlePnlLimitFactorWindowSizeTs",
|
||||
"name": "settlePnlLimitWindowSizeTs",
|
||||
"type": {
|
||||
"option": "u64"
|
||||
}
|
||||
|
@ -10820,22 +10861,37 @@ export const IDL: MangoV4 = {
|
|||
},
|
||||
{
|
||||
"name": "minVaultToDepositsRatio",
|
||||
"docs": [
|
||||
"Min fraction of deposits that must remain in the vault when borrowing."
|
||||
],
|
||||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsWindowSizeTs",
|
||||
"docs": [
|
||||
"Size in seconds of a net borrows window"
|
||||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "lastNetBorrowsWindowStartTs",
|
||||
"docs": [
|
||||
"Timestamp at which the last net borrows window started"
|
||||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsLimitNative",
|
||||
"name": "netBorrowsLimitQuote",
|
||||
"docs": [
|
||||
"Net borrow limit per window in quote native; set to -1 to disable."
|
||||
],
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsWindowNative",
|
||||
"name": "netBorrowsInWindow",
|
||||
"docs": [
|
||||
"Sum of all deposits and borrows in the last window, in native units."
|
||||
],
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
|
@ -11493,7 +11549,7 @@ export const IDL: MangoV4 = {
|
|||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "settlePnlLimitFactorWindowSizeTs",
|
||||
"name": "settlePnlLimitWindowSizeTs",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -396,6 +396,8 @@ async function main() {
|
|||
1000000,
|
||||
0.05,
|
||||
0,
|
||||
1.0,
|
||||
2 * 60 * 60,
|
||||
);
|
||||
console.log('done');
|
||||
} catch (error) {
|
||||
|
@ -425,9 +427,14 @@ async function main() {
|
|||
1,
|
||||
1,
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
|
||||
await group.reloadAll(client);
|
||||
|
@ -452,9 +459,14 @@ async function main() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
|
||||
await group.reloadAll(client);
|
||||
|
@ -479,9 +491,14 @@ async function main() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
|
||||
await group.reloadAll(client);
|
||||
|
@ -517,6 +534,8 @@ async function main() {
|
|||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
1.0,
|
||||
2 * 60 * 60,
|
||||
);
|
||||
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
|
||||
await group.reloadAll(client);
|
||||
|
|
|
@ -235,6 +235,8 @@ async function main() {
|
|||
0,
|
||||
0,
|
||||
0,
|
||||
1.0,
|
||||
2 * 60 * 60,
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
|
|
@ -170,6 +170,11 @@ async function main() {
|
|||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
try {
|
||||
// At a price of $1/ui-SOL we can buy 0.1 ui-SOL for the 100k native-USDC we have.
|
||||
|
@ -205,6 +210,11 @@ async function main() {
|
|||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue