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,
|
OracleStale,
|
||||||
#[msg("settlement amount must always be positive")]
|
#[msg("settlement amount must always be positive")]
|
||||||
SettlementAmountMustBePositive,
|
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 {
|
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);
|
let loan_origination_fee = cm!(loan * bank.loan_origination_fee_rate);
|
||||||
cm!(bank.collected_fees_native += loan_origination_fee);
|
cm!(bank.collected_fees_native += loan_origination_fee);
|
||||||
|
|
||||||
let is_active =
|
let is_active = bank.change_without_fee(
|
||||||
bank.change_without_fee(position, cm!(change.amount - loan_origination_fee))?;
|
position,
|
||||||
|
cm!(change.amount - loan_origination_fee),
|
||||||
|
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||||
|
)?;
|
||||||
if !is_active {
|
if !is_active {
|
||||||
deactivated_token_positions.push(change.raw_token_index);
|
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
|
// credit the liqor with quote tokens
|
||||||
let (liqor_quote, _, _) = liqor.ensure_token_position(settle_token_index)?;
|
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 {
|
emit!(TokenBalanceLog {
|
||||||
mango_group: ctx.accounts.group.key(),
|
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
|
let token_position = account
|
||||||
.token_position_mut(perp_market.settle_token_index)?
|
.token_position_mut(perp_market.settle_token_index)?
|
||||||
.0;
|
.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
|
// Update the settled balance on the market itself
|
||||||
perp_market.fees_settled = cm!(perp_market.fees_settled + settlement);
|
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_a.fixed.perp_spot_transfers += settlement_i64 - fee_i64);
|
||||||
cm!(account_b.fixed.perp_spot_transfers -= settlement_i64);
|
cm!(account_b.fixed.perp_spot_transfers -= settlement_i64);
|
||||||
|
|
||||||
|
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
|
||||||
|
|
||||||
// Transfer token balances
|
// Transfer token balances
|
||||||
// The fee is paid by the account with positive unsettled pnl
|
// 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 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;
|
let b_token_position = account_b.token_position_mut(settle_token_index)?.0;
|
||||||
bank.deposit(a_token_position, cm!(settlement - fee))?;
|
bank.deposit(a_token_position, cm!(settlement - fee), now_ts)?;
|
||||||
bank.withdraw_with_fee(b_token_position, settlement)?;
|
bank.withdraw_with_fee(b_token_position, settlement, now_ts)?;
|
||||||
|
|
||||||
emit!(TokenBalanceLog {
|
emit!(TokenBalanceLog {
|
||||||
mango_group: ctx.accounts.group.key(),
|
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, _) =
|
let (settler_token_position, settler_token_raw_index, _) =
|
||||||
settler.ensure_token_position(settle_token_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 {
|
emit!(TokenBalanceLog {
|
||||||
mango_group: ctx.accounts.group.key(),
|
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 (position, _) = account.token_position_mut(bank.token_index)?;
|
||||||
let native_before = position.native(bank);
|
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_after = position.native(bank);
|
||||||
let native_change = cm!(native_after - native_before);
|
let native_change = cm!(native_after - native_before);
|
||||||
let new_borrows = native_change
|
let new_borrows = native_change
|
||||||
|
|
|
@ -206,6 +206,8 @@ pub fn charge_loan_origination_fees(
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let serum3_account = account.serum3_orders_mut(market_index).unwrap();
|
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 oo_base_total = before_oo.native_base_total();
|
||||||
let actualized_base_loan = I80F48::from_num(
|
let actualized_base_loan = I80F48::from_num(
|
||||||
serum3_account
|
serum3_account
|
||||||
|
@ -218,8 +220,11 @@ pub fn charge_loan_origination_fees(
|
||||||
// now that the loan is actually materialized, charge the loan origination fee
|
// now that the loan is actually materialized, charge the loan origination fee
|
||||||
// note: the withdraw has already happened while placing the order
|
// note: the withdraw has already happened while placing the order
|
||||||
let base_token_account = account.token_position_mut(base_bank.token_index)?.0;
|
let base_token_account = account.token_position_mut(base_bank.token_index)?.0;
|
||||||
let (_, fee) =
|
let (_, fee) = base_bank.withdraw_loan_origination_fee(
|
||||||
base_bank.withdraw_loan_origination_fee(base_token_account, actualized_base_loan)?;
|
base_token_account,
|
||||||
|
actualized_base_loan,
|
||||||
|
now_ts,
|
||||||
|
)?;
|
||||||
|
|
||||||
emit!(WithdrawLoanOriginationFeeLog {
|
emit!(WithdrawLoanOriginationFeeLog {
|
||||||
mango_group: *group_pubkey,
|
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
|
// now that the loan is actually materialized, charge the loan origination fee
|
||||||
// note: the withdraw has already happened while placing the order
|
// note: the withdraw has already happened while placing the order
|
||||||
let quote_token_account = account.token_position_mut(quote_bank.token_index)?.0;
|
let quote_token_account = account.token_position_mut(quote_bank.token_index)?.0;
|
||||||
let (_, fee) =
|
let (_, fee) = quote_bank.withdraw_loan_origination_fee(
|
||||||
quote_bank.withdraw_loan_origination_fee(quote_token_account, actualized_quote_loan)?;
|
quote_token_account,
|
||||||
|
actualized_quote_loan,
|
||||||
|
now_ts,
|
||||||
|
)?;
|
||||||
|
|
||||||
emit!(WithdrawLoanOriginationFeeLog {
|
emit!(WithdrawLoanOriginationFeeLog {
|
||||||
mango_group: *group_pubkey,
|
mango_group: *group_pubkey,
|
||||||
|
|
|
@ -69,7 +69,13 @@ pub fn token_add_bank(
|
||||||
let existing_bank = ctx.accounts.existing_bank.load()?;
|
let existing_bank = ctx.accounts.existing_bank.load()?;
|
||||||
let mut bank = ctx.accounts.bank.load_init()?;
|
let mut bank = ctx.accounts.bank.load_init()?;
|
||||||
let bump = *ctx.bumps.get("bank").ok_or(MangoError::SomeError)?;
|
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 mut mint_info = ctx.accounts.mint_info.load_mut()?;
|
||||||
let free_slot = mint_info
|
let free_slot = mint_info
|
||||||
|
|
|
@ -113,7 +113,11 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
|
||||||
let amount_i80f48 = I80F48::from(amount);
|
let amount_i80f48 = I80F48::from(amount);
|
||||||
let position_is_active = {
|
let position_is_active = {
|
||||||
let mut bank = self.bank.load_mut()?;
|
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
|
// Transfer the actual tokens
|
||||||
|
|
|
@ -49,6 +49,9 @@ pub fn token_edit(
|
||||||
stable_price_delay_interval_seconds_opt: Option<u32>,
|
stable_price_delay_interval_seconds_opt: Option<u32>,
|
||||||
stable_price_delay_growth_limit_opt: Option<f32>,
|
stable_price_delay_growth_limit_opt: Option<f32>,
|
||||||
stable_price_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<()> {
|
) -> Result<()> {
|
||||||
let mut mint_info = ctx.accounts.mint_info.load_mut()?;
|
let mut mint_info = ctx.accounts.mint_info.load_mut()?;
|
||||||
mint_info.verify_banks_ais(ctx.remaining_accounts)?;
|
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;
|
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 -
|
// unchanged -
|
||||||
// dust
|
// dust
|
||||||
// flash_loan_token_account_initial
|
// 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.
|
// liquidators to exploit the insurance fund for 1 native token each call.
|
||||||
let liab_transfer = cm!(insurance_transfer_i80f48 / liab_price_adjusted);
|
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;
|
let mut liqee_liab_active = true;
|
||||||
if insurance_transfer > 0 {
|
if insurance_transfer > 0 {
|
||||||
// liqee gets liab assets (enable dusting to prevent a case where the position is brought
|
// liqee gets liab assets (enable dusting to prevent a case where the position is brought
|
||||||
// to +I80F48::DELTA)
|
// 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);
|
remaining_liab_loss = -liqee_liab.native(liab_bank);
|
||||||
|
|
||||||
// move insurance assets into quote bank
|
// move insurance assets into quote bank
|
||||||
|
@ -169,7 +171,8 @@ pub fn token_liq_bankruptcy(
|
||||||
// credit the liqor
|
// credit the liqor
|
||||||
let (liqor_quote, liqor_quote_raw_token_index, _) =
|
let (liqor_quote, liqor_quote_raw_token_index, _) =
|
||||||
liqor.ensure_token_position(QUOTE_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
|
// liqor quote
|
||||||
emit!(TokenBalanceLog {
|
emit!(TokenBalanceLog {
|
||||||
|
@ -185,7 +188,7 @@ pub fn token_liq_bankruptcy(
|
||||||
let (liqor_liab, liqor_liab_raw_token_index, _) =
|
let (liqor_liab, liqor_liab_raw_token_index, _) =
|
||||||
liqor.ensure_token_position(liab_token_index)?;
|
liqor.ensure_token_position(liab_token_index)?;
|
||||||
let (liqor_liab_active, loan_origination_fee) =
|
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
|
// liqor liab
|
||||||
emit!(TokenBalanceLog {
|
emit!(TokenBalanceLog {
|
||||||
|
@ -268,7 +271,8 @@ pub fn token_liq_bankruptcy(
|
||||||
if amount_for_bank.is_positive() {
|
if amount_for_bank.is_positive() {
|
||||||
// enable dusting, because each deposit() is allowed to round up. thus multiple deposit
|
// enable dusting, because each deposit() is allowed to round up. thus multiple deposit
|
||||||
// could bring the total position slightly above zero otherwise
|
// 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);
|
cm!(amount_to_credit -= amount_for_bank);
|
||||||
if amount_to_credit <= 0 {
|
if amount_to_credit <= 0 {
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -142,24 +142,38 @@ pub fn token_liq_with_token(
|
||||||
|
|
||||||
// Apply the balance changes to the liqor and liqee accounts
|
// 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_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 liqee_liab_indexed_position = liqee_liab_position.indexed_position;
|
||||||
|
|
||||||
let (liqor_liab_position, liqor_liab_raw_index, _) =
|
let (liqor_liab_position, liqor_liab_raw_index, _) =
|
||||||
liqor.ensure_token_position(liab_token_index)?;
|
liqor.ensure_token_position(liab_token_index)?;
|
||||||
let (liqor_liab_active, loan_origination_fee) =
|
let (liqor_liab_active, loan_origination_fee) = liab_bank.withdraw_with_fee(
|
||||||
liab_bank.withdraw_with_fee(liqor_liab_position, liab_transfer)?;
|
liqor_liab_position,
|
||||||
|
liab_transfer,
|
||||||
|
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||||
|
)?;
|
||||||
let liqor_liab_indexed_position = liqor_liab_position.indexed_position;
|
let liqor_liab_indexed_position = liqor_liab_position.indexed_position;
|
||||||
let liqee_liab_native_after = liqee_liab_position.native(liab_bank);
|
let liqee_liab_native_after = liqee_liab_position.native(liab_bank);
|
||||||
|
|
||||||
let (liqor_asset_position, liqor_asset_raw_index, _) =
|
let (liqor_asset_position, liqor_asset_raw_index, _) =
|
||||||
liqor.ensure_token_position(asset_token_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 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_position = liqee.token_position_mut_by_raw_index(liqee_asset_raw_index);
|
||||||
let liqee_asset_active =
|
let liqee_asset_active = asset_bank.withdraw_without_fee_with_dusting(
|
||||||
asset_bank.withdraw_without_fee_with_dusting(liqee_asset_position, asset_transfer)?;
|
liqee_asset_position,
|
||||||
|
asset_transfer,
|
||||||
|
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||||
|
)?;
|
||||||
let liqee_asset_indexed_position = liqee_asset_position.indexed_position;
|
let liqee_asset_indexed_position = liqee_asset_position.indexed_position;
|
||||||
let liqee_assets_native_after = liqee_asset_position.native(asset_bank);
|
let liqee_assets_native_after = liqee_asset_position.native(asset_bank);
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,9 @@ pub fn token_register(
|
||||||
maint_liab_weight: f32,
|
maint_liab_weight: f32,
|
||||||
init_liab_weight: f32,
|
init_liab_weight: f32,
|
||||||
liquidation_fee: f32,
|
liquidation_fee: f32,
|
||||||
|
min_vault_to_deposits_ratio: f64,
|
||||||
|
net_borrows_window_size_ts: u64,
|
||||||
|
net_borrows_limit_native: i64,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Require token 0 to be in the insurance token
|
// Require token 0 to be in the insurance token
|
||||||
if token_index == QUOTE_TOKEN_INDEX {
|
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()?;
|
let mut bank = ctx.accounts.bank.load_init()?;
|
||||||
*bank = Bank {
|
*bank = Bank {
|
||||||
|
@ -142,7 +145,13 @@ pub fn token_register(
|
||||||
oracle_conf_filter: oracle_config.to_oracle_config().conf_filter,
|
oracle_conf_filter: oracle_config.to_oracle_config().conf_filter,
|
||||||
oracle_config: oracle_config.to_oracle_config(),
|
oracle_config: oracle_config.to_oracle_config(),
|
||||||
stable_price_model: StablePriceModel::default(),
|
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);
|
require_gt!(bank.max_rate, MINIMUM_MAX_RATE);
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,8 @@ pub fn token_register_trustless(
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
require_neq!(token_index, 0);
|
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()?;
|
let mut bank = ctx.accounts.bank.load_init()?;
|
||||||
*bank = Bank {
|
*bank = Bank {
|
||||||
|
@ -119,7 +120,13 @@ pub fn token_register_trustless(
|
||||||
reserved: [0; 72],
|
reserved: [0; 72],
|
||||||
},
|
},
|
||||||
stable_price_model: StablePriceModel::default(),
|
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);
|
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)?;
|
.verify_banks_ais(ctx.remaining_accounts)?;
|
||||||
|
|
||||||
let clock = Clock::get()?;
|
let clock = Clock::get()?;
|
||||||
let now_ts = clock.unix_timestamp;
|
let now_ts: u64 = clock.unix_timestamp.try_into().unwrap();
|
||||||
|
|
||||||
// compute indexed_total
|
// compute indexed_total
|
||||||
let mut indexed_total_deposits = I80F48::ZERO;
|
let mut indexed_total_deposits = I80F48::ZERO;
|
||||||
|
|
|
@ -93,16 +93,17 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
|
||||||
amount
|
amount
|
||||||
};
|
};
|
||||||
|
|
||||||
require!(
|
let is_borrow = amount > native_position;
|
||||||
allow_borrow || amount <= native_position,
|
require!(allow_borrow || !is_borrow, MangoError::SomeError);
|
||||||
MangoError::SomeError
|
|
||||||
);
|
|
||||||
|
|
||||||
let amount_i80f48 = I80F48::from(amount);
|
let amount_i80f48 = I80F48::from(amount);
|
||||||
|
|
||||||
// Update the bank and position
|
// Update the bank and position
|
||||||
let (position_is_active, loan_origination_fee) =
|
let (position_is_active, loan_origination_fee) = bank.withdraw_with_fee(
|
||||||
bank.withdraw_with_fee(position, amount_i80f48)?;
|
position,
|
||||||
|
amount_i80f48,
|
||||||
|
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||||
|
)?;
|
||||||
|
|
||||||
// Provide a readable error message in case the vault doesn't have enough tokens
|
// Provide a readable error message in case the vault doesn't have enough tokens
|
||||||
if ctx.accounts.vault.amount < amount {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,9 @@ pub mod mango_v4 {
|
||||||
maint_liab_weight: f32,
|
maint_liab_weight: f32,
|
||||||
init_liab_weight: f32,
|
init_liab_weight: f32,
|
||||||
liquidation_fee: f32,
|
liquidation_fee: f32,
|
||||||
|
min_vault_to_deposits_ratio: f64,
|
||||||
|
net_borrows_window_size_ts: u64,
|
||||||
|
net_borrows_limit_native: i64,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
instructions::token_register(
|
instructions::token_register(
|
||||||
ctx,
|
ctx,
|
||||||
|
@ -88,6 +91,9 @@ pub mod mango_v4 {
|
||||||
maint_liab_weight,
|
maint_liab_weight,
|
||||||
init_liab_weight,
|
init_liab_weight,
|
||||||
liquidation_fee,
|
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_interval_seconds_opt: Option<u32>,
|
||||||
stable_price_delay_growth_limit_opt: Option<f32>,
|
stable_price_delay_growth_limit_opt: Option<f32>,
|
||||||
stable_price_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<()> {
|
) -> Result<()> {
|
||||||
instructions::token_edit(
|
instructions::token_edit(
|
||||||
ctx,
|
ctx,
|
||||||
|
@ -133,6 +142,9 @@ pub mod mango_v4 {
|
||||||
stable_price_delay_interval_seconds_opt,
|
stable_price_delay_interval_seconds_opt,
|
||||||
stable_price_delay_growth_limit_opt,
|
stable_price_delay_growth_limit_opt,
|
||||||
stable_price_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 super::{OracleConfig, TokenIndex, TokenPosition};
|
||||||
use crate::accounts_zerocopy::KeyedAccountReader;
|
use crate::accounts_zerocopy::KeyedAccountReader;
|
||||||
|
use crate::error::{Contextable, MangoError};
|
||||||
use crate::state::{oracle, StablePriceModel};
|
use crate::state::{oracle, StablePriceModel};
|
||||||
use crate::util;
|
use crate::util;
|
||||||
use crate::util::checked_math as cm;
|
use crate::util::checked_math as cm;
|
||||||
|
@ -57,8 +58,8 @@ pub struct Bank {
|
||||||
pub indexed_deposits: I80F48,
|
pub indexed_deposits: I80F48,
|
||||||
pub indexed_borrows: I80F48,
|
pub indexed_borrows: I80F48,
|
||||||
|
|
||||||
pub index_last_updated: i64,
|
pub index_last_updated: u64,
|
||||||
pub bank_rate_last_updated: i64,
|
pub bank_rate_last_updated: u64,
|
||||||
|
|
||||||
pub avg_utilization: I80F48,
|
pub avg_utilization: I80F48,
|
||||||
|
|
||||||
|
@ -106,8 +107,14 @@ pub struct Bank {
|
||||||
|
|
||||||
pub stable_price_model: StablePriceModel,
|
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")]
|
#[derivative(Debug = "ignore")]
|
||||||
pub reserved: [u8; 2176],
|
pub reserved: [u8; 2136],
|
||||||
}
|
}
|
||||||
const_assert_eq!(size_of::<Bank>(), 3112);
|
const_assert_eq!(size_of::<Bank>(), 3112);
|
||||||
const_assert_eq!(size_of::<Bank>() % 8, 0);
|
const_assert_eq!(size_of::<Bank>() % 8, 0);
|
||||||
|
@ -118,6 +125,7 @@ impl Bank {
|
||||||
vault: Pubkey,
|
vault: Pubkey,
|
||||||
bank_num: u32,
|
bank_num: u32,
|
||||||
bump: u8,
|
bump: u8,
|
||||||
|
now_ts: u64,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
// values that must be reset/changed
|
// values that must be reset/changed
|
||||||
|
@ -163,7 +171,12 @@ impl Bank {
|
||||||
mint_decimals: existing_bank.mint_decimals,
|
mint_decimals: existing_bank.mint_decimals,
|
||||||
oracle_config: existing_bank.oracle_config.clone(),
|
oracle_config: existing_bank.oracle_config.clone(),
|
||||||
stable_price_model: StablePriceModel::default(),
|
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
|
/// native_amount must be >= 0
|
||||||
/// fractional deposits can be relevant during liquidation, for example
|
/// fractional deposits can be relevant during liquidation, for example
|
||||||
pub fn deposit(&mut self, position: &mut TokenPosition, native_amount: I80F48) -> Result<bool> {
|
pub fn deposit(
|
||||||
self.deposit_internal(position, native_amount, !position.is_in_use())
|
&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.
|
/// Like `deposit()`, but allows dusting of in-use accounts.
|
||||||
|
@ -200,11 +218,26 @@ impl Bank {
|
||||||
&mut self,
|
&mut self,
|
||||||
position: &mut TokenPosition,
|
position: &mut TokenPosition,
|
||||||
native_amount: I80F48,
|
native_amount: I80F48,
|
||||||
|
now_ts: u64,
|
||||||
) -> Result<bool> {
|
) -> 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())
|
.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
|
/// Internal function to deposit funds
|
||||||
pub fn deposit_internal(
|
pub fn deposit_internal(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -213,6 +246,7 @@ impl Bank {
|
||||||
allow_dusting: bool,
|
allow_dusting: bool,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
require_gte!(native_amount, 0);
|
require_gte!(native_amount, 0);
|
||||||
|
|
||||||
let native_position = position.native(self);
|
let native_position = position.native(self);
|
||||||
let opening_indexed_position = position.indexed_position;
|
let opening_indexed_position = position.indexed_position;
|
||||||
|
|
||||||
|
@ -240,14 +274,12 @@ impl Bank {
|
||||||
// pay back borrows only, leaving a negative position
|
// pay back borrows only, leaving a negative position
|
||||||
cm!(self.indexed_borrows -= indexed_change);
|
cm!(self.indexed_borrows -= indexed_change);
|
||||||
position.indexed_position = new_indexed_value;
|
position.indexed_position = new_indexed_value;
|
||||||
self.update_cumulative_interest(position, opening_indexed_position);
|
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
} else if new_native_position < I80F48::ONE && allow_dusting {
|
} else if new_native_position < I80F48::ONE && allow_dusting {
|
||||||
// if there's less than one token deposited, zero the position
|
// if there's less than one token deposited, zero the position
|
||||||
cm!(self.dust += new_native_position);
|
cm!(self.dust += new_native_position);
|
||||||
cm!(self.indexed_borrows += position.indexed_position);
|
cm!(self.indexed_borrows += position.indexed_position);
|
||||||
position.indexed_position = I80F48::ZERO;
|
position.indexed_position = I80F48::ZERO;
|
||||||
self.update_cumulative_interest(position, opening_indexed_position);
|
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,7 +295,6 @@ impl Bank {
|
||||||
let indexed_change = div_rounding_up(native_amount, self.deposit_index);
|
let indexed_change = div_rounding_up(native_amount, self.deposit_index);
|
||||||
cm!(self.indexed_deposits += indexed_change);
|
cm!(self.indexed_deposits += indexed_change);
|
||||||
cm!(position.indexed_position += indexed_change);
|
cm!(position.indexed_position += indexed_change);
|
||||||
self.update_cumulative_interest(position, opening_indexed_position);
|
|
||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
@ -280,9 +311,15 @@ impl Bank {
|
||||||
&mut self,
|
&mut self,
|
||||||
position: &mut TokenPosition,
|
position: &mut TokenPosition,
|
||||||
native_amount: I80F48,
|
native_amount: I80F48,
|
||||||
|
now_ts: u64,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
let (position_is_active, _) =
|
let (position_is_active, _) = self.withdraw_internal_wrapper(
|
||||||
self.withdraw_internal(position, native_amount, false, !position.is_in_use())?;
|
position,
|
||||||
|
native_amount,
|
||||||
|
false,
|
||||||
|
!position.is_in_use(),
|
||||||
|
now_ts,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(position_is_active)
|
Ok(position_is_active)
|
||||||
}
|
}
|
||||||
|
@ -294,8 +331,9 @@ impl Bank {
|
||||||
&mut self,
|
&mut self,
|
||||||
position: &mut TokenPosition,
|
position: &mut TokenPosition,
|
||||||
native_amount: I80F48,
|
native_amount: I80F48,
|
||||||
|
now_ts: u64,
|
||||||
) -> Result<bool> {
|
) -> 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())
|
.map(|(not_dusted, _)| not_dusted || position.is_in_use())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,8 +349,30 @@ impl Bank {
|
||||||
&mut self,
|
&mut self,
|
||||||
position: &mut TokenPosition,
|
position: &mut TokenPosition,
|
||||||
native_amount: I80F48,
|
native_amount: I80F48,
|
||||||
|
now_ts: u64,
|
||||||
) -> Result<(bool, I80F48)> {
|
) -> 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
|
/// Internal function to withdraw funds
|
||||||
|
@ -322,6 +382,7 @@ impl Bank {
|
||||||
mut native_amount: I80F48,
|
mut native_amount: I80F48,
|
||||||
with_loan_origination_fee: bool,
|
with_loan_origination_fee: bool,
|
||||||
allow_dusting: bool,
|
allow_dusting: bool,
|
||||||
|
now_ts: u64,
|
||||||
) -> Result<(bool, I80F48)> {
|
) -> Result<(bool, I80F48)> {
|
||||||
require_gte!(native_amount, 0);
|
require_gte!(native_amount, 0);
|
||||||
let native_position = position.native(self);
|
let native_position = position.native(self);
|
||||||
|
@ -336,14 +397,12 @@ impl Bank {
|
||||||
cm!(self.dust += new_native_position);
|
cm!(self.dust += new_native_position);
|
||||||
cm!(self.indexed_deposits -= position.indexed_position);
|
cm!(self.indexed_deposits -= position.indexed_position);
|
||||||
position.indexed_position = I80F48::ZERO;
|
position.indexed_position = I80F48::ZERO;
|
||||||
self.update_cumulative_interest(position, opening_indexed_position);
|
|
||||||
return Ok((false, I80F48::ZERO));
|
return Ok((false, I80F48::ZERO));
|
||||||
} else {
|
} else {
|
||||||
// withdraw some deposits leaving a positive balance
|
// withdraw some deposits leaving a positive balance
|
||||||
let indexed_change = cm!(native_amount / self.deposit_index);
|
let indexed_change = cm!(native_amount / self.deposit_index);
|
||||||
cm!(self.indexed_deposits -= indexed_change);
|
cm!(self.indexed_deposits -= indexed_change);
|
||||||
cm!(position.indexed_position -= indexed_change);
|
cm!(position.indexed_position -= indexed_change);
|
||||||
self.update_cumulative_interest(position, opening_indexed_position);
|
|
||||||
return Ok((true, I80F48::ZERO));
|
return Ok((true, I80F48::ZERO));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -366,7 +425,10 @@ impl Bank {
|
||||||
let indexed_change = cm!(native_amount / self.borrow_index);
|
let indexed_change = cm!(native_amount / self.borrow_index);
|
||||||
cm!(self.indexed_borrows += indexed_change);
|
cm!(self.indexed_borrows += indexed_change);
|
||||||
cm!(position.indexed_position -= 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))
|
Ok((true, loan_origination_fee))
|
||||||
}
|
}
|
||||||
|
@ -376,13 +438,19 @@ impl Bank {
|
||||||
&mut self,
|
&mut self,
|
||||||
position: &mut TokenPosition,
|
position: &mut TokenPosition,
|
||||||
already_borrowed_native_amount: I80F48,
|
already_borrowed_native_amount: I80F48,
|
||||||
|
now_ts: u64,
|
||||||
) -> Result<(bool, I80F48)> {
|
) -> Result<(bool, I80F48)> {
|
||||||
let loan_origination_fee =
|
let loan_origination_fee =
|
||||||
cm!(self.loan_origination_fee_rate * already_borrowed_native_amount);
|
cm!(self.loan_origination_fee_rate * already_borrowed_native_amount);
|
||||||
cm!(self.collected_fees_native += loan_origination_fee);
|
cm!(self.collected_fees_native += loan_origination_fee);
|
||||||
|
|
||||||
let (position_is_active, _) =
|
let (position_is_active, _) = self.withdraw_internal_wrapper(
|
||||||
self.withdraw_internal(position, loan_origination_fee, false, !position.is_in_use())?;
|
position,
|
||||||
|
loan_origination_fee,
|
||||||
|
false,
|
||||||
|
!position.is_in_use(),
|
||||||
|
now_ts,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok((position_is_active, loan_origination_fee))
|
Ok((position_is_active, loan_origination_fee))
|
||||||
}
|
}
|
||||||
|
@ -392,11 +460,12 @@ impl Bank {
|
||||||
&mut self,
|
&mut self,
|
||||||
position: &mut TokenPosition,
|
position: &mut TokenPosition,
|
||||||
native_amount: I80F48,
|
native_amount: I80F48,
|
||||||
|
now_ts: u64,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
if native_amount >= 0 {
|
if native_amount >= 0 {
|
||||||
self.deposit(position, native_amount)
|
self.deposit(position, native_amount, now_ts)
|
||||||
} else {
|
} 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,
|
&mut self,
|
||||||
position: &mut TokenPosition,
|
position: &mut TokenPosition,
|
||||||
native_amount: I80F48,
|
native_amount: I80F48,
|
||||||
|
now_ts: u64,
|
||||||
) -> Result<(bool, I80F48)> {
|
) -> Result<(bool, I80F48)> {
|
||||||
if native_amount >= 0 {
|
if native_amount >= 0 {
|
||||||
Ok((self.deposit(position, native_amount)?, I80F48::ZERO))
|
Ok((self.deposit(position, native_amount, now_ts)?, I80F48::ZERO))
|
||||||
} else {
|
} 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(
|
pub fn update_cumulative_interest(
|
||||||
&self,
|
&self,
|
||||||
position: &mut TokenPosition,
|
position: &mut TokenPosition,
|
||||||
|
@ -524,9 +619,9 @@ impl Bank {
|
||||||
&self,
|
&self,
|
||||||
indexed_total_deposits: I80F48,
|
indexed_total_deposits: I80F48,
|
||||||
indexed_total_borrows: I80F48,
|
indexed_total_borrows: I80F48,
|
||||||
now_ts: i64,
|
now_ts: u64,
|
||||||
) -> I80F48 {
|
) -> I80F48 {
|
||||||
if now_ts <= 0 {
|
if now_ts == 0 {
|
||||||
return I80F48::ZERO;
|
return I80F48::ZERO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -664,6 +759,8 @@ mod tests {
|
||||||
//
|
//
|
||||||
|
|
||||||
let mut bank = Bank::zeroed();
|
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.deposit_index = I80F48::from_num(100.0);
|
||||||
bank.borrow_index = I80F48::from_num(10.0);
|
bank.borrow_index = I80F48::from_num(10.0);
|
||||||
bank.loan_origination_fee_rate = I80F48::from_num(0.1);
|
bank.loan_origination_fee_rate = I80F48::from_num(0.1);
|
||||||
|
@ -701,7 +798,8 @@ mod tests {
|
||||||
//
|
//
|
||||||
|
|
||||||
let change = I80F48::from(change);
|
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;
|
let mut expected_native = start_native + change;
|
||||||
if expected_native >= 0.0 && expected_native < 1.0 && !is_in_use {
|
if expected_native >= 0.0 && expected_native < 1.0 && !is_in_use {
|
||||||
|
@ -742,7 +840,7 @@ mod tests {
|
||||||
bank.index_last_updated = 1000;
|
bank.index_last_updated = 1000;
|
||||||
|
|
||||||
let compute_new_avg_utilization_runner =
|
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.avg_utilization =
|
||||||
bank.compute_new_avg_utilization(I80F48::ONE, utilization, now_ts);
|
bank.compute_new_avg_utilization(I80F48::ONE, utilization, now_ts);
|
||||||
bank.index_last_updated = now_ts;
|
bank.index_last_updated = now_ts;
|
||||||
|
|
|
@ -1422,6 +1422,8 @@ mod tests {
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
pub const DUMMY_NOW_TS: u64 = 0;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_precision() {
|
fn test_precision() {
|
||||||
// I80F48 can only represent until 1/2^48
|
// 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_asset_weight = I80F48::from_num(1.0 - maint_weights);
|
||||||
bank.data().maint_liab_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().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)
|
(bank, oracle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1578,6 +1582,7 @@ mod tests {
|
||||||
.deposit(
|
.deposit(
|
||||||
account.ensure_token_position(1).unwrap().0,
|
account.ensure_token_position(1).unwrap().0,
|
||||||
I80F48::from(100),
|
I80F48::from(100),
|
||||||
|
DUMMY_NOW_TS,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
bank2
|
bank2
|
||||||
|
@ -1585,6 +1590,7 @@ mod tests {
|
||||||
.withdraw_without_fee(
|
.withdraw_without_fee(
|
||||||
account.ensure_token_position(4).unwrap().0,
|
account.ensure_token_position(4).unwrap().0,
|
||||||
I80F48::from(10),
|
I80F48::from(10),
|
||||||
|
DUMMY_NOW_TS,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -1760,6 +1766,7 @@ mod tests {
|
||||||
.change_without_fee(
|
.change_without_fee(
|
||||||
account.ensure_token_position(1).unwrap().0,
|
account.ensure_token_position(1).unwrap().0,
|
||||||
I80F48::from(testcase.token1),
|
I80F48::from(testcase.token1),
|
||||||
|
DUMMY_NOW_TS,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
bank2
|
bank2
|
||||||
|
@ -1767,6 +1774,7 @@ mod tests {
|
||||||
.change_without_fee(
|
.change_without_fee(
|
||||||
account.ensure_token_position(4).unwrap().0,
|
account.ensure_token_position(4).unwrap().0,
|
||||||
I80F48::from(testcase.token2),
|
I80F48::from(testcase.token2),
|
||||||
|
DUMMY_NOW_TS,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
bank3
|
bank3
|
||||||
|
@ -1774,6 +1782,7 @@ mod tests {
|
||||||
.change_without_fee(
|
.change_without_fee(
|
||||||
account.ensure_token_position(5).unwrap().0,
|
account.ensure_token_position(5).unwrap().0,
|
||||||
I80F48::from(testcase.token3),
|
I80F48::from(testcase.token3),
|
||||||
|
DUMMY_NOW_TS,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -2285,6 +2294,7 @@ mod tests {
|
||||||
.change_without_fee(
|
.change_without_fee(
|
||||||
account.ensure_token_position(1).unwrap().0,
|
account.ensure_token_position(1).unwrap().0,
|
||||||
I80F48::from(100),
|
I80F48::from(100),
|
||||||
|
0,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -2366,6 +2376,7 @@ mod tests {
|
||||||
.change_without_fee(
|
.change_without_fee(
|
||||||
account.ensure_token_position(1).unwrap().0,
|
account.ensure_token_position(1).unwrap().0,
|
||||||
I80F48::from(100),
|
I80F48::from(100),
|
||||||
|
DUMMY_NOW_TS,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
bank1
|
bank1
|
||||||
|
@ -2373,6 +2384,7 @@ mod tests {
|
||||||
.change_without_fee(
|
.change_without_fee(
|
||||||
account2.ensure_token_position(1).unwrap().0,
|
account2.ensure_token_position(1).unwrap().0,
|
||||||
I80F48::from(-100),
|
I80F48::from(-100),
|
||||||
|
DUMMY_NOW_TS,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -772,6 +772,10 @@ pub struct TokenRegisterInstruction {
|
||||||
pub init_liab_weight: f32,
|
pub init_liab_weight: f32,
|
||||||
pub liquidation_fee: 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 group: Pubkey,
|
||||||
pub admin: TestKeypair,
|
pub admin: TestKeypair,
|
||||||
pub mint: Pubkey,
|
pub mint: Pubkey,
|
||||||
|
@ -812,6 +816,9 @@ impl ClientInstruction for TokenRegisterInstruction {
|
||||||
maint_liab_weight: self.maint_liab_weight,
|
maint_liab_weight: self.maint_liab_weight,
|
||||||
init_liab_weight: self.init_liab_weight,
|
init_liab_weight: self.init_liab_weight,
|
||||||
liquidation_fee: self.liquidation_fee,
|
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(
|
let bank = Pubkey::find_program_address(
|
||||||
|
@ -1069,6 +1076,82 @@ impl ClientInstruction for TokenResetStablePriceModel {
|
||||||
stable_price_delay_interval_seconds_opt: None,
|
stable_price_delay_interval_seconds_opt: None,
|
||||||
stable_price_delay_growth_limit_opt: None,
|
stable_price_delay_growth_limit_opt: None,
|
||||||
stable_price_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 {
|
let accounts = Self::Accounts {
|
||||||
|
|
|
@ -105,6 +105,9 @@ impl<'a> GroupWithTokensConfig {
|
||||||
admin,
|
admin,
|
||||||
mint: mint.pubkey,
|
mint: mint.pubkey,
|
||||||
payer,
|
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
|
.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,
|
maintLiabWeight: number,
|
||||||
initLiabWeight: number,
|
initLiabWeight: number,
|
||||||
liquidationFee: number,
|
liquidationFee: number,
|
||||||
|
minVaultToDepositsRatio: number,
|
||||||
|
netBorrowsWindowSizeTs: number,
|
||||||
|
netBorrowsLimitNative: number,
|
||||||
): Promise<TransactionSignature> {
|
): Promise<TransactionSignature> {
|
||||||
return await this.program.methods
|
return await this.program.methods
|
||||||
.tokenRegister(
|
.tokenRegister(
|
||||||
|
@ -243,6 +246,9 @@ export class MangoClient {
|
||||||
maintLiabWeight,
|
maintLiabWeight,
|
||||||
initLiabWeight,
|
initLiabWeight,
|
||||||
liquidationFee,
|
liquidationFee,
|
||||||
|
minVaultToDepositsRatio,
|
||||||
|
new BN(netBorrowsWindowSizeTs),
|
||||||
|
new BN(netBorrowsLimitNative),
|
||||||
)
|
)
|
||||||
.accounts({
|
.accounts({
|
||||||
group: group.publicKey,
|
group: group.publicKey,
|
||||||
|
@ -290,6 +296,9 @@ export class MangoClient {
|
||||||
maintLiabWeight: number | null,
|
maintLiabWeight: number | null,
|
||||||
initLiabWeight: number | null,
|
initLiabWeight: number | null,
|
||||||
liquidationFee: number | null,
|
liquidationFee: number | null,
|
||||||
|
minVaultToDepositsRatio: number | null,
|
||||||
|
netBorrowsLimitNative: number | null,
|
||||||
|
netBorrowsWindowSizeTs: number | null,
|
||||||
): Promise<TransactionSignature> {
|
): Promise<TransactionSignature> {
|
||||||
const bank = group.getFirstBankByMint(mintPk);
|
const bank = group.getFirstBankByMint(mintPk);
|
||||||
const mintInfo = group.mintInfosMapByTokenIndex.get(bank.tokenIndex)!;
|
const mintInfo = group.mintInfosMapByTokenIndex.get(bank.tokenIndex)!;
|
||||||
|
@ -307,6 +316,9 @@ export class MangoClient {
|
||||||
maintLiabWeight,
|
maintLiabWeight,
|
||||||
initLiabWeight,
|
initLiabWeight,
|
||||||
liquidationFee,
|
liquidationFee,
|
||||||
|
minVaultToDepositsRatio,
|
||||||
|
netBorrowsLimitNative,
|
||||||
|
netBorrowsWindowSizeTs,
|
||||||
)
|
)
|
||||||
.accounts({
|
.accounts({
|
||||||
group: group.publicKey,
|
group: group.publicKey,
|
||||||
|
@ -467,7 +479,6 @@ export class MangoClient {
|
||||||
group: group.publicKey,
|
group: group.publicKey,
|
||||||
admin: (this.program.provider as AnchorProvider).wallet.publicKey,
|
admin: (this.program.provider as AnchorProvider).wallet.publicKey,
|
||||||
oracle: oraclePk,
|
oracle: oraclePk,
|
||||||
payer: (this.program.provider as AnchorProvider).wallet.publicKey,
|
|
||||||
})
|
})
|
||||||
.rpc();
|
.rpc();
|
||||||
}
|
}
|
||||||
|
@ -1444,25 +1455,28 @@ export class MangoClient {
|
||||||
public async perpEditMarket(
|
public async perpEditMarket(
|
||||||
group: Group,
|
group: Group,
|
||||||
perpMarketIndex: PerpMarketIndex,
|
perpMarketIndex: PerpMarketIndex,
|
||||||
oracle: PublicKey,
|
oracle: PublicKey | null,
|
||||||
oracleConfig: OracleConfigParams,
|
oracleConfig: OracleConfigParams | null,
|
||||||
baseDecimals: number,
|
baseDecimals: number | null,
|
||||||
maintAssetWeight: number,
|
maintAssetWeight: number | null,
|
||||||
initAssetWeight: number,
|
initAssetWeight: number | null,
|
||||||
maintLiabWeight: number,
|
maintLiabWeight: number | null,
|
||||||
initLiabWeight: number,
|
initLiabWeight: number | null,
|
||||||
liquidationFee: number,
|
liquidationFee: number | null,
|
||||||
makerFee: number,
|
makerFee: number | null,
|
||||||
takerFee: number,
|
takerFee: number | null,
|
||||||
feePenalty: number,
|
feePenalty: number | null,
|
||||||
minFunding: number,
|
minFunding: number | null,
|
||||||
maxFunding: number,
|
maxFunding: number | null,
|
||||||
impactQuantity: number,
|
impactQuantity: number | null,
|
||||||
groupInsuranceFund: boolean,
|
groupInsuranceFund: boolean | null,
|
||||||
trustedMarket: boolean,
|
trustedMarket: boolean | null,
|
||||||
settleFeeFlat: number,
|
settleFeeFlat: number | null,
|
||||||
settleFeeAmountThreshold: number,
|
settleFeeAmountThreshold: number | null,
|
||||||
settleFeeFractionLowHealth: number,
|
settleFeeFractionLowHealth: number | null,
|
||||||
|
stablePriceDelayIntervalSeconds: number | null,
|
||||||
|
stablePriceDelayGrowthLimit: number | null,
|
||||||
|
stablePriceGrowthLimit: number | null,
|
||||||
): Promise<TransactionSignature> {
|
): Promise<TransactionSignature> {
|
||||||
const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex);
|
const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex);
|
||||||
|
|
||||||
|
@ -1480,13 +1494,16 @@ export class MangoClient {
|
||||||
takerFee,
|
takerFee,
|
||||||
minFunding,
|
minFunding,
|
||||||
maxFunding,
|
maxFunding,
|
||||||
new BN(impactQuantity),
|
impactQuantity ? new BN(impactQuantity) : null,
|
||||||
groupInsuranceFund,
|
groupInsuranceFund,
|
||||||
trustedMarket,
|
trustedMarket,
|
||||||
feePenalty,
|
feePenalty,
|
||||||
settleFeeFlat,
|
settleFeeFlat,
|
||||||
settleFeeAmountThreshold,
|
settleFeeAmountThreshold,
|
||||||
settleFeeFractionLowHealth,
|
settleFeeFractionLowHealth,
|
||||||
|
stablePriceDelayIntervalSeconds,
|
||||||
|
stablePriceDelayGrowthLimit,
|
||||||
|
stablePriceGrowthLimit,
|
||||||
)
|
)
|
||||||
.accounts({
|
.accounts({
|
||||||
group: group.publicKey,
|
group: group.publicKey,
|
||||||
|
|
|
@ -33,6 +33,10 @@ const MAINNET_ORACLES = new Map([
|
||||||
['MNGO', '79wm3jjcPr6RaNQ4DGvP5KxG1mNd3gEBsg6FsNVFezK4'],
|
['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() {
|
async function createGroup() {
|
||||||
const admin = Keypair.fromSecretKey(
|
const admin = Keypair.fromSecretKey(
|
||||||
Buffer.from(
|
Buffer.from(
|
||||||
|
@ -129,6 +133,9 @@ async function registerTokens() {
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Registering USDT...`);
|
console.log(`Registering USDT...`);
|
||||||
|
@ -149,6 +156,9 @@ async function registerTokens() {
|
||||||
1.05,
|
1.05,
|
||||||
1.1,
|
1.1,
|
||||||
0.025, // rule of thumb used - half of maintLiabWeight
|
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...`);
|
console.log(`Registering BTC...`);
|
||||||
|
@ -169,6 +179,9 @@ async function registerTokens() {
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
0.05,
|
0.05,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Registering ETH...`);
|
console.log(`Registering ETH...`);
|
||||||
|
@ -189,6 +202,9 @@ async function registerTokens() {
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
0.05,
|
0.05,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Registering soETH...`);
|
console.log(`Registering soETH...`);
|
||||||
|
@ -209,6 +225,9 @@ async function registerTokens() {
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
0.05,
|
0.05,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Registering SOL...`);
|
console.log(`Registering SOL...`);
|
||||||
|
@ -229,6 +248,9 @@ async function registerTokens() {
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
0.05,
|
0.05,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Registering mSOL...`);
|
console.log(`Registering mSOL...`);
|
||||||
|
@ -249,6 +271,9 @@ async function registerTokens() {
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
0.05,
|
0.05,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
|
|
||||||
// log tokens/banks
|
// log tokens/banks
|
||||||
|
|
|
@ -341,6 +341,18 @@ export type MangoV4 = {
|
||||||
{
|
{
|
||||||
"name": "liquidationFee",
|
"name": "liquidationFee",
|
||||||
"type": "f32"
|
"type": "f32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "minVaultToDepositsRatio",
|
||||||
|
"type": "f64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "netBorrowsWindowSizeTs",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "netBorrowsLimitNative",
|
||||||
|
"type": "i64"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -499,6 +511,11 @@ export type MangoV4 = {
|
||||||
"name": "mintInfo",
|
"name": "mintInfo",
|
||||||
"isMut": true,
|
"isMut": true,
|
||||||
"isSigner": false
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "oracle",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"args": [
|
"args": [
|
||||||
|
@ -571,6 +588,24 @@ export type MangoV4 = {
|
||||||
"type": {
|
"type": {
|
||||||
"option": "f32"
|
"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",
|
"name": "oracle",
|
||||||
"isMut": true,
|
"isMut": true,
|
||||||
"isSigner": false
|
"isSigner": false
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "payer",
|
|
||||||
"isMut": true,
|
|
||||||
"isSigner": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"args": [
|
"args": [
|
||||||
|
@ -2416,6 +2446,11 @@ export type MangoV4 = {
|
||||||
"name": "perpMarket",
|
"name": "perpMarket",
|
||||||
"isMut": true,
|
"isMut": true,
|
||||||
"isSigner": false
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "oracle",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"args": [
|
"args": [
|
||||||
|
@ -2534,6 +2569,24 @@ export type MangoV4 = {
|
||||||
"type": {
|
"type": {
|
||||||
"option": "f32"
|
"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",
|
"name": "orderbook",
|
||||||
"isMut": true,
|
"isMut": true,
|
||||||
"isSigner": false
|
"isSigner": false
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "oracle",
|
|
||||||
"isMut": false,
|
|
||||||
"isSigner": false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"args": [
|
"args": [
|
||||||
|
@ -3391,11 +3439,11 @@ export type MangoV4 = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "indexLastUpdated",
|
"name": "indexLastUpdated",
|
||||||
"type": "i64"
|
"type": "u64"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bankRateLastUpdated",
|
"name": "bankRateLastUpdated",
|
||||||
"type": "i64"
|
"type": "u64"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "avgUtilization",
|
"name": "avgUtilization",
|
||||||
|
@ -3523,12 +3571,38 @@ export type MangoV4 = {
|
||||||
"defined": "OracleConfig"
|
"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",
|
"name": "reserved",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"u8",
|
||||||
2464
|
2136
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4167,12 +4241,18 @@ export type MangoV4 = {
|
||||||
],
|
],
|
||||||
"type": "f32"
|
"type": "f32"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "stablePriceModel",
|
||||||
|
"type": {
|
||||||
|
"defined": "StablePriceModel"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "reserved",
|
"name": "reserved",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"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",
|
"name": "TokenInfo",
|
||||||
"type": {
|
"type": {
|
||||||
|
@ -4444,19 +4553,13 @@ export type MangoV4 = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "oraclePrice",
|
"name": "prices",
|
||||||
"type": {
|
"type": {
|
||||||
"defined": "I80F48"
|
"defined": "Prices"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "balance",
|
"name": "balanceNative",
|
||||||
"type": {
|
|
||||||
"defined": "I80F48"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "serum3MaxReserved",
|
|
||||||
"type": {
|
"type": {
|
||||||
"defined": "I80F48"
|
"defined": "I80F48"
|
||||||
}
|
}
|
||||||
|
@ -4470,7 +4573,13 @@ export type MangoV4 = {
|
||||||
"kind": "struct",
|
"kind": "struct",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"name": "reserved",
|
"name": "reservedBase",
|
||||||
|
"type": {
|
||||||
|
"defined": "I80F48"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reservedQuote",
|
||||||
"type": {
|
"type": {
|
||||||
"defined": "I80F48"
|
"defined": "I80F48"
|
||||||
}
|
}
|
||||||
|
@ -4524,10 +4633,20 @@ export type MangoV4 = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "base",
|
"name": "baseLotSize",
|
||||||
"type": {
|
"type": "i64"
|
||||||
"defined": "I80F48"
|
},
|
||||||
}
|
{
|
||||||
|
"name": "baseLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bidsBaseLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "asksBaseLots",
|
||||||
|
"type": "i64"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "quote",
|
"name": "quote",
|
||||||
|
@ -4536,9 +4655,9 @@ export type MangoV4 = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "oraclePrice",
|
"name": "prices",
|
||||||
"type": {
|
"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",
|
"name": "TokenIndex",
|
||||||
"docs": [
|
"docs": [
|
||||||
|
@ -6140,6 +6369,11 @@ export type MangoV4 = {
|
||||||
"type": "i128",
|
"type": "i128",
|
||||||
"index": false
|
"index": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "stablePrice",
|
||||||
|
"type": "i128",
|
||||||
|
"index": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "feesAccrued",
|
"name": "feesAccrued",
|
||||||
"type": "i128",
|
"type": "i128",
|
||||||
|
@ -6185,6 +6419,11 @@ export type MangoV4 = {
|
||||||
"type": "i128",
|
"type": "i128",
|
||||||
"index": false
|
"index": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "stablePrice",
|
||||||
|
"type": "i128",
|
||||||
|
"index": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "collectedFees",
|
"name": "collectedFees",
|
||||||
"type": "i128",
|
"type": "i128",
|
||||||
|
@ -6880,6 +7119,21 @@ export type MangoV4 = {
|
||||||
"code": 6024,
|
"code": 6024,
|
||||||
"name": "OracleStale",
|
"name": "OracleStale",
|
||||||
"msg": "an oracle is stale"
|
"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",
|
"name": "liquidationFee",
|
||||||
"type": "f32"
|
"type": "f32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "minVaultToDepositsRatio",
|
||||||
|
"type": "f64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "netBorrowsWindowSizeTs",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "netBorrowsLimitNative",
|
||||||
|
"type": "i64"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -7385,6 +7651,11 @@ export const IDL: MangoV4 = {
|
||||||
"name": "mintInfo",
|
"name": "mintInfo",
|
||||||
"isMut": true,
|
"isMut": true,
|
||||||
"isSigner": false
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "oracle",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"args": [
|
"args": [
|
||||||
|
@ -7457,6 +7728,24 @@ export const IDL: MangoV4 = {
|
||||||
"type": {
|
"type": {
|
||||||
"option": "f32"
|
"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",
|
"name": "oracle",
|
||||||
"isMut": true,
|
"isMut": true,
|
||||||
"isSigner": false
|
"isSigner": false
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "payer",
|
|
||||||
"isMut": true,
|
|
||||||
"isSigner": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"args": [
|
"args": [
|
||||||
|
@ -9302,6 +9586,11 @@ export const IDL: MangoV4 = {
|
||||||
"name": "perpMarket",
|
"name": "perpMarket",
|
||||||
"isMut": true,
|
"isMut": true,
|
||||||
"isSigner": false
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "oracle",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"args": [
|
"args": [
|
||||||
|
@ -9420,6 +9709,24 @@ export const IDL: MangoV4 = {
|
||||||
"type": {
|
"type": {
|
||||||
"option": "f32"
|
"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",
|
"name": "orderbook",
|
||||||
"isMut": true,
|
"isMut": true,
|
||||||
"isSigner": false
|
"isSigner": false
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "oracle",
|
|
||||||
"isMut": false,
|
|
||||||
"isSigner": false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"args": [
|
"args": [
|
||||||
|
@ -10277,11 +10579,11 @@ export const IDL: MangoV4 = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "indexLastUpdated",
|
"name": "indexLastUpdated",
|
||||||
"type": "i64"
|
"type": "u64"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bankRateLastUpdated",
|
"name": "bankRateLastUpdated",
|
||||||
"type": "i64"
|
"type": "u64"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "avgUtilization",
|
"name": "avgUtilization",
|
||||||
|
@ -10409,12 +10711,38 @@ export const IDL: MangoV4 = {
|
||||||
"defined": "OracleConfig"
|
"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",
|
"name": "reserved",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"u8",
|
||||||
2464
|
2136
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11053,12 +11381,18 @@ export const IDL: MangoV4 = {
|
||||||
],
|
],
|
||||||
"type": "f32"
|
"type": "f32"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "stablePriceModel",
|
||||||
|
"type": {
|
||||||
|
"defined": "StablePriceModel"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "reserved",
|
"name": "reserved",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"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",
|
"name": "TokenInfo",
|
||||||
"type": {
|
"type": {
|
||||||
|
@ -11330,19 +11693,13 @@ export const IDL: MangoV4 = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "oraclePrice",
|
"name": "prices",
|
||||||
"type": {
|
"type": {
|
||||||
"defined": "I80F48"
|
"defined": "Prices"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "balance",
|
"name": "balanceNative",
|
||||||
"type": {
|
|
||||||
"defined": "I80F48"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "serum3MaxReserved",
|
|
||||||
"type": {
|
"type": {
|
||||||
"defined": "I80F48"
|
"defined": "I80F48"
|
||||||
}
|
}
|
||||||
|
@ -11356,7 +11713,13 @@ export const IDL: MangoV4 = {
|
||||||
"kind": "struct",
|
"kind": "struct",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"name": "reserved",
|
"name": "reservedBase",
|
||||||
|
"type": {
|
||||||
|
"defined": "I80F48"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reservedQuote",
|
||||||
"type": {
|
"type": {
|
||||||
"defined": "I80F48"
|
"defined": "I80F48"
|
||||||
}
|
}
|
||||||
|
@ -11410,10 +11773,20 @@ export const IDL: MangoV4 = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "base",
|
"name": "baseLotSize",
|
||||||
"type": {
|
"type": "i64"
|
||||||
"defined": "I80F48"
|
},
|
||||||
}
|
{
|
||||||
|
"name": "baseLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bidsBaseLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "asksBaseLots",
|
||||||
|
"type": "i64"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "quote",
|
"name": "quote",
|
||||||
|
@ -11422,9 +11795,9 @@ export const IDL: MangoV4 = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "oraclePrice",
|
"name": "prices",
|
||||||
"type": {
|
"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",
|
"name": "TokenIndex",
|
||||||
"docs": [
|
"docs": [
|
||||||
|
@ -13026,6 +13509,11 @@ export const IDL: MangoV4 = {
|
||||||
"type": "i128",
|
"type": "i128",
|
||||||
"index": false
|
"index": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "stablePrice",
|
||||||
|
"type": "i128",
|
||||||
|
"index": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "feesAccrued",
|
"name": "feesAccrued",
|
||||||
"type": "i128",
|
"type": "i128",
|
||||||
|
@ -13071,6 +13559,11 @@ export const IDL: MangoV4 = {
|
||||||
"type": "i128",
|
"type": "i128",
|
||||||
"index": false
|
"index": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "stablePrice",
|
||||||
|
"type": "i128",
|
||||||
|
"index": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "collectedFees",
|
"name": "collectedFees",
|
||||||
"type": "i128",
|
"type": "i128",
|
||||||
|
@ -13766,6 +14259,21 @@ export const IDL: MangoV4 = {
|
||||||
"code": 6024,
|
"code": 6024,
|
||||||
"name": "OracleStale",
|
"name": "OracleStale",
|
||||||
"msg": "an oracle is stale"
|
"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'],
|
['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);
|
const GROUP_NUM = Number(process.env.GROUP_NUM || 0);
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
@ -122,6 +126,9 @@ async function main() {
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
await group.reloadAll(client);
|
await group.reloadAll(client);
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
|
@ -146,6 +153,9 @@ async function main() {
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
0.05,
|
0.05,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
await group.reloadAll(client);
|
await group.reloadAll(client);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -172,6 +182,9 @@ async function main() {
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
0.05,
|
0.05,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
await group.reloadAll(client);
|
await group.reloadAll(client);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -205,6 +218,9 @@ async function main() {
|
||||||
1.2,
|
1.2,
|
||||||
1.4,
|
1.4,
|
||||||
0.02,
|
0.02,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
await group.reloadAll(client);
|
await group.reloadAll(client);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -231,6 +247,9 @@ async function main() {
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
0.05,
|
0.05,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
await group.reloadAll(client);
|
await group.reloadAll(client);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -257,6 +276,9 @@ async function main() {
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
0.05,
|
0.05,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
await group.reloadAll(client);
|
await group.reloadAll(client);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -403,6 +425,9 @@ async function main() {
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
0,
|
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`);
|
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
|
||||||
await group.reloadAll(client);
|
await group.reloadAll(client);
|
||||||
|
@ -427,6 +452,9 @@ async function main() {
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
0.05,
|
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`);
|
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
|
||||||
await group.reloadAll(client);
|
await group.reloadAll(client);
|
||||||
|
@ -451,6 +479,9 @@ async function main() {
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
0.05,
|
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`);
|
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
|
||||||
await group.reloadAll(client);
|
await group.reloadAll(client);
|
||||||
|
@ -483,6 +514,9 @@ async function main() {
|
||||||
1000,
|
1000,
|
||||||
1000000,
|
1000000,
|
||||||
0.05,
|
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`);
|
console.log(`https://explorer.solana.com/tx/${sig}?cluster=devnet`);
|
||||||
await group.reloadAll(client);
|
await group.reloadAll(client);
|
||||||
|
|
|
@ -50,6 +50,10 @@ const MAINNET_SERUM3_MARKETS = new Map([
|
||||||
['DUST/SOL', '8WCzJpSNcLUYXPYeUDAXpH4hgqxFJpkYkVT6GJDSpcGx'],
|
['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 } =
|
const { MB_CLUSTER_URL, MB_PAYER_KEYPAIR, MB_USER_KEYPAIR, MB_USER2_KEYPAIR } =
|
||||||
process.env;
|
process.env;
|
||||||
|
|
||||||
|
@ -160,6 +164,9 @@ async function registerTokens() {
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Registering USDT...`);
|
console.log(`Registering USDT...`);
|
||||||
|
@ -180,6 +187,9 @@ async function registerTokens() {
|
||||||
1.05,
|
1.05,
|
||||||
1.1,
|
1.1,
|
||||||
0.025,
|
0.025,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Registering BTC...`);
|
console.log(`Registering BTC...`);
|
||||||
|
@ -200,6 +210,9 @@ async function registerTokens() {
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
0.05,
|
0.05,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Registering ETH...`);
|
console.log(`Registering ETH...`);
|
||||||
|
@ -220,6 +233,9 @@ async function registerTokens() {
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
0.05,
|
0.05,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Registering soETH...`);
|
console.log(`Registering soETH...`);
|
||||||
|
@ -240,6 +256,9 @@ async function registerTokens() {
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
0.05,
|
0.05,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Registering SOL...`);
|
console.log(`Registering SOL...`);
|
||||||
|
@ -260,6 +279,9 @@ async function registerTokens() {
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
0.05,
|
0.05,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Registering MSOL...`);
|
console.log(`Registering MSOL...`);
|
||||||
|
@ -280,6 +302,9 @@ async function registerTokens() {
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
0.05,
|
0.05,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
console.log(`Registering RAY...`);
|
console.log(`Registering RAY...`);
|
||||||
const rayMainnetMint = new PublicKey(MAINNET_MINTS.get('RAY')!);
|
const rayMainnetMint = new PublicKey(MAINNET_MINTS.get('RAY')!);
|
||||||
|
@ -306,6 +331,9 @@ async function registerTokens() {
|
||||||
8 / 7,
|
8 / 7,
|
||||||
4 / 3,
|
4 / 3,
|
||||||
1 / 16,
|
1 / 16,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Registering DUST...`);
|
console.log(`Registering DUST...`);
|
||||||
|
@ -333,6 +361,9 @@ async function registerTokens() {
|
||||||
81 / 80,
|
81 / 80,
|
||||||
41 / 40, // 40x leverage so we can test something
|
41 / 40, // 40x leverage so we can test something
|
||||||
1 / 160, // no liquidation fee
|
1 / 160, // no liquidation fee
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
|
|
||||||
// log tokens/banks
|
// log tokens/banks
|
||||||
|
|
|
@ -33,6 +33,10 @@ const MAINNET_SERUM3_MARKETS = new Map([
|
||||||
['SOL/USDC', '9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT'],
|
['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() {
|
async function main() {
|
||||||
const options = AnchorProvider.defaultOptions();
|
const options = AnchorProvider.defaultOptions();
|
||||||
const connection = new Connection(process.env.CLUSTER_URL!, options);
|
const connection = new Connection(process.env.CLUSTER_URL!, options);
|
||||||
|
@ -116,6 +120,9 @@ async function main() {
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
await group.reloadAll(client);
|
await group.reloadAll(client);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -142,6 +149,9 @@ async function main() {
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
0.05,
|
0.05,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
await group.reloadAll(client);
|
await group.reloadAll(client);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -168,6 +178,9 @@ async function main() {
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
0.05,
|
0.05,
|
||||||
|
MIN_VAULT_TO_DEPOSITS_RATIO,
|
||||||
|
NET_BORROWS_WINDOW_SIZE_TS,
|
||||||
|
NET_BORROWS_LIMIT_NATIVE,
|
||||||
);
|
);
|
||||||
await group.reloadAll(client);
|
await group.reloadAll(client);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -167,6 +167,9 @@ async function main() {
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
// At a price of $1/ui-SOL we can buy 0.1 ui-SOL for the 100k native-USDC we have.
|
// 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,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue