net borrow limits (#301)
* net borrow limits Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * fix client Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fix tests Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> Co-authored-by: Christian Kamm <mail@ckamm.de>
This commit is contained in:
parent
502f0767a8
commit
1732a5aff4
|
@ -57,6 +57,10 @@ pub enum MangoError {
|
|||
OracleStale,
|
||||
#[msg("settlement amount must always be positive")]
|
||||
SettlementAmountMustBePositive,
|
||||
#[msg("bank utilization has reached limit")]
|
||||
BankBorrowLimitReached,
|
||||
#[msg("bank net borrows has reached limit - this is an intermittent error - the limit will reset regularly")]
|
||||
BankNetBorrowsLimitReached,
|
||||
}
|
||||
|
||||
pub trait Contextable {
|
||||
|
|
|
@ -392,8 +392,11 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
let loan_origination_fee = cm!(loan * bank.loan_origination_fee_rate);
|
||||
cm!(bank.collected_fees_native += loan_origination_fee);
|
||||
|
||||
let is_active =
|
||||
bank.change_without_fee(position, cm!(change.amount - loan_origination_fee))?;
|
||||
let is_active = bank.change_without_fee(
|
||||
position,
|
||||
cm!(change.amount - loan_origination_fee),
|
||||
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||
)?;
|
||||
if !is_active {
|
||||
deactivated_token_positions.push(change.raw_token_index);
|
||||
}
|
||||
|
|
|
@ -159,7 +159,11 @@ pub fn perp_liq_bankruptcy(ctx: Context<PerpLiqBankruptcy>, max_liab_transfer: u
|
|||
|
||||
// credit the liqor with quote tokens
|
||||
let (liqor_quote, _, _) = liqor.ensure_token_position(settle_token_index)?;
|
||||
settle_bank.deposit(liqor_quote, insurance_transfer_i80f48)?;
|
||||
settle_bank.deposit(
|
||||
liqor_quote,
|
||||
insurance_transfer_i80f48,
|
||||
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||
)?;
|
||||
|
||||
emit!(TokenBalanceLog {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
|
|
|
@ -107,7 +107,11 @@ pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: u64) ->
|
|||
let token_position = account
|
||||
.token_position_mut(perp_market.settle_token_index)?
|
||||
.0;
|
||||
bank.withdraw_with_fee(token_position, settlement)?;
|
||||
bank.withdraw_with_fee(
|
||||
token_position,
|
||||
settlement,
|
||||
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||
)?;
|
||||
// Update the settled balance on the market itself
|
||||
perp_market.fees_settled = cm!(perp_market.fees_settled + settlement);
|
||||
|
||||
|
|
|
@ -180,12 +180,14 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
|
|||
cm!(account_a.fixed.perp_spot_transfers += settlement_i64 - fee_i64);
|
||||
cm!(account_b.fixed.perp_spot_transfers -= settlement_i64);
|
||||
|
||||
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
|
||||
|
||||
// Transfer token balances
|
||||
// The fee is paid by the account with positive unsettled pnl
|
||||
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))?;
|
||||
bank.withdraw_with_fee(b_token_position, settlement)?;
|
||||
bank.deposit(a_token_position, cm!(settlement - fee), now_ts)?;
|
||||
bank.withdraw_with_fee(b_token_position, settlement, now_ts)?;
|
||||
|
||||
emit!(TokenBalanceLog {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
|
@ -220,7 +222,7 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
|
|||
|
||||
let (settler_token_position, settler_token_raw_index, _) =
|
||||
settler.ensure_token_position(settle_token_index)?;
|
||||
let settler_token_position_active = bank.deposit(settler_token_position, fee)?;
|
||||
let settler_token_position_active = bank.deposit(settler_token_position, fee, now_ts)?;
|
||||
|
||||
emit!(TokenBalanceLog {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
|
|
|
@ -429,7 +429,11 @@ pub fn apply_vault_difference(
|
|||
|
||||
let (position, _) = account.token_position_mut(bank.token_index)?;
|
||||
let native_before = position.native(bank);
|
||||
bank.change_without_fee(position, needed_change)?;
|
||||
bank.change_without_fee(
|
||||
position,
|
||||
needed_change,
|
||||
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||
)?;
|
||||
let native_after = position.native(bank);
|
||||
let native_change = cm!(native_after - native_before);
|
||||
let new_borrows = native_change
|
||||
|
|
|
@ -206,6 +206,8 @@ pub fn charge_loan_origination_fees(
|
|||
) -> Result<()> {
|
||||
let serum3_account = account.serum3_orders_mut(market_index).unwrap();
|
||||
|
||||
let now_ts = Clock::get()?.unix_timestamp.try_into().unwrap();
|
||||
|
||||
let oo_base_total = before_oo.native_base_total();
|
||||
let actualized_base_loan = I80F48::from_num(
|
||||
serum3_account
|
||||
|
@ -218,8 +220,11 @@ pub fn charge_loan_origination_fees(
|
|||
// now that the loan is actually materialized, charge the loan origination fee
|
||||
// note: the withdraw has already happened while placing the order
|
||||
let base_token_account = account.token_position_mut(base_bank.token_index)?.0;
|
||||
let (_, fee) =
|
||||
base_bank.withdraw_loan_origination_fee(base_token_account, actualized_base_loan)?;
|
||||
let (_, fee) = base_bank.withdraw_loan_origination_fee(
|
||||
base_token_account,
|
||||
actualized_base_loan,
|
||||
now_ts,
|
||||
)?;
|
||||
|
||||
emit!(WithdrawLoanOriginationFeeLog {
|
||||
mango_group: *group_pubkey,
|
||||
|
@ -243,8 +248,11 @@ pub fn charge_loan_origination_fees(
|
|||
// now that the loan is actually materialized, charge the loan origination fee
|
||||
// note: the withdraw has already happened while placing the order
|
||||
let quote_token_account = account.token_position_mut(quote_bank.token_index)?.0;
|
||||
let (_, fee) =
|
||||
quote_bank.withdraw_loan_origination_fee(quote_token_account, actualized_quote_loan)?;
|
||||
let (_, fee) = quote_bank.withdraw_loan_origination_fee(
|
||||
quote_token_account,
|
||||
actualized_quote_loan,
|
||||
now_ts,
|
||||
)?;
|
||||
|
||||
emit!(WithdrawLoanOriginationFeeLog {
|
||||
mango_group: *group_pubkey,
|
||||
|
|
|
@ -69,7 +69,13 @@ pub fn token_add_bank(
|
|||
let existing_bank = ctx.accounts.existing_bank.load()?;
|
||||
let mut bank = ctx.accounts.bank.load_init()?;
|
||||
let bump = *ctx.bumps.get("bank").ok_or(MangoError::SomeError)?;
|
||||
*bank = Bank::from_existing_bank(&existing_bank, ctx.accounts.vault.key(), bank_num, bump);
|
||||
*bank = Bank::from_existing_bank(
|
||||
&existing_bank,
|
||||
ctx.accounts.vault.key(),
|
||||
bank_num,
|
||||
bump,
|
||||
Clock::get()?.unix_timestamp as u64,
|
||||
);
|
||||
|
||||
let mut mint_info = ctx.accounts.mint_info.load_mut()?;
|
||||
let free_slot = mint_info
|
||||
|
|
|
@ -113,7 +113,11 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
|
|||
let amount_i80f48 = I80F48::from(amount);
|
||||
let position_is_active = {
|
||||
let mut bank = self.bank.load_mut()?;
|
||||
bank.deposit(position, amount_i80f48)?
|
||||
bank.deposit(
|
||||
position,
|
||||
amount_i80f48,
|
||||
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||
)?
|
||||
};
|
||||
|
||||
// Transfer the actual tokens
|
||||
|
|
|
@ -49,6 +49,9 @@ pub fn token_edit(
|
|||
stable_price_delay_interval_seconds_opt: Option<u32>,
|
||||
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_window_size_ts_opt: Option<u64>,
|
||||
) -> Result<()> {
|
||||
let mut mint_info = ctx.accounts.mint_info.load_mut()?;
|
||||
mint_info.verify_banks_ais(ctx.remaining_accounts)?;
|
||||
|
@ -142,6 +145,16 @@ pub fn token_edit(
|
|||
bank.stable_price_model.stable_growth_limit = stable_price_growth_limit;
|
||||
}
|
||||
|
||||
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_window_size_ts) = net_borrows_window_size_ts_opt {
|
||||
bank.net_borrows_window_size_ts = net_borrows_window_size_ts;
|
||||
}
|
||||
|
||||
// unchanged -
|
||||
// dust
|
||||
// flash_loan_token_account_initial
|
||||
|
|
|
@ -143,11 +143,13 @@ pub fn token_liq_bankruptcy(
|
|||
// liquidators to exploit the insurance fund for 1 native token each call.
|
||||
let liab_transfer = cm!(insurance_transfer_i80f48 / liab_price_adjusted);
|
||||
|
||||
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
|
||||
|
||||
let mut liqee_liab_active = true;
|
||||
if insurance_transfer > 0 {
|
||||
// liqee gets liab assets (enable dusting to prevent a case where the position is brought
|
||||
// to +I80F48::DELTA)
|
||||
liqee_liab_active = liab_bank.deposit_with_dusting(liqee_liab, liab_transfer)?;
|
||||
liqee_liab_active = liab_bank.deposit_with_dusting(liqee_liab, liab_transfer, now_ts)?;
|
||||
remaining_liab_loss = -liqee_liab.native(liab_bank);
|
||||
|
||||
// move insurance assets into quote bank
|
||||
|
@ -169,7 +171,8 @@ pub fn token_liq_bankruptcy(
|
|||
// credit the liqor
|
||||
let (liqor_quote, liqor_quote_raw_token_index, _) =
|
||||
liqor.ensure_token_position(QUOTE_TOKEN_INDEX)?;
|
||||
let liqor_quote_active = quote_bank.deposit(liqor_quote, insurance_transfer_i80f48)?;
|
||||
let liqor_quote_active =
|
||||
quote_bank.deposit(liqor_quote, insurance_transfer_i80f48, now_ts)?;
|
||||
|
||||
// liqor quote
|
||||
emit!(TokenBalanceLog {
|
||||
|
@ -185,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)?;
|
||||
liab_bank.withdraw_with_fee(liqor_liab, liab_transfer, now_ts)?;
|
||||
|
||||
// liqor liab
|
||||
emit!(TokenBalanceLog {
|
||||
|
@ -268,7 +271,8 @@ pub fn token_liq_bankruptcy(
|
|||
if amount_for_bank.is_positive() {
|
||||
// enable dusting, because each deposit() is allowed to round up. thus multiple deposit
|
||||
// could bring the total position slightly above zero otherwise
|
||||
liqee_liab_active = bank.deposit_with_dusting(liqee_liab, amount_for_bank)?;
|
||||
liqee_liab_active =
|
||||
bank.deposit_with_dusting(liqee_liab, amount_for_bank, now_ts)?;
|
||||
cm!(amount_to_credit -= amount_for_bank);
|
||||
if amount_to_credit <= 0 {
|
||||
break;
|
||||
|
|
|
@ -142,24 +142,38 @@ pub fn token_liq_with_token(
|
|||
|
||||
// Apply the balance changes to the liqor and liqee accounts
|
||||
let liqee_liab_position = liqee.token_position_mut_by_raw_index(liqee_liab_raw_index);
|
||||
let liqee_liab_active = liab_bank.deposit_with_dusting(liqee_liab_position, liab_transfer)?;
|
||||
let liqee_liab_active = liab_bank.deposit_with_dusting(
|
||||
liqee_liab_position,
|
||||
liab_transfer,
|
||||
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||
)?;
|
||||
let liqee_liab_indexed_position = liqee_liab_position.indexed_position;
|
||||
|
||||
let (liqor_liab_position, liqor_liab_raw_index, _) =
|
||||
liqor.ensure_token_position(liab_token_index)?;
|
||||
let (liqor_liab_active, loan_origination_fee) =
|
||||
liab_bank.withdraw_with_fee(liqor_liab_position, liab_transfer)?;
|
||||
let (liqor_liab_active, loan_origination_fee) = liab_bank.withdraw_with_fee(
|
||||
liqor_liab_position,
|
||||
liab_transfer,
|
||||
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||
)?;
|
||||
let liqor_liab_indexed_position = liqor_liab_position.indexed_position;
|
||||
let liqee_liab_native_after = liqee_liab_position.native(liab_bank);
|
||||
|
||||
let (liqor_asset_position, liqor_asset_raw_index, _) =
|
||||
liqor.ensure_token_position(asset_token_index)?;
|
||||
let liqor_asset_active = asset_bank.deposit(liqor_asset_position, asset_transfer)?;
|
||||
let liqor_asset_active = asset_bank.deposit(
|
||||
liqor_asset_position,
|
||||
asset_transfer,
|
||||
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||
)?;
|
||||
let liqor_asset_indexed_position = liqor_asset_position.indexed_position;
|
||||
|
||||
let liqee_asset_position = liqee.token_position_mut_by_raw_index(liqee_asset_raw_index);
|
||||
let liqee_asset_active =
|
||||
asset_bank.withdraw_without_fee_with_dusting(liqee_asset_position, asset_transfer)?;
|
||||
let liqee_asset_active = asset_bank.withdraw_without_fee_with_dusting(
|
||||
liqee_asset_position,
|
||||
asset_transfer,
|
||||
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||
)?;
|
||||
let liqee_asset_indexed_position = liqee_asset_position.indexed_position;
|
||||
let liqee_assets_native_after = liqee_asset_position.native(asset_bank);
|
||||
|
||||
|
|
|
@ -90,6 +90,9 @@ pub fn token_register(
|
|||
maint_liab_weight: f32,
|
||||
init_liab_weight: f32,
|
||||
liquidation_fee: f32,
|
||||
min_vault_to_deposits_ratio: f64,
|
||||
net_borrows_window_size_ts: u64,
|
||||
net_borrows_limit_native: i64,
|
||||
) -> Result<()> {
|
||||
// Require token 0 to be in the insurance token
|
||||
if token_index == QUOTE_TOKEN_INDEX {
|
||||
|
@ -99,7 +102,7 @@ pub fn token_register(
|
|||
);
|
||||
}
|
||||
|
||||
let now_ts = Clock::get()?.unix_timestamp;
|
||||
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
|
||||
|
||||
let mut bank = ctx.accounts.bank.load_init()?;
|
||||
*bank = Bank {
|
||||
|
@ -142,7 +145,13 @@ pub fn token_register(
|
|||
oracle_conf_filter: oracle_config.to_oracle_config().conf_filter,
|
||||
oracle_config: oracle_config.to_oracle_config(),
|
||||
stable_price_model: StablePriceModel::default(),
|
||||
reserved: [0; 2176],
|
||||
min_vault_to_deposits_ratio,
|
||||
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,
|
||||
reserved: [0; 2136],
|
||||
};
|
||||
require_gt!(bank.max_rate, MINIMUM_MAX_RATE);
|
||||
|
||||
|
|
|
@ -72,7 +72,8 @@ pub fn token_register_trustless(
|
|||
) -> Result<()> {
|
||||
require_neq!(token_index, 0);
|
||||
|
||||
let now_ts = Clock::get()?.unix_timestamp;
|
||||
let net_borrows_window_size_ts = 24 * 60 * 60 as u64;
|
||||
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
|
||||
|
||||
let mut bank = ctx.accounts.bank.load_init()?;
|
||||
*bank = Bank {
|
||||
|
@ -119,7 +120,13 @@ pub fn token_register_trustless(
|
|||
reserved: [0; 72],
|
||||
},
|
||||
stable_price_model: StablePriceModel::default(),
|
||||
reserved: [0; 2176],
|
||||
min_vault_to_deposits_ratio: 0.2,
|
||||
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,
|
||||
reserved: [0; 2136],
|
||||
};
|
||||
require_gt!(bank.max_rate, MINIMUM_MAX_RATE);
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ pub fn token_update_index_and_rate(ctx: Context<TokenUpdateIndexAndRate>) -> Res
|
|||
.verify_banks_ais(ctx.remaining_accounts)?;
|
||||
|
||||
let clock = Clock::get()?;
|
||||
let now_ts = clock.unix_timestamp;
|
||||
let now_ts: u64 = clock.unix_timestamp.try_into().unwrap();
|
||||
|
||||
// compute indexed_total
|
||||
let mut indexed_total_deposits = I80F48::ZERO;
|
||||
|
|
|
@ -93,16 +93,17 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
|
|||
amount
|
||||
};
|
||||
|
||||
require!(
|
||||
allow_borrow || amount <= native_position,
|
||||
MangoError::SomeError
|
||||
);
|
||||
let is_borrow = amount > native_position;
|
||||
require!(allow_borrow || !is_borrow, MangoError::SomeError);
|
||||
|
||||
let amount_i80f48 = I80F48::from(amount);
|
||||
|
||||
// Update the bank and position
|
||||
let (position_is_active, loan_origination_fee) =
|
||||
bank.withdraw_with_fee(position, amount_i80f48)?;
|
||||
let (position_is_active, loan_origination_fee) = bank.withdraw_with_fee(
|
||||
position,
|
||||
amount_i80f48,
|
||||
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||
)?;
|
||||
|
||||
// Provide a readable error message in case the vault doesn't have enough tokens
|
||||
if ctx.accounts.vault.amount < amount {
|
||||
|
@ -178,5 +179,21 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
|
|||
});
|
||||
}
|
||||
|
||||
// Prevent borrowing away the full bank vault. Keep some in reserve to satisfy non-borrow withdraws
|
||||
let bank_native_deposits = bank.native_deposits();
|
||||
if bank_native_deposits != I80F48::ZERO && is_borrow {
|
||||
ctx.accounts.vault.reload()?;
|
||||
let bank_native_deposits: f64 = bank_native_deposits.checked_to_num().unwrap();
|
||||
let vault_amount = ctx.accounts.vault.amount as f64;
|
||||
if vault_amount < bank.min_vault_to_deposits_ratio * bank_native_deposits {
|
||||
return err!(MangoError::BankBorrowLimitReached).with_context(|| {
|
||||
format!(
|
||||
"vault_amount ({:?}) below min_vault_to_deposits_ratio * bank_native_deposits ({:?})",
|
||||
vault_amount, bank.min_vault_to_deposits_ratio * bank_native_deposits,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -74,6 +74,9 @@ pub mod mango_v4 {
|
|||
maint_liab_weight: f32,
|
||||
init_liab_weight: f32,
|
||||
liquidation_fee: f32,
|
||||
min_vault_to_deposits_ratio: f64,
|
||||
net_borrows_window_size_ts: u64,
|
||||
net_borrows_limit_native: i64,
|
||||
) -> Result<()> {
|
||||
instructions::token_register(
|
||||
ctx,
|
||||
|
@ -88,6 +91,9 @@ pub mod mango_v4 {
|
|||
maint_liab_weight,
|
||||
init_liab_weight,
|
||||
liquidation_fee,
|
||||
min_vault_to_deposits_ratio,
|
||||
net_borrows_window_size_ts,
|
||||
net_borrows_limit_native,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -116,6 +122,9 @@ pub mod mango_v4 {
|
|||
stable_price_delay_interval_seconds_opt: Option<u32>,
|
||||
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_window_size_ts_opt: Option<u64>,
|
||||
) -> Result<()> {
|
||||
instructions::token_edit(
|
||||
ctx,
|
||||
|
@ -133,6 +142,9 @@ pub mod mango_v4 {
|
|||
stable_price_delay_interval_seconds_opt,
|
||||
stable_price_delay_growth_limit_opt,
|
||||
stable_price_growth_limit_opt,
|
||||
min_vault_to_deposits_ratio_opt,
|
||||
net_borrows_limit_native_opt,
|
||||
net_borrows_window_size_ts_opt,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use super::{OracleConfig, TokenIndex, TokenPosition};
|
||||
use crate::accounts_zerocopy::KeyedAccountReader;
|
||||
use crate::error::{Contextable, MangoError};
|
||||
use crate::state::{oracle, StablePriceModel};
|
||||
use crate::util;
|
||||
use crate::util::checked_math as cm;
|
||||
|
@ -57,8 +58,8 @@ pub struct Bank {
|
|||
pub indexed_deposits: I80F48,
|
||||
pub indexed_borrows: I80F48,
|
||||
|
||||
pub index_last_updated: i64,
|
||||
pub bank_rate_last_updated: i64,
|
||||
pub index_last_updated: u64,
|
||||
pub bank_rate_last_updated: u64,
|
||||
|
||||
pub avg_utilization: I80F48,
|
||||
|
||||
|
@ -106,8 +107,14 @@ pub struct Bank {
|
|||
|
||||
pub stable_price_model: StablePriceModel,
|
||||
|
||||
pub min_vault_to_deposits_ratio: f64,
|
||||
pub net_borrows_window_size_ts: u64,
|
||||
pub last_net_borrows_window_start_ts: u64,
|
||||
pub net_borrows_limit_native: i64,
|
||||
pub net_borrows_window_native: i64,
|
||||
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub reserved: [u8; 2176],
|
||||
pub reserved: [u8; 2136],
|
||||
}
|
||||
const_assert_eq!(size_of::<Bank>(), 3112);
|
||||
const_assert_eq!(size_of::<Bank>() % 8, 0);
|
||||
|
@ -118,6 +125,7 @@ impl Bank {
|
|||
vault: Pubkey,
|
||||
bank_num: u32,
|
||||
bump: u8,
|
||||
now_ts: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
// values that must be reset/changed
|
||||
|
@ -163,7 +171,12 @@ impl Bank {
|
|||
mint_decimals: existing_bank.mint_decimals,
|
||||
oracle_config: existing_bank.oracle_config.clone(),
|
||||
stable_price_model: StablePriceModel::default(),
|
||||
reserved: [0; 2176],
|
||||
min_vault_to_deposits_ratio: existing_bank.min_vault_to_deposits_ratio,
|
||||
net_borrows_limit_native: existing_bank.net_borrows_limit_native,
|
||||
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,
|
||||
reserved: [0; 2136],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,8 +202,13 @@ impl Bank {
|
|||
///
|
||||
/// native_amount must be >= 0
|
||||
/// fractional deposits can be relevant during liquidation, for example
|
||||
pub fn deposit(&mut self, position: &mut TokenPosition, native_amount: I80F48) -> Result<bool> {
|
||||
self.deposit_internal(position, native_amount, !position.is_in_use())
|
||||
pub fn deposit(
|
||||
&mut self,
|
||||
position: &mut TokenPosition,
|
||||
native_amount: I80F48,
|
||||
now_ts: u64,
|
||||
) -> Result<bool> {
|
||||
self.deposit_internal_wrapper(position, native_amount, !position.is_in_use(), now_ts)
|
||||
}
|
||||
|
||||
/// Like `deposit()`, but allows dusting of in-use accounts.
|
||||
|
@ -200,11 +218,26 @@ impl Bank {
|
|||
&mut self,
|
||||
position: &mut TokenPosition,
|
||||
native_amount: I80F48,
|
||||
now_ts: u64,
|
||||
) -> Result<bool> {
|
||||
self.deposit_internal(position, native_amount, true)
|
||||
self.deposit_internal_wrapper(position, native_amount, true, now_ts)
|
||||
.map(|not_dusted| not_dusted || position.is_in_use())
|
||||
}
|
||||
|
||||
pub fn deposit_internal_wrapper(
|
||||
&mut self,
|
||||
position: &mut TokenPosition,
|
||||
native_amount: I80F48,
|
||||
allow_dusting: bool,
|
||||
now_ts: u64,
|
||||
) -> Result<bool> {
|
||||
self.update_net_borrows(-native_amount, now_ts)?;
|
||||
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);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Internal function to deposit funds
|
||||
pub fn deposit_internal(
|
||||
&mut self,
|
||||
|
@ -213,6 +246,7 @@ impl Bank {
|
|||
allow_dusting: bool,
|
||||
) -> Result<bool> {
|
||||
require_gte!(native_amount, 0);
|
||||
|
||||
let native_position = position.native(self);
|
||||
let opening_indexed_position = position.indexed_position;
|
||||
|
||||
|
@ -240,14 +274,12 @@ impl Bank {
|
|||
// pay back borrows only, leaving a negative position
|
||||
cm!(self.indexed_borrows -= indexed_change);
|
||||
position.indexed_position = new_indexed_value;
|
||||
self.update_cumulative_interest(position, opening_indexed_position);
|
||||
return Ok(true);
|
||||
} else if new_native_position < I80F48::ONE && allow_dusting {
|
||||
// if there's less than one token deposited, zero the position
|
||||
cm!(self.dust += new_native_position);
|
||||
cm!(self.indexed_borrows += position.indexed_position);
|
||||
position.indexed_position = I80F48::ZERO;
|
||||
self.update_cumulative_interest(position, opening_indexed_position);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
|
@ -263,7 +295,6 @@ impl Bank {
|
|||
let indexed_change = div_rounding_up(native_amount, self.deposit_index);
|
||||
cm!(self.indexed_deposits += indexed_change);
|
||||
cm!(position.indexed_position += indexed_change);
|
||||
self.update_cumulative_interest(position, opening_indexed_position);
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
@ -280,9 +311,15 @@ impl Bank {
|
|||
&mut self,
|
||||
position: &mut TokenPosition,
|
||||
native_amount: I80F48,
|
||||
now_ts: u64,
|
||||
) -> Result<bool> {
|
||||
let (position_is_active, _) =
|
||||
self.withdraw_internal(position, native_amount, false, !position.is_in_use())?;
|
||||
let (position_is_active, _) = self.withdraw_internal_wrapper(
|
||||
position,
|
||||
native_amount,
|
||||
false,
|
||||
!position.is_in_use(),
|
||||
now_ts,
|
||||
)?;
|
||||
|
||||
Ok(position_is_active)
|
||||
}
|
||||
|
@ -294,8 +331,9 @@ impl Bank {
|
|||
&mut self,
|
||||
position: &mut TokenPosition,
|
||||
native_amount: I80F48,
|
||||
now_ts: u64,
|
||||
) -> Result<bool> {
|
||||
self.withdraw_internal(position, native_amount, false, true)
|
||||
self.withdraw_internal_wrapper(position, native_amount, false, true, now_ts)
|
||||
.map(|(not_dusted, _)| not_dusted || position.is_in_use())
|
||||
}
|
||||
|
||||
|
@ -311,8 +349,30 @@ impl Bank {
|
|||
&mut self,
|
||||
position: &mut TokenPosition,
|
||||
native_amount: I80F48,
|
||||
now_ts: u64,
|
||||
) -> Result<(bool, I80F48)> {
|
||||
self.withdraw_internal(position, native_amount, true, !position.is_in_use())
|
||||
self.withdraw_internal_wrapper(position, native_amount, true, !position.is_in_use(), now_ts)
|
||||
}
|
||||
|
||||
/// Internal function to withdraw funds
|
||||
fn withdraw_internal_wrapper(
|
||||
&mut self,
|
||||
position: &mut TokenPosition,
|
||||
mut native_amount: I80F48,
|
||||
with_loan_origination_fee: bool,
|
||||
allow_dusting: bool,
|
||||
now_ts: u64,
|
||||
) -> Result<(bool, I80F48)> {
|
||||
let opening_indexed_position = position.indexed_position;
|
||||
let res = self.withdraw_internal(
|
||||
position,
|
||||
native_amount,
|
||||
with_loan_origination_fee,
|
||||
allow_dusting,
|
||||
now_ts,
|
||||
);
|
||||
self.update_cumulative_interest(position, opening_indexed_position);
|
||||
res
|
||||
}
|
||||
|
||||
/// Internal function to withdraw funds
|
||||
|
@ -322,6 +382,7 @@ impl Bank {
|
|||
mut native_amount: I80F48,
|
||||
with_loan_origination_fee: bool,
|
||||
allow_dusting: bool,
|
||||
now_ts: u64,
|
||||
) -> Result<(bool, I80F48)> {
|
||||
require_gte!(native_amount, 0);
|
||||
let native_position = position.native(self);
|
||||
|
@ -336,14 +397,12 @@ impl Bank {
|
|||
cm!(self.dust += new_native_position);
|
||||
cm!(self.indexed_deposits -= position.indexed_position);
|
||||
position.indexed_position = I80F48::ZERO;
|
||||
self.update_cumulative_interest(position, opening_indexed_position);
|
||||
return Ok((false, I80F48::ZERO));
|
||||
} else {
|
||||
// withdraw some deposits leaving a positive balance
|
||||
let indexed_change = cm!(native_amount / self.deposit_index);
|
||||
cm!(self.indexed_deposits -= indexed_change);
|
||||
cm!(position.indexed_position -= indexed_change);
|
||||
self.update_cumulative_interest(position, opening_indexed_position);
|
||||
return Ok((true, I80F48::ZERO));
|
||||
}
|
||||
}
|
||||
|
@ -366,7 +425,10 @@ impl Bank {
|
|||
let indexed_change = cm!(native_amount / self.borrow_index);
|
||||
cm!(self.indexed_borrows += indexed_change);
|
||||
cm!(position.indexed_position -= indexed_change);
|
||||
self.update_cumulative_interest(position, opening_indexed_position);
|
||||
|
||||
// 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)?;
|
||||
|
||||
Ok((true, loan_origination_fee))
|
||||
}
|
||||
|
@ -376,13 +438,19 @@ impl Bank {
|
|||
&mut self,
|
||||
position: &mut TokenPosition,
|
||||
already_borrowed_native_amount: I80F48,
|
||||
now_ts: u64,
|
||||
) -> Result<(bool, I80F48)> {
|
||||
let loan_origination_fee =
|
||||
cm!(self.loan_origination_fee_rate * already_borrowed_native_amount);
|
||||
cm!(self.collected_fees_native += loan_origination_fee);
|
||||
|
||||
let (position_is_active, _) =
|
||||
self.withdraw_internal(position, loan_origination_fee, false, !position.is_in_use())?;
|
||||
let (position_is_active, _) = self.withdraw_internal_wrapper(
|
||||
position,
|
||||
loan_origination_fee,
|
||||
false,
|
||||
!position.is_in_use(),
|
||||
now_ts,
|
||||
)?;
|
||||
|
||||
Ok((position_is_active, loan_origination_fee))
|
||||
}
|
||||
|
@ -392,11 +460,12 @@ impl Bank {
|
|||
&mut self,
|
||||
position: &mut TokenPosition,
|
||||
native_amount: I80F48,
|
||||
now_ts: u64,
|
||||
) -> Result<bool> {
|
||||
if native_amount >= 0 {
|
||||
self.deposit(position, native_amount)
|
||||
self.deposit(position, native_amount, now_ts)
|
||||
} else {
|
||||
self.withdraw_without_fee(position, -native_amount)
|
||||
self.withdraw_without_fee(position, -native_amount, now_ts)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -405,14 +474,40 @@ impl Bank {
|
|||
&mut self,
|
||||
position: &mut TokenPosition,
|
||||
native_amount: I80F48,
|
||||
now_ts: u64,
|
||||
) -> Result<(bool, I80F48)> {
|
||||
if native_amount >= 0 {
|
||||
Ok((self.deposit(position, native_amount)?, I80F48::ZERO))
|
||||
Ok((self.deposit(position, native_amount, now_ts)?, I80F48::ZERO))
|
||||
} else {
|
||||
self.withdraw_with_fee(position, -native_amount)
|
||||
self.withdraw_with_fee(position, -native_amount, now_ts)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_net_borrows(&mut self, native_amount: I80F48, now_ts: u64) -> 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 {
|
||||
// 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())
|
||||
};
|
||||
|
||||
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
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_cumulative_interest(
|
||||
&self,
|
||||
position: &mut TokenPosition,
|
||||
|
@ -524,9 +619,9 @@ impl Bank {
|
|||
&self,
|
||||
indexed_total_deposits: I80F48,
|
||||
indexed_total_borrows: I80F48,
|
||||
now_ts: i64,
|
||||
now_ts: u64,
|
||||
) -> I80F48 {
|
||||
if now_ts <= 0 {
|
||||
if now_ts == 0 {
|
||||
return I80F48::ZERO;
|
||||
}
|
||||
|
||||
|
@ -664,6 +759,8 @@ 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.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);
|
||||
|
@ -701,7 +798,8 @@ mod tests {
|
|||
//
|
||||
|
||||
let change = I80F48::from(change);
|
||||
let (is_active, _) = bank.change_with_fee(&mut account, change)?;
|
||||
let dummy_now_ts = 1 as u64;
|
||||
let (is_active, _) = bank.change_with_fee(&mut account, change, dummy_now_ts)?;
|
||||
|
||||
let mut expected_native = start_native + change;
|
||||
if expected_native >= 0.0 && expected_native < 1.0 && !is_in_use {
|
||||
|
@ -742,7 +840,7 @@ mod tests {
|
|||
bank.index_last_updated = 1000;
|
||||
|
||||
let compute_new_avg_utilization_runner =
|
||||
|bank: &mut Bank, utilization: I80F48, now_ts: i64| {
|
||||
|bank: &mut Bank, utilization: I80F48, now_ts: u64| {
|
||||
bank.avg_utilization =
|
||||
bank.compute_new_avg_utilization(I80F48::ONE, utilization, now_ts);
|
||||
bank.index_last_updated = now_ts;
|
||||
|
|
|
@ -1422,6 +1422,8 @@ mod tests {
|
|||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub const DUMMY_NOW_TS: u64 = 0;
|
||||
|
||||
#[test]
|
||||
fn test_precision() {
|
||||
// I80F48 can only represent until 1/2^48
|
||||
|
@ -1529,6 +1531,8 @@ mod tests {
|
|||
bank.data().maint_asset_weight = I80F48::from_num(1.0 - maint_weights);
|
||||
bank.data().maint_liab_weight = I80F48::from_num(1.0 + maint_weights);
|
||||
bank.data().stable_price_model.reset_to_price(price, 0);
|
||||
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, oracle)
|
||||
}
|
||||
|
||||
|
@ -1578,6 +1582,7 @@ mod tests {
|
|||
.deposit(
|
||||
account.ensure_token_position(1).unwrap().0,
|
||||
I80F48::from(100),
|
||||
DUMMY_NOW_TS,
|
||||
)
|
||||
.unwrap();
|
||||
bank2
|
||||
|
@ -1585,6 +1590,7 @@ mod tests {
|
|||
.withdraw_without_fee(
|
||||
account.ensure_token_position(4).unwrap().0,
|
||||
I80F48::from(10),
|
||||
DUMMY_NOW_TS,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -1760,6 +1766,7 @@ mod tests {
|
|||
.change_without_fee(
|
||||
account.ensure_token_position(1).unwrap().0,
|
||||
I80F48::from(testcase.token1),
|
||||
DUMMY_NOW_TS,
|
||||
)
|
||||
.unwrap();
|
||||
bank2
|
||||
|
@ -1767,6 +1774,7 @@ mod tests {
|
|||
.change_without_fee(
|
||||
account.ensure_token_position(4).unwrap().0,
|
||||
I80F48::from(testcase.token2),
|
||||
DUMMY_NOW_TS,
|
||||
)
|
||||
.unwrap();
|
||||
bank3
|
||||
|
@ -1774,6 +1782,7 @@ mod tests {
|
|||
.change_without_fee(
|
||||
account.ensure_token_position(5).unwrap().0,
|
||||
I80F48::from(testcase.token3),
|
||||
DUMMY_NOW_TS,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -2285,6 +2294,7 @@ mod tests {
|
|||
.change_without_fee(
|
||||
account.ensure_token_position(1).unwrap().0,
|
||||
I80F48::from(100),
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -2366,6 +2376,7 @@ mod tests {
|
|||
.change_without_fee(
|
||||
account.ensure_token_position(1).unwrap().0,
|
||||
I80F48::from(100),
|
||||
DUMMY_NOW_TS,
|
||||
)
|
||||
.unwrap();
|
||||
bank1
|
||||
|
@ -2373,6 +2384,7 @@ mod tests {
|
|||
.change_without_fee(
|
||||
account2.ensure_token_position(1).unwrap().0,
|
||||
I80F48::from(-100),
|
||||
DUMMY_NOW_TS,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -772,6 +772,10 @@ pub struct TokenRegisterInstruction {
|
|||
pub init_liab_weight: f32,
|
||||
pub liquidation_fee: f32,
|
||||
|
||||
pub min_vault_to_deposits_ratio: f64,
|
||||
pub net_borrows_limit_native: i64,
|
||||
pub net_borrows_window_size_ts: u64,
|
||||
|
||||
pub group: Pubkey,
|
||||
pub admin: TestKeypair,
|
||||
pub mint: Pubkey,
|
||||
|
@ -812,6 +816,9 @@ impl ClientInstruction for TokenRegisterInstruction {
|
|||
maint_liab_weight: self.maint_liab_weight,
|
||||
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_window_size_ts: self.net_borrows_window_size_ts,
|
||||
};
|
||||
|
||||
let bank = Pubkey::find_program_address(
|
||||
|
@ -1069,6 +1076,82 @@ impl ClientInstruction for TokenResetStablePriceModel {
|
|||
stable_price_delay_interval_seconds_opt: None,
|
||||
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_window_size_ts_opt: None,
|
||||
};
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: self.group,
|
||||
admin: self.admin.pubkey(),
|
||||
mint_info: mint_info_key,
|
||||
oracle: mint_info.oracle,
|
||||
};
|
||||
|
||||
let mut instruction = make_instruction(program_id, &accounts, instruction);
|
||||
instruction
|
||||
.accounts
|
||||
.extend(mint_info.banks().iter().map(|&k| AccountMeta {
|
||||
pubkey: k,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
}));
|
||||
(accounts, instruction)
|
||||
}
|
||||
|
||||
fn signers(&self) -> Vec<TestKeypair> {
|
||||
vec![self.admin]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TokenEditNetBorrows {
|
||||
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_window_size_ts_opt: Option<u64>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ClientInstruction for TokenEditNetBorrows {
|
||||
type Accounts = mango_v4::accounts::TokenEdit;
|
||||
type Instruction = mango_v4::instruction::TokenEdit;
|
||||
async fn to_instruction(
|
||||
&self,
|
||||
account_loader: impl ClientAccountLoader + 'async_trait,
|
||||
) -> (Self::Accounts, instruction::Instruction) {
|
||||
let program_id = mango_v4::id();
|
||||
|
||||
let mint_info_key = Pubkey::find_program_address(
|
||||
&[
|
||||
b"MintInfo".as_ref(),
|
||||
self.group.as_ref(),
|
||||
self.mint.as_ref(),
|
||||
],
|
||||
&program_id,
|
||||
)
|
||||
.0;
|
||||
let mint_info: MintInfo = account_loader.load(&mint_info_key).await.unwrap();
|
||||
|
||||
let instruction = Self::Instruction {
|
||||
oracle_opt: None,
|
||||
oracle_config_opt: None,
|
||||
group_insurance_fund_opt: None,
|
||||
interest_rate_params_opt: None,
|
||||
loan_fee_rate_opt: None,
|
||||
loan_origination_fee_rate_opt: None,
|
||||
maint_asset_weight_opt: None,
|
||||
init_asset_weight_opt: None,
|
||||
maint_liab_weight_opt: None,
|
||||
init_liab_weight_opt: None,
|
||||
liquidation_fee_opt: None,
|
||||
stable_price_delay_interval_seconds_opt: None,
|
||||
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_window_size_ts_opt: self.net_borrows_window_size_ts_opt,
|
||||
};
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
|
|
|
@ -105,6 +105,9 @@ impl<'a> GroupWithTokensConfig {
|
|||
admin,
|
||||
mint: mint.pubkey,
|
||||
payer,
|
||||
min_vault_to_deposits_ratio: 0.2,
|
||||
net_borrows_limit_native: 1_000_000_000_000,
|
||||
net_borrows_window_size_ts: 24 * 60 * 60,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -0,0 +1,352 @@
|
|||
#![cfg(all(feature = "test-bpf"))]
|
||||
|
||||
use mango_setup::*;
|
||||
use program_test::*;
|
||||
use solana_program_test::*;
|
||||
use solana_sdk::transport::TransportError;
|
||||
|
||||
mod program_test;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_bank_utilization_based_borrow_limit() -> Result<(), TransportError> {
|
||||
let context = TestContext::new().await;
|
||||
let solana = &context.solana.clone();
|
||||
|
||||
let admin = TestKeypair::new();
|
||||
let owner = context.users[0].key;
|
||||
let payer = context.users[1].key;
|
||||
let mints = &context.mints[0..2];
|
||||
let payer_mint_accounts = &context.users[1].token_accounts[0..=2];
|
||||
|
||||
let initial_token_deposit = 10_000;
|
||||
|
||||
//
|
||||
// SETUP: Create a group and an account
|
||||
//
|
||||
|
||||
let GroupWithTokens { group, .. } = GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints: mints.to_vec(),
|
||||
..GroupWithTokensConfig::default()
|
||||
}
|
||||
.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
|
||||
//
|
||||
{
|
||||
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 deposit_amount = initial_token_deposit;
|
||||
|
||||
// account_1 tries to borrow all existing deposits on mint_0
|
||||
// should fail because borrow limit would be reached
|
||||
let res = send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: deposit_amount,
|
||||
allow_borrow: true,
|
||||
account: account_1,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
assert!(res.is_err());
|
||||
solana.advance_clock().await;
|
||||
|
||||
// account_1 tries to borrow < limit on mint_0
|
||||
// should succeed because borrow limit won't be reached
|
||||
send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: deposit_amount / 10 * 7,
|
||||
allow_borrow: true,
|
||||
account: account_1,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
solana.advance_clock().await;
|
||||
|
||||
// account_0 tries to withdraw all remaining on mint_0
|
||||
// should succeed because withdraws without borrows are not limited
|
||||
send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: deposit_amount / 10 * 3,
|
||||
allow_borrow: false,
|
||||
account: account_0,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_bank_net_borrows_based_borrow_limit() -> Result<(), TransportError> {
|
||||
let context = TestContext::new().await;
|
||||
let solana = &context.solana.clone();
|
||||
|
||||
let admin = TestKeypair::new();
|
||||
let owner = context.users[0].key;
|
||||
let payer = context.users[1].key;
|
||||
let mints = &context.mints[0..2];
|
||||
let payer_mint_accounts = &context.users[1].token_accounts[0..=2];
|
||||
|
||||
//
|
||||
// SETUP: Create a group and an account
|
||||
//
|
||||
|
||||
let GroupWithTokens { group, tokens, .. } = GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints: mints.to_vec(),
|
||||
..GroupWithTokensConfig::default()
|
||||
}
|
||||
.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;
|
||||
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
//
|
||||
// SETUP: Deposit user funds
|
||||
//
|
||||
{
|
||||
// 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();
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// We elapse at least 3 seconds, so that next block is in new window
|
||||
solana.advance_clock().await;
|
||||
solana.advance_clock().await;
|
||||
solana.advance_clock().await;
|
||||
|
||||
{
|
||||
// succeeds because borrow is less than net borrow limit
|
||||
send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: 5000,
|
||||
allow_borrow: true,
|
||||
account: account_1,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// fails because borrow is greater than remaining margin in net borrow limit
|
||||
let res = send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: 4000,
|
||||
allow_borrow: true,
|
||||
account: account_1,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
assert!(res.is_err());
|
||||
|
||||
// succeeds because is not a borrow
|
||||
send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: 4000,
|
||||
allow_borrow: false,
|
||||
account: account_0,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// We elapse at least 3 seconds, so that next block is in new window
|
||||
solana.advance_clock().await;
|
||||
solana.advance_clock().await;
|
||||
solana.advance_clock().await;
|
||||
|
||||
// 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();
|
||||
solana.advance_clock().await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -229,6 +229,9 @@ export class MangoClient {
|
|||
maintLiabWeight: number,
|
||||
initLiabWeight: number,
|
||||
liquidationFee: number,
|
||||
minVaultToDepositsRatio: number,
|
||||
netBorrowsWindowSizeTs: number,
|
||||
netBorrowsLimitNative: number,
|
||||
): Promise<TransactionSignature> {
|
||||
return await this.program.methods
|
||||
.tokenRegister(
|
||||
|
@ -243,6 +246,9 @@ export class MangoClient {
|
|||
maintLiabWeight,
|
||||
initLiabWeight,
|
||||
liquidationFee,
|
||||
minVaultToDepositsRatio,
|
||||
new BN(netBorrowsWindowSizeTs),
|
||||
new BN(netBorrowsLimitNative),
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
@ -290,6 +296,9 @@ export class MangoClient {
|
|||
maintLiabWeight: number | null,
|
||||
initLiabWeight: number | null,
|
||||
liquidationFee: number | null,
|
||||
minVaultToDepositsRatio: number | null,
|
||||
netBorrowsLimitNative: number | null,
|
||||
netBorrowsWindowSizeTs: number | null,
|
||||
): Promise<TransactionSignature> {
|
||||
const bank = group.getFirstBankByMint(mintPk);
|
||||
const mintInfo = group.mintInfosMapByTokenIndex.get(bank.tokenIndex)!;
|
||||
|
@ -307,6 +316,9 @@ export class MangoClient {
|
|||
maintLiabWeight,
|
||||
initLiabWeight,
|
||||
liquidationFee,
|
||||
minVaultToDepositsRatio,
|
||||
netBorrowsLimitNative,
|
||||
netBorrowsWindowSizeTs,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
@ -467,7 +479,6 @@ export class MangoClient {
|
|||
group: group.publicKey,
|
||||
admin: (this.program.provider as AnchorProvider).wallet.publicKey,
|
||||
oracle: oraclePk,
|
||||
payer: (this.program.provider as AnchorProvider).wallet.publicKey,
|
||||
})
|
||||
.rpc();
|
||||
}
|
||||
|
@ -1444,25 +1455,28 @@ export class MangoClient {
|
|||
public async perpEditMarket(
|
||||
group: Group,
|
||||
perpMarketIndex: PerpMarketIndex,
|
||||
oracle: PublicKey,
|
||||
oracleConfig: OracleConfigParams,
|
||||
baseDecimals: number,
|
||||
maintAssetWeight: number,
|
||||
initAssetWeight: number,
|
||||
maintLiabWeight: number,
|
||||
initLiabWeight: number,
|
||||
liquidationFee: number,
|
||||
makerFee: number,
|
||||
takerFee: number,
|
||||
feePenalty: number,
|
||||
minFunding: number,
|
||||
maxFunding: number,
|
||||
impactQuantity: number,
|
||||
groupInsuranceFund: boolean,
|
||||
trustedMarket: boolean,
|
||||
settleFeeFlat: number,
|
||||
settleFeeAmountThreshold: number,
|
||||
settleFeeFractionLowHealth: number,
|
||||
oracle: PublicKey | null,
|
||||
oracleConfig: OracleConfigParams | null,
|
||||
baseDecimals: number | null,
|
||||
maintAssetWeight: number | null,
|
||||
initAssetWeight: number | null,
|
||||
maintLiabWeight: number | null,
|
||||
initLiabWeight: number | null,
|
||||
liquidationFee: number | null,
|
||||
makerFee: number | null,
|
||||
takerFee: number | null,
|
||||
feePenalty: number | null,
|
||||
minFunding: number | null,
|
||||
maxFunding: number | null,
|
||||
impactQuantity: number | null,
|
||||
groupInsuranceFund: boolean | null,
|
||||
trustedMarket: boolean | null,
|
||||
settleFeeFlat: number | null,
|
||||
settleFeeAmountThreshold: number | null,
|
||||
settleFeeFractionLowHealth: number | null,
|
||||
stablePriceDelayIntervalSeconds: number | null,
|
||||
stablePriceDelayGrowthLimit: number | null,
|
||||
stablePriceGrowthLimit: number | null,
|
||||
): Promise<TransactionSignature> {
|
||||
const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex);
|
||||
|
||||
|
@ -1480,13 +1494,16 @@ export class MangoClient {
|
|||
takerFee,
|
||||
minFunding,
|
||||
maxFunding,
|
||||
new BN(impactQuantity),
|
||||
impactQuantity ? new BN(impactQuantity) : null,
|
||||
groupInsuranceFund,
|
||||
trustedMarket,
|
||||
feePenalty,
|
||||
settleFeeFlat,
|
||||
settleFeeAmountThreshold,
|
||||
settleFeeFractionLowHealth,
|
||||
stablePriceDelayIntervalSeconds,
|
||||
stablePriceDelayGrowthLimit,
|
||||
stablePriceGrowthLimit,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
|
|
@ -33,6 +33,10 @@ const MAINNET_ORACLES = new Map([
|
|||
['MNGO', '79wm3jjcPr6RaNQ4DGvP5KxG1mNd3gEBsg6FsNVFezK4'],
|
||||
]);
|
||||
|
||||
const MIN_VAULT_TO_DEPOSITS_RATIO = 0.2;
|
||||
const NET_BORROWS_WINDOW_SIZE_TS = 24 * 60 * 60;
|
||||
const NET_BORROWS_LIMIT_NATIVE = 1 * Math.pow(10, 7) * Math.pow(10, 6);
|
||||
|
||||
async function createGroup() {
|
||||
const admin = Keypair.fromSecretKey(
|
||||
Buffer.from(
|
||||
|
@ -129,6 +133,9 @@ async function registerTokens() {
|
|||
1,
|
||||
1,
|
||||
0,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
|
||||
console.log(`Registering USDT...`);
|
||||
|
@ -149,6 +156,9 @@ async function registerTokens() {
|
|||
1.05,
|
||||
1.1,
|
||||
0.025, // rule of thumb used - half of maintLiabWeight
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
|
||||
console.log(`Registering BTC...`);
|
||||
|
@ -169,6 +179,9 @@ async function registerTokens() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
|
||||
console.log(`Registering ETH...`);
|
||||
|
@ -189,6 +202,9 @@ async function registerTokens() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
|
||||
console.log(`Registering soETH...`);
|
||||
|
@ -209,6 +225,9 @@ async function registerTokens() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
|
||||
console.log(`Registering SOL...`);
|
||||
|
@ -229,6 +248,9 @@ async function registerTokens() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
|
||||
console.log(`Registering mSOL...`);
|
||||
|
@ -249,6 +271,9 @@ async function registerTokens() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
|
||||
// log tokens/banks
|
||||
|
|
|
@ -341,6 +341,18 @@ export type MangoV4 = {
|
|||
{
|
||||
"name": "liquidationFee",
|
||||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "minVaultToDepositsRatio",
|
||||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsWindowSizeTs",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsLimitNative",
|
||||
"type": "i64"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -499,6 +511,11 @@ export type MangoV4 = {
|
|||
"name": "mintInfo",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "oracle",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
|
@ -571,6 +588,24 @@ export type MangoV4 = {
|
|||
"type": {
|
||||
"option": "f32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stablePriceDelayIntervalSecondsOpt",
|
||||
"type": {
|
||||
"option": "u32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stablePriceDelayGrowthLimitOpt",
|
||||
"type": {
|
||||
"option": "f32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stablePriceGrowthLimitOpt",
|
||||
"type": {
|
||||
"option": "f32"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -1058,11 +1093,6 @@ export type MangoV4 = {
|
|||
"name": "oracle",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "payer",
|
||||
"isMut": true,
|
||||
"isSigner": true
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
|
@ -2416,6 +2446,11 @@ export type MangoV4 = {
|
|||
"name": "perpMarket",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "oracle",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
|
@ -2534,6 +2569,24 @@ export type MangoV4 = {
|
|||
"type": {
|
||||
"option": "f32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stablePriceDelayIntervalSecondsOpt",
|
||||
"type": {
|
||||
"option": "u32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stablePriceDelayGrowthLimitOpt",
|
||||
"type": {
|
||||
"option": "f32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stablePriceGrowthLimitOpt",
|
||||
"type": {
|
||||
"option": "f32"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -3131,11 +3184,6 @@ export type MangoV4 = {
|
|||
"name": "orderbook",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "oracle",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
|
@ -3391,11 +3439,11 @@ export type MangoV4 = {
|
|||
},
|
||||
{
|
||||
"name": "indexLastUpdated",
|
||||
"type": "i64"
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "bankRateLastUpdated",
|
||||
"type": "i64"
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "avgUtilization",
|
||||
|
@ -3523,12 +3571,38 @@ export type MangoV4 = {
|
|||
"defined": "OracleConfig"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stablePriceModel",
|
||||
"type": {
|
||||
"defined": "StablePriceModel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "minVaultToDepositsRatio",
|
||||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsWindowSizeTs",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "lastNetBorrowsWindowStartTs",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsLimitNative",
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsWindowNative",
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
2464
|
||||
2136
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -4167,12 +4241,18 @@ export type MangoV4 = {
|
|||
],
|
||||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "stablePriceModel",
|
||||
"type": {
|
||||
"defined": "StablePriceModel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
2244
|
||||
1956
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -4410,6 +4490,35 @@ export type MangoV4 = {
|
|||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Prices",
|
||||
"docs": [
|
||||
"Information about prices for a bank or perp market."
|
||||
],
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "oracle",
|
||||
"docs": [
|
||||
"The current oracle price"
|
||||
],
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stable",
|
||||
"docs": [
|
||||
"A \"stable\" price, provided by StablePriceModel"
|
||||
],
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "TokenInfo",
|
||||
"type": {
|
||||
|
@ -4444,19 +4553,13 @@ export type MangoV4 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "oraclePrice",
|
||||
"name": "prices",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
"defined": "Prices"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "balance",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "serum3MaxReserved",
|
||||
"name": "balanceNative",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
|
@ -4470,7 +4573,13 @@ export type MangoV4 = {
|
|||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "reserved",
|
||||
"name": "reservedBase",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "reservedQuote",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
|
@ -4524,10 +4633,20 @@ export type MangoV4 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "base",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
"name": "baseLotSize",
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
"name": "baseLots",
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
"name": "bidsBaseLots",
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
"name": "asksBaseLots",
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
"name": "quote",
|
||||
|
@ -4536,9 +4655,9 @@ export type MangoV4 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "oraclePrice",
|
||||
"name": "prices",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
"defined": "Prices"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -5387,6 +5506,116 @@ export type MangoV4 = {
|
|||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "StablePriceModel",
|
||||
"docs": [
|
||||
"Maintains a \"stable_price\" based on the oracle price.",
|
||||
"",
|
||||
"The stable price follows the oracle price, but its relative rate of",
|
||||
"change is limited (to `stable_growth_limit`) and futher reduced if",
|
||||
"the oracle price is far from the `delay_price`.",
|
||||
"",
|
||||
"Conceptually the `delay_price` is itself a time delayed",
|
||||
"(`24 * delay_interval_seconds`, assume 24h) and relative rate of change limited",
|
||||
"function of the oracle price. It is implemented as averaging the oracle",
|
||||
"price over every `delay_interval_seconds` (assume 1h) and then applying the",
|
||||
"`delay_growth_limit` between intervals."
|
||||
],
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "stablePrice",
|
||||
"docs": [
|
||||
"Current stable price to use in health"
|
||||
],
|
||||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "lastUpdateTimestamp",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "delayPrices",
|
||||
"docs": [
|
||||
"Stored delay_price for each delay_interval.",
|
||||
"If we want the delay_price to be 24h delayed, we would store one for each hour.",
|
||||
"This is used in a cyclical way: We use the maximally-delayed value at delay_interval_index",
|
||||
"and once enough time passes to move to the next delay interval, that gets overwritten and",
|
||||
"we use the next one."
|
||||
],
|
||||
"type": {
|
||||
"array": [
|
||||
"f64",
|
||||
24
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "delayAccumulatorPrice",
|
||||
"docs": [
|
||||
"The delay price is based on an average over each delay_interval. The contributions",
|
||||
"to the average are summed up here."
|
||||
],
|
||||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "delayAccumulatorTime",
|
||||
"docs": [
|
||||
"Accumulating the total time for the above average."
|
||||
],
|
||||
"type": "u32"
|
||||
},
|
||||
{
|
||||
"name": "delayIntervalSeconds",
|
||||
"docs": [
|
||||
"Length of a delay_interval"
|
||||
],
|
||||
"type": "u32"
|
||||
},
|
||||
{
|
||||
"name": "delayGrowthLimit",
|
||||
"docs": [
|
||||
"Maximal relative difference between two delay_price in consecutive intervals."
|
||||
],
|
||||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "stableGrowthLimit",
|
||||
"docs": [
|
||||
"Maximal per-second relative difference of the stable price.",
|
||||
"It gets further reduced if stable and delay price disagree."
|
||||
],
|
||||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "lastDelayIntervalIndex",
|
||||
"docs": [
|
||||
"The delay_interval_index that update() was last called on."
|
||||
],
|
||||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
7
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
48
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "TokenIndex",
|
||||
"docs": [
|
||||
|
@ -6140,6 +6369,11 @@ export type MangoV4 = {
|
|||
"type": "i128",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "stablePrice",
|
||||
"type": "i128",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "feesAccrued",
|
||||
"type": "i128",
|
||||
|
@ -6185,6 +6419,11 @@ export type MangoV4 = {
|
|||
"type": "i128",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "stablePrice",
|
||||
"type": "i128",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "collectedFees",
|
||||
"type": "i128",
|
||||
|
@ -6880,6 +7119,21 @@ export type MangoV4 = {
|
|||
"code": 6024,
|
||||
"name": "OracleStale",
|
||||
"msg": "an oracle is stale"
|
||||
},
|
||||
{
|
||||
"code": 6025,
|
||||
"name": "SettlementAmountMustBePositive",
|
||||
"msg": "settlement amount must always be positive"
|
||||
},
|
||||
{
|
||||
"code": 6026,
|
||||
"name": "BankBorrowLimitReached",
|
||||
"msg": "bank utilization has reached limit"
|
||||
},
|
||||
{
|
||||
"code": 6027,
|
||||
"name": "BankNetBorrowsLimitReached",
|
||||
"msg": "bank net borrows has reached limit"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -7227,6 +7481,18 @@ export const IDL: MangoV4 = {
|
|||
{
|
||||
"name": "liquidationFee",
|
||||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "minVaultToDepositsRatio",
|
||||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsWindowSizeTs",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsLimitNative",
|
||||
"type": "i64"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -7385,6 +7651,11 @@ export const IDL: MangoV4 = {
|
|||
"name": "mintInfo",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "oracle",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
|
@ -7457,6 +7728,24 @@ export const IDL: MangoV4 = {
|
|||
"type": {
|
||||
"option": "f32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stablePriceDelayIntervalSecondsOpt",
|
||||
"type": {
|
||||
"option": "u32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stablePriceDelayGrowthLimitOpt",
|
||||
"type": {
|
||||
"option": "f32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stablePriceGrowthLimitOpt",
|
||||
"type": {
|
||||
"option": "f32"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -7944,11 +8233,6 @@ export const IDL: MangoV4 = {
|
|||
"name": "oracle",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "payer",
|
||||
"isMut": true,
|
||||
"isSigner": true
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
|
@ -9302,6 +9586,11 @@ export const IDL: MangoV4 = {
|
|||
"name": "perpMarket",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "oracle",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
|
@ -9420,6 +9709,24 @@ export const IDL: MangoV4 = {
|
|||
"type": {
|
||||
"option": "f32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stablePriceDelayIntervalSecondsOpt",
|
||||
"type": {
|
||||
"option": "u32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stablePriceDelayGrowthLimitOpt",
|
||||
"type": {
|
||||
"option": "f32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stablePriceGrowthLimitOpt",
|
||||
"type": {
|
||||
"option": "f32"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -10017,11 +10324,6 @@ export const IDL: MangoV4 = {
|
|||
"name": "orderbook",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "oracle",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
|
@ -10277,11 +10579,11 @@ export const IDL: MangoV4 = {
|
|||
},
|
||||
{
|
||||
"name": "indexLastUpdated",
|
||||
"type": "i64"
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "bankRateLastUpdated",
|
||||
"type": "i64"
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "avgUtilization",
|
||||
|
@ -10409,12 +10711,38 @@ export const IDL: MangoV4 = {
|
|||
"defined": "OracleConfig"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stablePriceModel",
|
||||
"type": {
|
||||
"defined": "StablePriceModel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "minVaultToDepositsRatio",
|
||||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsWindowSizeTs",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "lastNetBorrowsWindowStartTs",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsLimitNative",
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
"name": "netBorrowsWindowNative",
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
2464
|
||||
2136
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -11053,12 +11381,18 @@ export const IDL: MangoV4 = {
|
|||
],
|
||||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "stablePriceModel",
|
||||
"type": {
|
||||
"defined": "StablePriceModel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
2244
|
||||
1956
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -11296,6 +11630,35 @@ export const IDL: MangoV4 = {
|
|||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Prices",
|
||||
"docs": [
|
||||
"Information about prices for a bank or perp market."
|
||||
],
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "oracle",
|
||||
"docs": [
|
||||
"The current oracle price"
|
||||
],
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stable",
|
||||
"docs": [
|
||||
"A \"stable\" price, provided by StablePriceModel"
|
||||
],
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "TokenInfo",
|
||||
"type": {
|
||||
|
@ -11330,19 +11693,13 @@ export const IDL: MangoV4 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "oraclePrice",
|
||||
"name": "prices",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
"defined": "Prices"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "balance",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "serum3MaxReserved",
|
||||
"name": "balanceNative",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
|
@ -11356,7 +11713,13 @@ export const IDL: MangoV4 = {
|
|||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "reserved",
|
||||
"name": "reservedBase",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "reservedQuote",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
|
@ -11410,10 +11773,20 @@ export const IDL: MangoV4 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "base",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
"name": "baseLotSize",
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
"name": "baseLots",
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
"name": "bidsBaseLots",
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
"name": "asksBaseLots",
|
||||
"type": "i64"
|
||||
},
|
||||
{
|
||||
"name": "quote",
|
||||
|
@ -11422,9 +11795,9 @@ export const IDL: MangoV4 = {
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "oraclePrice",
|
||||
"name": "prices",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
"defined": "Prices"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -12273,6 +12646,116 @@ export const IDL: MangoV4 = {
|
|||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "StablePriceModel",
|
||||
"docs": [
|
||||
"Maintains a \"stable_price\" based on the oracle price.",
|
||||
"",
|
||||
"The stable price follows the oracle price, but its relative rate of",
|
||||
"change is limited (to `stable_growth_limit`) and futher reduced if",
|
||||
"the oracle price is far from the `delay_price`.",
|
||||
"",
|
||||
"Conceptually the `delay_price` is itself a time delayed",
|
||||
"(`24 * delay_interval_seconds`, assume 24h) and relative rate of change limited",
|
||||
"function of the oracle price. It is implemented as averaging the oracle",
|
||||
"price over every `delay_interval_seconds` (assume 1h) and then applying the",
|
||||
"`delay_growth_limit` between intervals."
|
||||
],
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "stablePrice",
|
||||
"docs": [
|
||||
"Current stable price to use in health"
|
||||
],
|
||||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "lastUpdateTimestamp",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "delayPrices",
|
||||
"docs": [
|
||||
"Stored delay_price for each delay_interval.",
|
||||
"If we want the delay_price to be 24h delayed, we would store one for each hour.",
|
||||
"This is used in a cyclical way: We use the maximally-delayed value at delay_interval_index",
|
||||
"and once enough time passes to move to the next delay interval, that gets overwritten and",
|
||||
"we use the next one."
|
||||
],
|
||||
"type": {
|
||||
"array": [
|
||||
"f64",
|
||||
24
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "delayAccumulatorPrice",
|
||||
"docs": [
|
||||
"The delay price is based on an average over each delay_interval. The contributions",
|
||||
"to the average are summed up here."
|
||||
],
|
||||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "delayAccumulatorTime",
|
||||
"docs": [
|
||||
"Accumulating the total time for the above average."
|
||||
],
|
||||
"type": "u32"
|
||||
},
|
||||
{
|
||||
"name": "delayIntervalSeconds",
|
||||
"docs": [
|
||||
"Length of a delay_interval"
|
||||
],
|
||||
"type": "u32"
|
||||
},
|
||||
{
|
||||
"name": "delayGrowthLimit",
|
||||
"docs": [
|
||||
"Maximal relative difference between two delay_price in consecutive intervals."
|
||||
],
|
||||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "stableGrowthLimit",
|
||||
"docs": [
|
||||
"Maximal per-second relative difference of the stable price.",
|
||||
"It gets further reduced if stable and delay price disagree."
|
||||
],
|
||||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "lastDelayIntervalIndex",
|
||||
"docs": [
|
||||
"The delay_interval_index that update() was last called on."
|
||||
],
|
||||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
7
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
48
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "TokenIndex",
|
||||
"docs": [
|
||||
|
@ -13026,6 +13509,11 @@ export const IDL: MangoV4 = {
|
|||
"type": "i128",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "stablePrice",
|
||||
"type": "i128",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "feesAccrued",
|
||||
"type": "i128",
|
||||
|
@ -13071,6 +13559,11 @@ export const IDL: MangoV4 = {
|
|||
"type": "i128",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "stablePrice",
|
||||
"type": "i128",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "collectedFees",
|
||||
"type": "i128",
|
||||
|
@ -13766,6 +14259,21 @@ export const IDL: MangoV4 = {
|
|||
"code": 6024,
|
||||
"name": "OracleStale",
|
||||
"msg": "an oracle is stale"
|
||||
},
|
||||
{
|
||||
"code": 6025,
|
||||
"name": "SettlementAmountMustBePositive",
|
||||
"msg": "settlement amount must always be positive"
|
||||
},
|
||||
{
|
||||
"code": 6026,
|
||||
"name": "BankBorrowLimitReached",
|
||||
"msg": "bank utilization has reached limit"
|
||||
},
|
||||
{
|
||||
"code": 6027,
|
||||
"name": "BankNetBorrowsLimitReached",
|
||||
"msg": "bank net borrows has reached limit"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
@ -44,6 +44,10 @@ const DEVNET_ORACLES = new Map([
|
|||
['SRM', '992moaMQKs32GKZ9dxi8keyM2bUmbrwBZpK4p2K6X5Vs'],
|
||||
]);
|
||||
|
||||
const MIN_VAULT_TO_DEPOSITS_RATIO = 0.2;
|
||||
const NET_BORROWS_WINDOW_SIZE_TS = 24 * 60 * 60;
|
||||
const NET_BORROWS_LIMIT_NATIVE = 1 * Math.pow(10, 7) * Math.pow(10, 6);
|
||||
|
||||
const GROUP_NUM = Number(process.env.GROUP_NUM || 0);
|
||||
|
||||
async function main() {
|
||||
|
@ -122,6 +126,9 @@ async function main() {
|
|||
1,
|
||||
1,
|
||||
0,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
await group.reloadAll(client);
|
||||
} catch (error) {}
|
||||
|
@ -146,6 +153,9 @@ async function main() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
await group.reloadAll(client);
|
||||
} catch (error) {
|
||||
|
@ -172,6 +182,9 @@ async function main() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
await group.reloadAll(client);
|
||||
} catch (error) {
|
||||
|
@ -205,6 +218,9 @@ async function main() {
|
|||
1.2,
|
||||
1.4,
|
||||
0.02,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
await group.reloadAll(client);
|
||||
} catch (error) {
|
||||
|
@ -231,6 +247,9 @@ async function main() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
await group.reloadAll(client);
|
||||
} catch (error) {
|
||||
|
@ -257,6 +276,9 @@ async function main() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
await group.reloadAll(client);
|
||||
} catch (error) {
|
||||
|
@ -403,6 +425,9 @@ async function main() {
|
|||
1,
|
||||
1,
|
||||
0,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
|
||||
await group.reloadAll(client);
|
||||
|
@ -427,6 +452,9 @@ async function main() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
|
||||
await group.reloadAll(client);
|
||||
|
@ -451,6 +479,9 @@ async function main() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
|
||||
await group.reloadAll(client);
|
||||
|
@ -483,6 +514,9 @@ async function main() {
|
|||
1000,
|
||||
1000000,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
|
||||
await group.reloadAll(client);
|
||||
|
|
|
@ -50,6 +50,10 @@ const MAINNET_SERUM3_MARKETS = new Map([
|
|||
['DUST/SOL', '8WCzJpSNcLUYXPYeUDAXpH4hgqxFJpkYkVT6GJDSpcGx'],
|
||||
]);
|
||||
|
||||
const MIN_VAULT_TO_DEPOSITS_RATIO = 0.2;
|
||||
const NET_BORROWS_WINDOW_SIZE_TS = 24 * 60 * 60;
|
||||
const NET_BORROWS_LIMIT_NATIVE = 1 * Math.pow(10, 7) * Math.pow(10, 6);
|
||||
|
||||
const { MB_CLUSTER_URL, MB_PAYER_KEYPAIR, MB_USER_KEYPAIR, MB_USER2_KEYPAIR } =
|
||||
process.env;
|
||||
|
||||
|
@ -160,6 +164,9 @@ async function registerTokens() {
|
|||
1,
|
||||
1,
|
||||
0,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
|
||||
console.log(`Registering USDT...`);
|
||||
|
@ -180,6 +187,9 @@ async function registerTokens() {
|
|||
1.05,
|
||||
1.1,
|
||||
0.025,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
|
||||
console.log(`Registering BTC...`);
|
||||
|
@ -200,6 +210,9 @@ async function registerTokens() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
|
||||
console.log(`Registering ETH...`);
|
||||
|
@ -220,6 +233,9 @@ async function registerTokens() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
|
||||
console.log(`Registering soETH...`);
|
||||
|
@ -240,6 +256,9 @@ async function registerTokens() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
|
||||
console.log(`Registering SOL...`);
|
||||
|
@ -260,6 +279,9 @@ async function registerTokens() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
|
||||
console.log(`Registering MSOL...`);
|
||||
|
@ -280,6 +302,9 @@ async function registerTokens() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
console.log(`Registering RAY...`);
|
||||
const rayMainnetMint = new PublicKey(MAINNET_MINTS.get('RAY')!);
|
||||
|
@ -306,6 +331,9 @@ async function registerTokens() {
|
|||
8 / 7,
|
||||
4 / 3,
|
||||
1 / 16,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
|
||||
console.log(`Registering DUST...`);
|
||||
|
@ -333,6 +361,9 @@ async function registerTokens() {
|
|||
81 / 80,
|
||||
41 / 40, // 40x leverage so we can test something
|
||||
1 / 160, // no liquidation fee
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
|
||||
// log tokens/banks
|
||||
|
|
|
@ -33,6 +33,10 @@ const MAINNET_SERUM3_MARKETS = new Map([
|
|||
['SOL/USDC', '9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT'],
|
||||
]);
|
||||
|
||||
const MIN_VAULT_TO_DEPOSITS_RATIO = 0.2;
|
||||
const NET_BORROWS_WINDOW_SIZE_TS = 24 * 60 * 60;
|
||||
const NET_BORROWS_LIMIT_NATIVE = 1 * Math.pow(10, 7) * Math.pow(10, 6);
|
||||
|
||||
async function main() {
|
||||
const options = AnchorProvider.defaultOptions();
|
||||
const connection = new Connection(process.env.CLUSTER_URL!, options);
|
||||
|
@ -116,6 +120,9 @@ async function main() {
|
|||
1,
|
||||
1,
|
||||
0,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
await group.reloadAll(client);
|
||||
} catch (error) {
|
||||
|
@ -142,6 +149,9 @@ async function main() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
await group.reloadAll(client);
|
||||
} catch (error) {
|
||||
|
@ -168,6 +178,9 @@ async function main() {
|
|||
1.1,
|
||||
1.2,
|
||||
0.05,
|
||||
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||
NET_BORROWS_WINDOW_SIZE_TS,
|
||||
NET_BORROWS_LIMIT_NATIVE,
|
||||
);
|
||||
await group.reloadAll(client);
|
||||
} catch (error) {
|
||||
|
|
|
@ -167,6 +167,9 @@ async function main() {
|
|||
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.
|
||||
|
@ -199,6 +202,9 @@ async function main() {
|
|||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue