force close tokens program part (#518)
* force close tokens Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * add test Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * reset Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> --------- Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
parent
2dca3003df
commit
b07857c696
|
@ -715,7 +715,7 @@
|
|||
{
|
||||
"name": "reduceOnlyOpt",
|
||||
"type": {
|
||||
"option": "bool"
|
||||
"option": "u8"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -723,6 +723,12 @@
|
|||
"type": {
|
||||
"option": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "forceCloseOpt",
|
||||
"type": {
|
||||
"option": "bool"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -2570,6 +2576,45 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tokenForceCloseBorrowsWithToken",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "liqor",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "liqorOwner",
|
||||
"isMut": false,
|
||||
"isSigner": true
|
||||
},
|
||||
{
|
||||
"name": "liqee",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "assetTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "liabTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "maxLiabTransfer",
|
||||
"type": "u64"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tokenLiqBankruptcy",
|
||||
"accounts": [
|
||||
|
@ -4125,12 +4170,16 @@
|
|||
"name": "reduceOnly",
|
||||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "forceClose",
|
||||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
2119
|
||||
2118
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -6981,6 +7030,9 @@
|
|||
},
|
||||
{
|
||||
"name": "AccountBuybackFeesWithMngo"
|
||||
},
|
||||
{
|
||||
"name": "TokenForceCloseBorrowsWithToken"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -8684,6 +8736,11 @@
|
|||
"code": 6045,
|
||||
"name": "HealthRegionBadInnerInstruction",
|
||||
"msg": "HealthRegions allow only specific instructions between Begin and End"
|
||||
},
|
||||
{
|
||||
"code": 6046,
|
||||
"name": "TokenInForceClose",
|
||||
"msg": "token is in force close"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -47,6 +47,7 @@ pub use token_add_bank::*;
|
|||
pub use token_deposit::*;
|
||||
pub use token_deregister::*;
|
||||
pub use token_edit::*;
|
||||
pub use token_force_close_borrows_with_token::*;
|
||||
pub use token_liq_bankruptcy::*;
|
||||
pub use token_liq_with_token::*;
|
||||
pub use token_register::*;
|
||||
|
@ -103,6 +104,7 @@ mod token_add_bank;
|
|||
mod token_deposit;
|
||||
mod token_deregister;
|
||||
mod token_edit;
|
||||
mod token_force_close_borrows_with_token;
|
||||
mod token_liq_bankruptcy;
|
||||
mod token_liq_with_token;
|
||||
mod token_register;
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
use crate::error::*;
|
||||
use crate::state::*;
|
||||
use anchor_lang::prelude::*;
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TokenForceCloseBorrowsWithToken<'info> {
|
||||
#[account(
|
||||
constraint = group.load()?.is_ix_enabled(IxGate::TokenForceCloseBorrowsWithToken) @ MangoError::IxIsDisabled,
|
||||
)]
|
||||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
has_one = group,
|
||||
constraint = liqor.load()?.is_operational() @ MangoError::AccountIsFrozen
|
||||
// liqor_owner is checked at #1
|
||||
)]
|
||||
pub liqor: AccountLoader<'info, MangoAccountFixed>,
|
||||
pub liqor_owner: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
has_one = group,
|
||||
constraint = liqee.load()?.is_operational() @ MangoError::AccountIsFrozen
|
||||
)]
|
||||
pub liqee: AccountLoader<'info, MangoAccountFixed>,
|
||||
}
|
|
@ -97,6 +97,8 @@ pub enum MangoError {
|
|||
PerpOrderIdNotFound,
|
||||
#[msg("HealthRegions allow only specific instructions between Begin and End")]
|
||||
HealthRegionBadInnerInstruction,
|
||||
#[msg("token is in force close")]
|
||||
TokenInForceClose,
|
||||
}
|
||||
|
||||
impl MangoError {
|
||||
|
|
|
@ -343,10 +343,15 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
|
||||
let change_amount = change.amount - loan_origination_fee;
|
||||
let native_after_change = native + change_amount;
|
||||
if bank.is_reduce_only() {
|
||||
if bank.are_deposits_reduce_only() {
|
||||
require!(
|
||||
(change_amount < 0 && native_after_change >= 0)
|
||||
|| (change_amount > 0 && native_after_change < 1),
|
||||
native_after_change < 1 || native_after_change <= native,
|
||||
MangoError::TokenInReduceOnlyMode
|
||||
);
|
||||
}
|
||||
if bank.are_borrows_reduce_only() {
|
||||
require!(
|
||||
native_after_change >= native || native_after_change >= 0,
|
||||
MangoError::TokenInReduceOnlyMode
|
||||
);
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@ pub fn ix_gate_set(ctx: Context<IxGateSet>, ix_gate: u128) -> Result<()> {
|
|||
log_if_changed(&group, ix_gate, IxGate::TokenUpdateIndexAndRate);
|
||||
log_if_changed(&group, ix_gate, IxGate::TokenWithdraw);
|
||||
log_if_changed(&group, ix_gate, IxGate::AccountBuybackFeesWithMngo);
|
||||
log_if_changed(&group, ix_gate, IxGate::TokenForceCloseBorrowsWithToken);
|
||||
|
||||
group.ix_gate = ix_gate;
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ pub use token_add_bank::*;
|
|||
pub use token_deposit::*;
|
||||
pub use token_deregister::*;
|
||||
pub use token_edit::*;
|
||||
pub use token_force_close_borrows_with_token::*;
|
||||
pub use token_liq_bankruptcy::*;
|
||||
pub use token_liq_with_token::*;
|
||||
pub use token_register::*;
|
||||
|
@ -103,6 +104,7 @@ mod token_add_bank;
|
|||
mod token_deposit;
|
||||
mod token_deregister;
|
||||
mod token_edit;
|
||||
mod token_force_close_borrows_with_token;
|
||||
mod token_liq_bankruptcy;
|
||||
mod token_liq_with_token;
|
||||
mod token_register;
|
||||
|
|
|
@ -51,7 +51,7 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
|
|||
let account = self.account.load_full()?;
|
||||
let position = account.token_position(token_index)?;
|
||||
|
||||
let amount_i80f48 = if reduce_only || bank.is_reduce_only() {
|
||||
let amount_i80f48 = if reduce_only || bank.are_deposits_reduce_only() {
|
||||
position
|
||||
.native(&bank)
|
||||
.min(I80F48::ZERO)
|
||||
|
@ -61,7 +61,7 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
|
|||
} else {
|
||||
I80F48::from(amount)
|
||||
};
|
||||
if bank.is_reduce_only() {
|
||||
if bank.are_deposits_reduce_only() {
|
||||
require!(
|
||||
reduce_only || amount_i80f48 == I80F48::from(amount),
|
||||
MangoError::TokenInReduceOnlyMode
|
||||
|
|
|
@ -36,8 +36,9 @@ pub fn token_edit(
|
|||
deposit_weight_scale_start_quote_opt: Option<f64>,
|
||||
reset_stable_price: bool,
|
||||
reset_net_borrow_limit: bool,
|
||||
reduce_only_opt: Option<bool>,
|
||||
reduce_only_opt: Option<u8>,
|
||||
name_opt: Option<String>,
|
||||
force_close_opt: Option<bool>,
|
||||
) -> Result<()> {
|
||||
let group = ctx.accounts.group.load()?;
|
||||
|
||||
|
@ -274,14 +275,15 @@ pub fn token_edit(
|
|||
msg!(
|
||||
"Reduce only: old - {:?}, new - {:?}",
|
||||
bank.reduce_only,
|
||||
u8::from(reduce_only)
|
||||
reduce_only
|
||||
);
|
||||
bank.reduce_only = u8::from(reduce_only);
|
||||
|
||||
// security admin can only enable reduce_only
|
||||
if !reduce_only {
|
||||
// security admin can only make it stricter
|
||||
// anything that makes it less strict, should require admin
|
||||
if reduce_only == 0 || (reduce_only == 2 && bank.reduce_only == 1) {
|
||||
require_group_admin = true;
|
||||
}
|
||||
bank.reduce_only = reduce_only;
|
||||
};
|
||||
|
||||
if let Some(name) = name_opt.as_ref() {
|
||||
|
@ -289,6 +291,19 @@ pub fn token_edit(
|
|||
bank.name = fill_from_str(&name)?;
|
||||
require_group_admin = true;
|
||||
};
|
||||
|
||||
if let Some(force_close) = force_close_opt {
|
||||
if force_close {
|
||||
require!(bank.reduce_only > 0, MangoError::SomeError);
|
||||
}
|
||||
msg!(
|
||||
"Force close: old - {:?}, new - {:?}",
|
||||
bank.force_close,
|
||||
u8::from(force_close)
|
||||
);
|
||||
bank.force_close = u8::from(force_close);
|
||||
require_group_admin = true;
|
||||
};
|
||||
}
|
||||
|
||||
// account constraint #1
|
||||
|
|
|
@ -0,0 +1,202 @@
|
|||
use crate::accounts_ix::*;
|
||||
use crate::error::*;
|
||||
use crate::health::*;
|
||||
use crate::logs::TokenBalanceLog;
|
||||
use crate::state::*;
|
||||
use anchor_lang::prelude::*;
|
||||
use fixed::types::I80F48;
|
||||
|
||||
pub fn token_force_close_borrows_with_token(
|
||||
ctx: Context<TokenForceCloseBorrowsWithToken>,
|
||||
// which asset tokens are allowed, is checked at #3
|
||||
asset_token_index: TokenIndex,
|
||||
// token's force_close flag is checked at #2
|
||||
liab_token_index: TokenIndex,
|
||||
max_liab_transfer: u64,
|
||||
) -> Result<()> {
|
||||
let group_pk = &ctx.accounts.group.key();
|
||||
|
||||
require_neq!(asset_token_index, liab_token_index, MangoError::SomeError);
|
||||
|
||||
let mut account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk)
|
||||
.context("create account retriever")?;
|
||||
|
||||
require_keys_neq!(ctx.accounts.liqor.key(), ctx.accounts.liqee.key());
|
||||
let mut liqor = ctx.accounts.liqor.load_full_mut()?;
|
||||
// account constraint #1
|
||||
require!(
|
||||
liqor
|
||||
.fixed
|
||||
.is_owner_or_delegate(ctx.accounts.liqor_owner.key()),
|
||||
MangoError::SomeError
|
||||
);
|
||||
require_msg_typed!(
|
||||
!liqor.fixed.being_liquidated(),
|
||||
MangoError::BeingLiquidated,
|
||||
"liqor account"
|
||||
);
|
||||
|
||||
let mut liqee = ctx.accounts.liqee.load_full_mut()?;
|
||||
|
||||
//
|
||||
// Transfer liab_token from liqor to liqee to close the borrows.
|
||||
// Transfer corresponding amount of asset_token from liqee to liqor.
|
||||
//
|
||||
let now_ts = Clock::get()?.unix_timestamp.try_into().unwrap();
|
||||
{
|
||||
let liqor: &mut MangoAccountRefMut = &mut liqor.borrow_mut();
|
||||
let liqor_key = ctx.accounts.liqor.key();
|
||||
let liqee: &mut MangoAccountRefMut = &mut liqee.borrow_mut();
|
||||
let liqee_key = ctx.accounts.liqee.key();
|
||||
|
||||
let (asset_bank, asset_oracle_price, opt_liab_bank_and_price) =
|
||||
account_retriever.banks_mut_and_oracles(asset_token_index, liab_token_index)?;
|
||||
let (liab_bank, liab_oracle_price) = opt_liab_bank_and_price.unwrap();
|
||||
|
||||
// account constraint #2
|
||||
require!(liab_bank.is_force_close(), MangoError::TokenInForceClose);
|
||||
|
||||
// account constraint #3
|
||||
// only allow combination of asset and liab token,
|
||||
// where liqee's health would be guaranteed to not decrease
|
||||
require_gte!(
|
||||
liab_bank.init_liab_weight,
|
||||
asset_bank.init_liab_weight * (I80F48::ONE + liab_bank.liquidation_fee),
|
||||
MangoError::SomeError
|
||||
);
|
||||
|
||||
let (liqee_asset_position, liqee_asset_raw_index) =
|
||||
liqee.token_position_and_raw_index(asset_token_index)?;
|
||||
let liqee_asset_native = liqee_asset_position.native(asset_bank);
|
||||
|
||||
let (liqee_liab_position, liqee_liab_raw_index, _) =
|
||||
liqee.ensure_token_position(liab_token_index)?;
|
||||
let liqee_liab_native = liqee_liab_position.native(liab_bank);
|
||||
require!(liqee_liab_native.is_negative(), MangoError::SomeError);
|
||||
|
||||
let (liqor_liab_position, liqor_liab_raw_index, _) =
|
||||
liqor.ensure_token_position(liab_token_index)?;
|
||||
let liqor_liab_native = liqor_liab_position.native(liab_bank);
|
||||
// Require that liqor obtain deposits before he tries to liquidate liqee's borrows, to prevent
|
||||
// moving other liquidator from earning further liquidation fee from these borrows by trying to liquidate the current liqor
|
||||
require!(liqor_liab_native.is_positive(), MangoError::SomeError);
|
||||
|
||||
// The amount of liab native tokens we will transfer
|
||||
let max_liab_transfer = I80F48::from(max_liab_transfer);
|
||||
let liab_transfer = max_liab_transfer
|
||||
.min(-liqee_liab_native)
|
||||
.min(liqor_liab_native)
|
||||
.max(I80F48::ZERO);
|
||||
|
||||
// The amount of asset native tokens we will give up for them
|
||||
let fee_factor = I80F48::ONE + liab_bank.liquidation_fee;
|
||||
let liab_oracle_price_adjusted = liab_oracle_price * fee_factor;
|
||||
let asset_transfer = liab_transfer * liab_oracle_price_adjusted / asset_oracle_price;
|
||||
|
||||
// Apply the balance changes to the liqor and liqee accounts
|
||||
let liqee_liab_active =
|
||||
liab_bank.deposit_with_dusting(liqee_liab_position, liab_transfer, now_ts)?;
|
||||
let liqee_liab_indexed_position = liqee_liab_position.indexed_position;
|
||||
|
||||
let (liqor_liab_active, loan_origination_fee) = liab_bank.withdraw_with_fee(
|
||||
liqor_liab_position,
|
||||
liab_transfer,
|
||||
now_ts,
|
||||
liab_oracle_price,
|
||||
)?;
|
||||
let liqor_liab_indexed_position = liqor_liab_position.indexed_position;
|
||||
let liqee_liab_native_after = liqee_liab_position.native(liab_bank);
|
||||
|
||||
let (liqor_asset_position, liqor_asset_raw_index, _) =
|
||||
liqor.ensure_token_position(asset_token_index)?;
|
||||
let liqor_asset_active =
|
||||
asset_bank.deposit(liqor_asset_position, asset_transfer, now_ts)?;
|
||||
let liqor_asset_indexed_position = liqor_asset_position.indexed_position;
|
||||
|
||||
let liqee_asset_position = liqee.token_position_mut_by_raw_index(liqee_asset_raw_index);
|
||||
let liqee_asset_active = asset_bank.withdraw_without_fee_with_dusting(
|
||||
liqee_asset_position,
|
||||
asset_transfer,
|
||||
now_ts,
|
||||
asset_oracle_price,
|
||||
)?;
|
||||
let liqee_asset_indexed_position = liqee_asset_position.indexed_position;
|
||||
let liqee_assets_native_after = liqee_asset_position.native(asset_bank);
|
||||
|
||||
msg!(
|
||||
"Force closed {} liab for {} asset",
|
||||
liab_transfer,
|
||||
asset_transfer
|
||||
);
|
||||
|
||||
// liqee asset
|
||||
emit!(TokenBalanceLog {
|
||||
mango_group: liqee.fixed.group,
|
||||
mango_account: liqee_key,
|
||||
token_index: asset_token_index,
|
||||
indexed_position: liqee_asset_indexed_position.to_bits(),
|
||||
deposit_index: asset_bank.deposit_index.to_bits(),
|
||||
borrow_index: asset_bank.borrow_index.to_bits(),
|
||||
});
|
||||
// liqee liab
|
||||
emit!(TokenBalanceLog {
|
||||
mango_group: liqee.fixed.group,
|
||||
mango_account: liqee_key,
|
||||
token_index: liab_token_index,
|
||||
indexed_position: liqee_liab_indexed_position.to_bits(),
|
||||
deposit_index: liab_bank.deposit_index.to_bits(),
|
||||
borrow_index: liab_bank.borrow_index.to_bits(),
|
||||
});
|
||||
// liqor asset
|
||||
emit!(TokenBalanceLog {
|
||||
mango_group: liqee.fixed.group,
|
||||
mango_account: liqor_key,
|
||||
token_index: asset_token_index,
|
||||
indexed_position: liqor_asset_indexed_position.to_bits(),
|
||||
deposit_index: asset_bank.deposit_index.to_bits(),
|
||||
borrow_index: asset_bank.borrow_index.to_bits(),
|
||||
});
|
||||
// liqor liab
|
||||
emit!(TokenBalanceLog {
|
||||
mango_group: liqee.fixed.group,
|
||||
mango_account: liqor_key,
|
||||
token_index: liab_token_index,
|
||||
indexed_position: liqor_liab_indexed_position.to_bits(),
|
||||
deposit_index: liab_bank.deposit_index.to_bits(),
|
||||
borrow_index: liab_bank.borrow_index.to_bits(),
|
||||
});
|
||||
|
||||
let liqee_health_cache = new_health_cache(&liqee.borrow(), &mut account_retriever)
|
||||
.context("create liqee health cache")?;
|
||||
let liqee_liq_end_health = liqee_health_cache.health(HealthType::LiquidationEnd);
|
||||
liqee
|
||||
.fixed
|
||||
.maybe_recover_from_being_liquidated(liqee_liq_end_health);
|
||||
|
||||
// Since we use a scanning account retriever, it's safe to deactivate inactive token positions
|
||||
if !liqee_asset_active {
|
||||
liqee.deactivate_token_position_and_log(liqee_asset_raw_index, liqee_key);
|
||||
}
|
||||
if !liqee_liab_active {
|
||||
liqee.deactivate_token_position_and_log(liqee_liab_raw_index, liqee_key);
|
||||
}
|
||||
if !liqor_asset_active {
|
||||
liqor.deactivate_token_position_and_log(liqor_asset_raw_index, liqor_key);
|
||||
}
|
||||
if !liqor_liab_active {
|
||||
liqor.deactivate_token_position_and_log(liqor_liab_raw_index, liqor_key)
|
||||
}
|
||||
};
|
||||
|
||||
// Check liqor's health
|
||||
// This should always improve liqor health, since we decrease the zero-asset-weight
|
||||
// liab token and gain some asset token, this check is just for denfensive measure
|
||||
let liqor_health = compute_health(&liqor.borrow(), HealthType::Init, &mut account_retriever)
|
||||
.context("compute liqor health")?;
|
||||
require!(liqor_health >= 0, MangoError::HealthMustBePositive);
|
||||
|
||||
// TODO log
|
||||
// emit!(TokenForceCloseBorrowWithToken
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -88,7 +88,8 @@ pub fn token_register(
|
|||
borrow_weight_scale_start_quote: f64::MAX,
|
||||
deposit_weight_scale_start_quote: f64::MAX,
|
||||
reduce_only: 0,
|
||||
reserved: [0; 2119],
|
||||
force_close: 0,
|
||||
reserved: [0; 2118],
|
||||
};
|
||||
require_gt!(bank.max_rate, MINIMUM_MAX_RATE);
|
||||
|
||||
|
|
|
@ -74,7 +74,8 @@ pub fn token_register_trustless(
|
|||
borrow_weight_scale_start_quote: 100_000_000_000.0, // $100k
|
||||
deposit_weight_scale_start_quote: 100_000_000_000.0, // $100k
|
||||
reduce_only: 0,
|
||||
reserved: [0; 2119],
|
||||
force_close: 0,
|
||||
reserved: [0; 2118],
|
||||
};
|
||||
require_gt!(bank.max_rate, MINIMUM_MAX_RATE);
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
|
|||
|
||||
let is_borrow = amount > native_position;
|
||||
require!(allow_borrow || !is_borrow, MangoError::SomeError);
|
||||
if bank.is_reduce_only() {
|
||||
if bank.are_borrows_reduce_only() {
|
||||
require!(!is_borrow, MangoError::TokenInReduceOnlyMode);
|
||||
}
|
||||
|
||||
|
|
|
@ -169,8 +169,9 @@ pub mod mango_v4 {
|
|||
deposit_weight_scale_start_quote_opt: Option<f64>,
|
||||
reset_stable_price: bool,
|
||||
reset_net_borrow_limit: bool,
|
||||
reduce_only_opt: Option<bool>,
|
||||
reduce_only_opt: Option<u8>,
|
||||
name_opt: Option<String>,
|
||||
force_close_opt: Option<bool>,
|
||||
) -> Result<()> {
|
||||
#[cfg(feature = "enable-gpl")]
|
||||
instructions::token_edit(
|
||||
|
@ -198,6 +199,7 @@ pub mod mango_v4 {
|
|||
reset_net_borrow_limit,
|
||||
reduce_only_opt,
|
||||
name_opt,
|
||||
force_close_opt,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -540,6 +542,22 @@ pub mod mango_v4 {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn token_force_close_borrows_with_token(
|
||||
ctx: Context<TokenForceCloseBorrowsWithToken>,
|
||||
asset_token_index: TokenIndex,
|
||||
liab_token_index: TokenIndex,
|
||||
max_liab_transfer: u64,
|
||||
) -> Result<()> {
|
||||
#[cfg(feature = "enable-gpl")]
|
||||
instructions::token_force_close_borrows_with_token(
|
||||
ctx,
|
||||
asset_token_index,
|
||||
liab_token_index,
|
||||
max_liab_transfer,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn token_liq_bankruptcy(
|
||||
ctx: Context<TokenLiqBankruptcy>,
|
||||
max_liab_transfer: I80F48,
|
||||
|
|
|
@ -127,10 +127,16 @@ pub struct Bank {
|
|||
/// See scaled_init_asset_weight().
|
||||
pub deposit_weight_scale_start_quote: f64,
|
||||
|
||||
// We have 3 modes
|
||||
// 0 - Off,
|
||||
// 1 - ReduceDepositsReduceBorrows - standard
|
||||
// 2 - ReduceBorrows - borrows can only be reduced, but deposits have no restriction, special case for
|
||||
// force close mode, where liqor should first acquire deposits before closing liqee's borrows
|
||||
pub reduce_only: u8,
|
||||
pub force_close: u8,
|
||||
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub reserved: [u8; 2119],
|
||||
pub reserved: [u8; 2118],
|
||||
}
|
||||
const_assert_eq!(
|
||||
size_of::<Bank>(),
|
||||
|
@ -158,7 +164,8 @@ const_assert_eq!(
|
|||
+ 8
|
||||
+ 8
|
||||
+ 1
|
||||
+ 2119
|
||||
+ 1
|
||||
+ 2118
|
||||
);
|
||||
const_assert_eq!(size_of::<Bank>(), 3064);
|
||||
const_assert_eq!(size_of::<Bank>() % 8, 0);
|
||||
|
@ -219,7 +226,8 @@ impl Bank {
|
|||
borrow_weight_scale_start_quote: f64::MAX,
|
||||
deposit_weight_scale_start_quote: f64::MAX,
|
||||
reduce_only: 0,
|
||||
reserved: [0; 2119],
|
||||
force_close: 0,
|
||||
reserved: [0; 2118],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,10 +237,18 @@ impl Bank {
|
|||
.trim_matches(char::from(0))
|
||||
}
|
||||
|
||||
pub fn is_reduce_only(&self) -> bool {
|
||||
pub fn are_deposits_reduce_only(&self) -> bool {
|
||||
self.reduce_only == 1
|
||||
}
|
||||
|
||||
pub fn are_borrows_reduce_only(&self) -> bool {
|
||||
self.reduce_only == 1 || self.reduce_only == 2
|
||||
}
|
||||
|
||||
pub fn is_force_close(&self) -> bool {
|
||||
self.force_close == 1
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn native_borrows(&self) -> I80F48 {
|
||||
self.borrow_index * self.indexed_borrows
|
||||
|
|
|
@ -187,6 +187,7 @@ pub enum IxGate {
|
|||
TokenUpdateIndexAndRate = 46,
|
||||
TokenWithdraw = 47,
|
||||
AccountBuybackFeesWithMngo = 48,
|
||||
TokenForceCloseBorrowsWithToken = 49,
|
||||
// NOTE: Adding new variants requires matching changes in ts and the ix_gate_set instruction.
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ mod test_benchmark;
|
|||
mod test_borrow_limits;
|
||||
mod test_delegate;
|
||||
mod test_fees_buyback_with_mngo;
|
||||
mod test_force_close;
|
||||
mod test_health_compute;
|
||||
mod test_health_region;
|
||||
mod test_ix_gate_set;
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_force_close() -> Result<(), TransportError> {
|
||||
let test_builder = TestContextBuilder::new();
|
||||
let context = test_builder.start_default().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..4];
|
||||
let payer_mint_accounts = &context.users[1].token_accounts[0..4];
|
||||
|
||||
//
|
||||
// SETUP: Create a group and an account to fill the vaults
|
||||
//
|
||||
|
||||
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints: mints.to_vec(),
|
||||
..GroupWithTokensConfig::default()
|
||||
}
|
||||
.create(solana)
|
||||
.await;
|
||||
let collateral_token = &tokens[0];
|
||||
let borrow_token = &tokens[1];
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
TokenEditWeights {
|
||||
group,
|
||||
admin,
|
||||
mint: mints[1].pubkey,
|
||||
init_asset_weight: 0.6,
|
||||
maint_asset_weight: 0.8,
|
||||
maint_liab_weight: 1.2,
|
||||
init_liab_weight: 1.5, // changed from 1.4
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// deposit some funds, to the vaults aren't empty
|
||||
create_funded_account(
|
||||
&solana,
|
||||
group,
|
||||
owner,
|
||||
99,
|
||||
&context.users[1],
|
||||
mints,
|
||||
100000,
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
|
||||
let deposit1_amount = 100;
|
||||
let liqor = create_funded_account(
|
||||
&solana,
|
||||
group,
|
||||
owner,
|
||||
1,
|
||||
&context.users[0],
|
||||
&[mints[0]],
|
||||
deposit1_amount,
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
|
||||
//
|
||||
// SETUP: Make an account with some collateral and some borrows
|
||||
//
|
||||
let liqee = create_funded_account(
|
||||
&solana,
|
||||
group,
|
||||
owner,
|
||||
0,
|
||||
&context.users[0],
|
||||
&[mints[0]],
|
||||
deposit1_amount,
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
|
||||
let borrow1_amount = 10;
|
||||
send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: borrow1_amount,
|
||||
allow_borrow: true,
|
||||
account: liqee,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// test force close is enabled
|
||||
//
|
||||
assert!(send_tx(
|
||||
solana,
|
||||
TokenForceCloseBorrowsWithTokenInstruction {
|
||||
liqee: liqee,
|
||||
liqor: liqor,
|
||||
liqor_owner: owner,
|
||||
asset_token_index: collateral_token.index,
|
||||
liab_token_index: borrow_token.index,
|
||||
max_liab_transfer: 10000,
|
||||
asset_bank_index: 0,
|
||||
liab_bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
// set force close, and reduce only to 1
|
||||
send_tx(
|
||||
solana,
|
||||
TokenMakeReduceOnly {
|
||||
admin,
|
||||
group,
|
||||
mint: mints[1].pubkey,
|
||||
reduce_only: 1,
|
||||
force_close: false,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// test liqor needs deposits to be gte than the borrows it wants to liquidate
|
||||
//
|
||||
assert!(send_tx(
|
||||
solana,
|
||||
TokenForceCloseBorrowsWithTokenInstruction {
|
||||
liqee: liqee,
|
||||
liqor: liqor,
|
||||
liqor_owner: owner,
|
||||
asset_token_index: collateral_token.index,
|
||||
liab_token_index: borrow_token.index,
|
||||
max_liab_transfer: 10000,
|
||||
asset_bank_index: 0,
|
||||
liab_bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
//
|
||||
// test deposit with reduce only set to 1
|
||||
//
|
||||
let deposit1_amount = 11;
|
||||
assert!(send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit1_amount,
|
||||
reduce_only: false,
|
||||
account: liqor,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
// set force close, and reduce only to 2
|
||||
send_tx(
|
||||
solana,
|
||||
TokenMakeReduceOnly {
|
||||
admin,
|
||||
group,
|
||||
mint: mints[1].pubkey,
|
||||
reduce_only: 2,
|
||||
force_close: true,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// test deposit with reduce only set to 2
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit1_amount,
|
||||
reduce_only: false,
|
||||
account: liqor,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// test force close borrows
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
TokenForceCloseBorrowsWithTokenInstruction {
|
||||
liqee: liqee,
|
||||
liqor: liqor,
|
||||
liqor_owner: owner,
|
||||
asset_token_index: collateral_token.index,
|
||||
liab_token_index: borrow_token.index,
|
||||
max_liab_transfer: 10000,
|
||||
asset_bank_index: 0,
|
||||
liab_bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(account_position_closed(solana, liqee, borrow_token.bank).await);
|
||||
assert_eq!(
|
||||
account_position(solana, liqee, collateral_token.bank).await,
|
||||
100 - 10
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -60,6 +60,8 @@ async fn test_reduce_only_token() -> Result<(), TransportError> {
|
|||
admin,
|
||||
group,
|
||||
mint: mints[0].pubkey,
|
||||
reduce_only: 1,
|
||||
force_close: false,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -159,6 +161,8 @@ async fn test_reduce_only_token() -> Result<(), TransportError> {
|
|||
admin,
|
||||
group,
|
||||
mint: mints[2].pubkey,
|
||||
reduce_only: 1,
|
||||
force_close: false,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -1072,6 +1072,7 @@ fn token_edit_instruction_default() -> mango_v4::instruction::TokenEdit {
|
|||
reset_net_borrow_limit: false,
|
||||
reduce_only_opt: None,
|
||||
name_opt: None,
|
||||
force_close_opt: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1259,6 +1260,8 @@ pub struct TokenMakeReduceOnly {
|
|||
pub group: Pubkey,
|
||||
pub admin: TestKeypair,
|
||||
pub mint: Pubkey,
|
||||
pub reduce_only: u8,
|
||||
pub force_close: bool,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
|
@ -1283,7 +1286,8 @@ impl ClientInstruction for TokenMakeReduceOnly {
|
|||
let mint_info: MintInfo = account_loader.load(&mint_info_key).await.unwrap();
|
||||
|
||||
let instruction = Self::Instruction {
|
||||
reduce_only_opt: Some(true),
|
||||
reduce_only_opt: Some(self.reduce_only),
|
||||
force_close_opt: Some(self.force_close),
|
||||
..token_edit_instruction_default()
|
||||
};
|
||||
|
||||
|
@ -2595,6 +2599,69 @@ impl ClientInstruction for Serum3LiqForceCancelOrdersInstruction {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct TokenForceCloseBorrowsWithTokenInstruction {
|
||||
pub liqee: Pubkey,
|
||||
pub liqor: Pubkey,
|
||||
pub liqor_owner: TestKeypair,
|
||||
|
||||
pub asset_token_index: TokenIndex,
|
||||
pub asset_bank_index: usize,
|
||||
pub liab_token_index: TokenIndex,
|
||||
pub liab_bank_index: usize,
|
||||
pub max_liab_transfer: u64,
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ClientInstruction for TokenForceCloseBorrowsWithTokenInstruction {
|
||||
type Accounts = mango_v4::accounts::TokenForceCloseBorrowsWithToken;
|
||||
type Instruction = mango_v4::instruction::TokenForceCloseBorrowsWithToken;
|
||||
async fn to_instruction(
|
||||
&self,
|
||||
account_loader: impl ClientAccountLoader + 'async_trait,
|
||||
) -> (Self::Accounts, instruction::Instruction) {
|
||||
let program_id = mango_v4::id();
|
||||
let instruction = Self::Instruction {
|
||||
asset_token_index: self.asset_token_index,
|
||||
liab_token_index: self.liab_token_index,
|
||||
max_liab_transfer: self.max_liab_transfer,
|
||||
};
|
||||
|
||||
let liqee = account_loader
|
||||
.load_mango_account(&self.liqee)
|
||||
.await
|
||||
.unwrap();
|
||||
let liqor = account_loader
|
||||
.load_mango_account(&self.liqor)
|
||||
.await
|
||||
.unwrap();
|
||||
let health_check_metas = derive_liquidation_remaining_account_metas(
|
||||
&account_loader,
|
||||
&liqee,
|
||||
&liqor,
|
||||
self.asset_token_index,
|
||||
self.asset_bank_index,
|
||||
self.liab_token_index,
|
||||
self.liab_bank_index,
|
||||
)
|
||||
.await;
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: liqee.fixed.group,
|
||||
liqee: self.liqee,
|
||||
liqor: self.liqor,
|
||||
liqor_owner: self.liqor_owner.pubkey(),
|
||||
};
|
||||
|
||||
let mut instruction = make_instruction(program_id, &accounts, instruction);
|
||||
instruction.accounts.extend(health_check_metas.into_iter());
|
||||
|
||||
(accounts, instruction)
|
||||
}
|
||||
|
||||
fn signers(&self) -> Vec<TestKeypair> {
|
||||
vec![self.liqor_owner]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TokenLiqWithTokenInstruction {
|
||||
pub liqee: Pubkey,
|
||||
pub liqor: Pubkey,
|
||||
|
|
|
@ -139,6 +139,7 @@ async function tokenEdit(): Promise<void> {
|
|||
params.resetNetBorrowLimit ?? false,
|
||||
params.reduceOnly,
|
||||
params.name,
|
||||
params.forceClose,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
AddressLookupTableAccount,
|
||||
Cluster,
|
||||
Commitment,
|
||||
Connection,
|
||||
Keypair,
|
||||
MemcmpFilter,
|
||||
PublicKey,
|
||||
|
@ -17,7 +18,6 @@ import {
|
|||
SystemProgram,
|
||||
TransactionInstruction,
|
||||
TransactionSignature,
|
||||
Connection,
|
||||
} from '@solana/web3.js';
|
||||
import bs58 from 'bs58';
|
||||
import { Bank, MintInfo, TokenIndex } from './accounts/bank';
|
||||
|
@ -398,6 +398,7 @@ export class MangoClient {
|
|||
params.resetNetBorrowLimit ?? false,
|
||||
params.reduceOnly,
|
||||
params.name,
|
||||
params.forceClose,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
|
|
@ -24,8 +24,9 @@ export interface TokenEditParams {
|
|||
depositWeightScaleStartQuote: number | null;
|
||||
resetStablePrice: boolean | null;
|
||||
resetNetBorrowLimit: boolean | null;
|
||||
reduceOnly: boolean | null;
|
||||
reduceOnly: number | null;
|
||||
name: string | null;
|
||||
forceClose: boolean | null;
|
||||
}
|
||||
|
||||
export const NullTokenEditParams: TokenEditParams = {
|
||||
|
@ -52,6 +53,7 @@ export const NullTokenEditParams: TokenEditParams = {
|
|||
resetNetBorrowLimit: null,
|
||||
reduceOnly: null,
|
||||
name: null,
|
||||
forceClose: null,
|
||||
};
|
||||
|
||||
export interface PerpEditParams {
|
||||
|
@ -172,6 +174,7 @@ export interface IxGateParams {
|
|||
TokenUpdateIndexAndRate: boolean;
|
||||
TokenWithdraw: boolean;
|
||||
AccountBuybackFeesWithMngo: boolean;
|
||||
TokenForceCloseBorrowsWithToken: boolean;
|
||||
}
|
||||
|
||||
// Default with all ixs enabled, use with buildIxGate
|
||||
|
@ -228,6 +231,7 @@ export const TrueIxGateParams: IxGateParams = {
|
|||
TokenUpdateIndexAndRate: true,
|
||||
TokenWithdraw: true,
|
||||
AccountBuybackFeesWithMngo: true,
|
||||
TokenForceCloseBorrowsWithToken: true,
|
||||
};
|
||||
|
||||
// build ix gate e.g. buildIxGate(Builder(TrueIxGateParams).TokenDeposit(false).build()).toNumber(),
|
||||
|
@ -294,6 +298,7 @@ export function buildIxGate(p: IxGateParams): BN {
|
|||
toggleIx(ixGate, p, 'TokenUpdateIndexAndRate', 46);
|
||||
toggleIx(ixGate, p, 'TokenWithdraw', 47);
|
||||
toggleIx(ixGate, p, 'AccountBuybackFeesWithMngo', 48);
|
||||
toggleIx(ixGate, p, 'TokenForceCloseBorrowsWithToken', 49);
|
||||
|
||||
return ixGate;
|
||||
}
|
||||
|
|
|
@ -715,7 +715,7 @@ export type MangoV4 = {
|
|||
{
|
||||
"name": "reduceOnlyOpt",
|
||||
"type": {
|
||||
"option": "bool"
|
||||
"option": "u8"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -723,6 +723,12 @@ export type MangoV4 = {
|
|||
"type": {
|
||||
"option": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "forceCloseOpt",
|
||||
"type": {
|
||||
"option": "bool"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -2570,6 +2576,45 @@ export type MangoV4 = {
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tokenForceCloseBorrowsWithToken",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "liqor",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "liqorOwner",
|
||||
"isMut": false,
|
||||
"isSigner": true
|
||||
},
|
||||
{
|
||||
"name": "liqee",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "assetTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "liabTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "maxLiabTransfer",
|
||||
"type": "u64"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tokenLiqBankruptcy",
|
||||
"accounts": [
|
||||
|
@ -4125,12 +4170,16 @@ export type MangoV4 = {
|
|||
"name": "reduceOnly",
|
||||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "forceClose",
|
||||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
2119
|
||||
2118
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -6981,6 +7030,9 @@ export type MangoV4 = {
|
|||
},
|
||||
{
|
||||
"name": "AccountBuybackFeesWithMngo"
|
||||
},
|
||||
{
|
||||
"name": "TokenForceCloseBorrowsWithToken"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -8684,6 +8736,11 @@ export type MangoV4 = {
|
|||
"code": 6045,
|
||||
"name": "HealthRegionBadInnerInstruction",
|
||||
"msg": "HealthRegions allow only specific instructions between Begin and End"
|
||||
},
|
||||
{
|
||||
"code": 6046,
|
||||
"name": "TokenInForceClose",
|
||||
"msg": "token is in force close"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -9405,7 +9462,7 @@ export const IDL: MangoV4 = {
|
|||
{
|
||||
"name": "reduceOnlyOpt",
|
||||
"type": {
|
||||
"option": "bool"
|
||||
"option": "u8"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -9413,6 +9470,12 @@ export const IDL: MangoV4 = {
|
|||
"type": {
|
||||
"option": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "forceCloseOpt",
|
||||
"type": {
|
||||
"option": "bool"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -11260,6 +11323,45 @@ export const IDL: MangoV4 = {
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tokenForceCloseBorrowsWithToken",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "liqor",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "liqorOwner",
|
||||
"isMut": false,
|
||||
"isSigner": true
|
||||
},
|
||||
{
|
||||
"name": "liqee",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "assetTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "liabTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "maxLiabTransfer",
|
||||
"type": "u64"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tokenLiqBankruptcy",
|
||||
"accounts": [
|
||||
|
@ -12815,12 +12917,16 @@ export const IDL: MangoV4 = {
|
|||
"name": "reduceOnly",
|
||||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "forceClose",
|
||||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
2119
|
||||
2118
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -15671,6 +15777,9 @@ export const IDL: MangoV4 = {
|
|||
},
|
||||
{
|
||||
"name": "AccountBuybackFeesWithMngo"
|
||||
},
|
||||
{
|
||||
"name": "TokenForceCloseBorrowsWithToken"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -17374,6 +17483,11 @@ export const IDL: MangoV4 = {
|
|||
"code": 6045,
|
||||
"name": "HealthRegionBadInnerInstruction",
|
||||
"msg": "HealthRegions allow only specific instructions between Begin and End"
|
||||
},
|
||||
{
|
||||
"code": 6046,
|
||||
"name": "TokenInForceClose",
|
||||
"msg": "token is in force close"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue