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:
microwavedcola1 2022-11-25 13:45:17 +01:00 committed by GitHub
parent 502f0767a8
commit 1732a5aff4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1440 additions and 143 deletions

View File

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

View File

@ -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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"
}
]
};

View File

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

View File

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

View File

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

View File

@ -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,
);
}
}