Allow the insurance fund to be for any bank (#946)
Co-authored-by: microwavedcola1 <microwavedcola@gmail.com>
(cherry picked from commit ec2d10af6e
)
This commit is contained in:
parent
e6c9a3864d
commit
98ed7eff14
|
@ -24,7 +24,7 @@ use mango_v4::accounts_zerocopy::KeyedAccountSharedData;
|
||||||
use mango_v4::health::HealthCache;
|
use mango_v4::health::HealthCache;
|
||||||
use mango_v4::state::{
|
use mango_v4::state::{
|
||||||
Bank, Group, MangoAccountValue, OracleAccountInfos, PerpMarket, PerpMarketIndex,
|
Bank, Group, MangoAccountValue, OracleAccountInfos, PerpMarket, PerpMarketIndex,
|
||||||
PlaceOrderType, SelfTradeBehavior, Serum3MarketIndex, Side, TokenIndex, INSURANCE_TOKEN_INDEX,
|
PlaceOrderType, SelfTradeBehavior, Serum3MarketIndex, Side, TokenIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::confirm_transaction::{wait_for_transaction_confirmation, RpcConfirmTransactionConfig};
|
use crate::confirm_transaction::{wait_for_transaction_confirmation, RpcConfirmTransactionConfig};
|
||||||
|
@ -1763,13 +1763,13 @@ impl MangoClient {
|
||||||
let mango_account = &self.mango_account().await?;
|
let mango_account = &self.mango_account().await?;
|
||||||
let perp = self.context.perp(market_index);
|
let perp = self.context.perp(market_index);
|
||||||
let settle_token_info = self.context.token(perp.settle_token_index);
|
let settle_token_info = self.context.token(perp.settle_token_index);
|
||||||
let insurance_token_info = self.context.token(INSURANCE_TOKEN_INDEX);
|
let insurance_token_info = self.context.token_by_mint(&group.insurance_mint)?;
|
||||||
|
|
||||||
let (health_remaining_ams, health_cu) = self
|
let (health_remaining_ams, health_cu) = self
|
||||||
.derive_health_check_remaining_account_metas_two_accounts(
|
.derive_health_check_remaining_account_metas_two_accounts(
|
||||||
mango_account,
|
mango_account,
|
||||||
liqee.1,
|
liqee.1,
|
||||||
&[INSURANCE_TOKEN_INDEX],
|
&[insurance_token_info.token_index],
|
||||||
&[],
|
&[],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -1917,10 +1917,15 @@ impl MangoClient {
|
||||||
liab_token_index: TokenIndex,
|
liab_token_index: TokenIndex,
|
||||||
max_liab_transfer: I80F48,
|
max_liab_transfer: I80F48,
|
||||||
) -> anyhow::Result<PreparedInstructions> {
|
) -> anyhow::Result<PreparedInstructions> {
|
||||||
let mango_account = &self.mango_account().await?;
|
let group = account_fetcher_fetch_anchor_account::<Group>(
|
||||||
let quote_token_index = 0;
|
&*self.account_fetcher,
|
||||||
|
&self.context.group,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let quote_info = self.context.token(quote_token_index);
|
let mango_account = &self.mango_account().await?;
|
||||||
|
|
||||||
|
let insurance_info = self.context.token_by_mint(&group.insurance_mint)?;
|
||||||
let liab_info = self.context.token(liab_token_index);
|
let liab_info = self.context.token(liab_token_index);
|
||||||
|
|
||||||
let bank_remaining_ams = liab_info
|
let bank_remaining_ams = liab_info
|
||||||
|
@ -1933,8 +1938,8 @@ impl MangoClient {
|
||||||
.derive_health_check_remaining_account_metas_two_accounts(
|
.derive_health_check_remaining_account_metas_two_accounts(
|
||||||
mango_account,
|
mango_account,
|
||||||
liqee.1,
|
liqee.1,
|
||||||
&[INSURANCE_TOKEN_INDEX],
|
&[insurance_info.token_index],
|
||||||
&[quote_token_index, liab_token_index],
|
&[insurance_info.token_index, liab_token_index],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -1955,7 +1960,7 @@ impl MangoClient {
|
||||||
liqor: self.mango_account_address,
|
liqor: self.mango_account_address,
|
||||||
liqor_owner: self.authority(),
|
liqor_owner: self.authority(),
|
||||||
liab_mint_info: liab_info.mint_info_address,
|
liab_mint_info: liab_info.mint_info_address,
|
||||||
quote_vault: quote_info.first_vault(),
|
quote_vault: insurance_info.first_vault(),
|
||||||
insurance_vault: group.insurance_vault,
|
insurance_vault: group.insurance_vault,
|
||||||
token_program: Token::id(),
|
token_program: Token::id(),
|
||||||
},
|
},
|
||||||
|
|
|
@ -326,6 +326,86 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "groupChangeInsuranceFund",
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "group",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"insurance_vault",
|
||||||
|
"admin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "admin",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "insuranceVault",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "withdrawDestination",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newInsuranceMint",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newInsuranceVault",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"pda": {
|
||||||
|
"seeds": [
|
||||||
|
{
|
||||||
|
"kind": "const",
|
||||||
|
"type": "string",
|
||||||
|
"value": "InsuranceVault"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "account",
|
||||||
|
"type": "publicKey",
|
||||||
|
"path": "group"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "account",
|
||||||
|
"type": "publicKey",
|
||||||
|
"account": "Mint",
|
||||||
|
"path": "new_insurance_mint"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "payer",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tokenProgram",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "systemProgram",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rent",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "ixGateSet",
|
"name": "ixGateSet",
|
||||||
"accounts": [
|
"accounts": [
|
||||||
|
@ -11120,6 +11200,9 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "HealthCheck"
|
"name": "HealthCheck"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GroupChangeInsuranceFund"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
use crate::{error::MangoError, state::*};
|
||||||
|
use anchor_lang::prelude::*;
|
||||||
|
use anchor_spl::token::{self, Mint, Token, TokenAccount};
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct GroupChangeInsuranceFund<'info> {
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
has_one = insurance_vault,
|
||||||
|
has_one = admin,
|
||||||
|
constraint = group.load()?.is_ix_enabled(IxGate::GroupChangeInsuranceFund) @ MangoError::IxIsDisabled,
|
||||||
|
)]
|
||||||
|
pub group: AccountLoader<'info, Group>,
|
||||||
|
pub admin: Signer<'info>,
|
||||||
|
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
close = payer,
|
||||||
|
)]
|
||||||
|
pub insurance_vault: Account<'info, TokenAccount>,
|
||||||
|
|
||||||
|
#[account(mut)]
|
||||||
|
pub withdraw_destination: Account<'info, TokenAccount>,
|
||||||
|
|
||||||
|
pub new_insurance_mint: Account<'info, Mint>,
|
||||||
|
|
||||||
|
#[account(
|
||||||
|
init,
|
||||||
|
seeds = [b"InsuranceVault".as_ref(), group.key().as_ref(), new_insurance_mint.key().as_ref()],
|
||||||
|
bump,
|
||||||
|
token::authority = group,
|
||||||
|
token::mint = new_insurance_mint,
|
||||||
|
payer = payer
|
||||||
|
)]
|
||||||
|
pub new_insurance_vault: Account<'info, TokenAccount>,
|
||||||
|
|
||||||
|
#[account(mut)]
|
||||||
|
pub payer: Signer<'info>,
|
||||||
|
|
||||||
|
pub token_program: Program<'info, Token>,
|
||||||
|
pub system_program: Program<'info, System>,
|
||||||
|
pub rent: Sysvar<'info, Rent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'info> GroupChangeInsuranceFund<'info> {
|
||||||
|
pub fn transfer_ctx(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> {
|
||||||
|
let program = self.token_program.to_account_info();
|
||||||
|
let accounts = token::Transfer {
|
||||||
|
from: self.insurance_vault.to_account_info(),
|
||||||
|
to: self.withdraw_destination.to_account_info(),
|
||||||
|
authority: self.group.to_account_info(),
|
||||||
|
};
|
||||||
|
CpiContext::new(program, accounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close_ctx(&self) -> CpiContext<'_, '_, '_, 'info, token::CloseAccount<'info>> {
|
||||||
|
CpiContext::new(
|
||||||
|
self.token_program.to_account_info(),
|
||||||
|
token::CloseAccount {
|
||||||
|
account: self.insurance_vault.to_account_info(),
|
||||||
|
destination: self.payer.to_account_info(),
|
||||||
|
authority: self.group.to_account_info(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ pub use alt_set::*;
|
||||||
pub use benchmark::*;
|
pub use benchmark::*;
|
||||||
pub use compute_account_data::*;
|
pub use compute_account_data::*;
|
||||||
pub use flash_loan::*;
|
pub use flash_loan::*;
|
||||||
|
pub use group_change_insurance_fund::*;
|
||||||
pub use group_close::*;
|
pub use group_close::*;
|
||||||
pub use group_create::*;
|
pub use group_create::*;
|
||||||
pub use group_edit::*;
|
pub use group_edit::*;
|
||||||
|
@ -92,6 +93,7 @@ mod alt_set;
|
||||||
mod benchmark;
|
mod benchmark;
|
||||||
mod compute_account_data;
|
mod compute_account_data;
|
||||||
mod flash_loan;
|
mod flash_loan;
|
||||||
|
mod group_change_insurance_fund;
|
||||||
mod group_close;
|
mod group_close;
|
||||||
mod group_create;
|
mod group_create;
|
||||||
mod group_edit;
|
mod group_edit;
|
||||||
|
|
|
@ -115,7 +115,7 @@ pub struct PerpLiqNegativePnlOrBankruptcyV2<'info> {
|
||||||
#[account(
|
#[account(
|
||||||
mut,
|
mut,
|
||||||
has_one = group,
|
has_one = group,
|
||||||
constraint = insurance_bank.load()?.token_index == INSURANCE_TOKEN_INDEX
|
constraint = insurance_bank.load()?.mint == insurance_vault.mint,
|
||||||
)]
|
)]
|
||||||
pub insurance_bank: AccountLoader<'info, Bank>,
|
pub insurance_bank: AccountLoader<'info, Bank>,
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::state::*;
|
||||||
|
|
||||||
// Remaining accounts:
|
// Remaining accounts:
|
||||||
// - all banks for liab_mint_info (writable)
|
// - all banks for liab_mint_info (writable)
|
||||||
// - merged health accounts for liqor+liqee
|
// - merged health accounts for liqor + liqee, including the bank for the insurance token
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct TokenLiqBankruptcy<'info> {
|
pub struct TokenLiqBankruptcy<'info> {
|
||||||
#[account(
|
#[account(
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
use anchor_lang::prelude::*;
|
||||||
|
use anchor_spl::token;
|
||||||
|
|
||||||
|
use crate::{accounts_ix::GroupChangeInsuranceFund, group_seeds};
|
||||||
|
|
||||||
|
pub fn group_change_insurance_fund(ctx: Context<GroupChangeInsuranceFund>) -> Result<()> {
|
||||||
|
{
|
||||||
|
let group = ctx.accounts.group.load()?;
|
||||||
|
let group_seeds = group_seeds!(group);
|
||||||
|
token::transfer(
|
||||||
|
ctx.accounts.transfer_ctx().with_signer(&[group_seeds]),
|
||||||
|
ctx.accounts.insurance_vault.amount,
|
||||||
|
)?;
|
||||||
|
token::close_account(ctx.accounts.close_ctx().with_signer(&[group_seeds]))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut group = ctx.accounts.group.load_mut()?;
|
||||||
|
group.insurance_vault = ctx.accounts.new_insurance_vault.key();
|
||||||
|
group.insurance_mint = ctx.accounts.new_insurance_mint.key();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -98,6 +98,7 @@ pub fn ix_gate_set(ctx: Context<IxGateSet>, ix_gate: u128) -> Result<()> {
|
||||||
log_if_changed(&group, ix_gate, IxGate::TokenForceWithdraw);
|
log_if_changed(&group, ix_gate, IxGate::TokenForceWithdraw);
|
||||||
log_if_changed(&group, ix_gate, IxGate::SequenceCheck);
|
log_if_changed(&group, ix_gate, IxGate::SequenceCheck);
|
||||||
log_if_changed(&group, ix_gate, IxGate::HealthCheck);
|
log_if_changed(&group, ix_gate, IxGate::HealthCheck);
|
||||||
|
log_if_changed(&group, ix_gate, IxGate::GroupChangeInsuranceFund);
|
||||||
|
|
||||||
group.ix_gate = ix_gate;
|
group.ix_gate = ix_gate;
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ pub use alt_set::*;
|
||||||
pub use benchmark::*;
|
pub use benchmark::*;
|
||||||
pub use compute_account_data::*;
|
pub use compute_account_data::*;
|
||||||
pub use flash_loan::*;
|
pub use flash_loan::*;
|
||||||
|
pub use group_change_insurance_fund::*;
|
||||||
pub use group_close::*;
|
pub use group_close::*;
|
||||||
pub use group_create::*;
|
pub use group_create::*;
|
||||||
pub use group_edit::*;
|
pub use group_edit::*;
|
||||||
|
@ -83,6 +84,7 @@ mod alt_set;
|
||||||
mod benchmark;
|
mod benchmark;
|
||||||
mod compute_account_data;
|
mod compute_account_data;
|
||||||
mod flash_loan;
|
mod flash_loan;
|
||||||
|
mod group_change_insurance_fund;
|
||||||
mod group_close;
|
mod group_close;
|
||||||
mod group_create;
|
mod group_create;
|
||||||
mod group_edit;
|
mod group_edit;
|
||||||
|
|
|
@ -26,6 +26,23 @@ pub fn token_liq_bankruptcy(
|
||||||
let (bank_ais, health_ais) = &ctx.remaining_accounts.split_at(liab_mint_info.num_banks());
|
let (bank_ais, health_ais) = &ctx.remaining_accounts.split_at(liab_mint_info.num_banks());
|
||||||
liab_mint_info.verify_banks_ais(bank_ais)?;
|
liab_mint_info.verify_banks_ais(bank_ais)?;
|
||||||
|
|
||||||
|
// find the insurance bank token index
|
||||||
|
let insurance_mint = ctx.accounts.insurance_vault.mint;
|
||||||
|
let insurance_token_index = health_ais
|
||||||
|
.iter()
|
||||||
|
.find_map(|ai| {
|
||||||
|
ai.load::<Bank>()
|
||||||
|
.and_then(|b| {
|
||||||
|
if b.mint == insurance_mint {
|
||||||
|
Ok(b.token_index)
|
||||||
|
} else {
|
||||||
|
Err(MangoError::InvalidBank.into())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
.ok_or_else(|| error_msg!("could not find bank for insurance mint in health accounts"))?;
|
||||||
|
|
||||||
require_keys_neq!(ctx.accounts.liqor.key(), ctx.accounts.liqee.key());
|
require_keys_neq!(ctx.accounts.liqor.key(), ctx.accounts.liqee.key());
|
||||||
|
|
||||||
let mut liqor = ctx.accounts.liqor.load_full_mut()?;
|
let mut liqor = ctx.accounts.liqor.load_full_mut()?;
|
||||||
|
@ -51,10 +68,10 @@ pub fn token_liq_bankruptcy(
|
||||||
liqee_health_cache.require_after_phase2_liquidation()?;
|
liqee_health_cache.require_after_phase2_liquidation()?;
|
||||||
liqee.fixed.set_being_liquidated(true);
|
liqee.fixed.set_being_liquidated(true);
|
||||||
|
|
||||||
let liab_is_insurance_token = liab_token_index == INSURANCE_TOKEN_INDEX;
|
let liab_is_insurance_token = liab_token_index == insurance_token_index;
|
||||||
let (liab_bank, liab_oracle_price, opt_quote_bank_and_price) =
|
let (liab_bank, liab_oracle_price, opt_insurance_bank_and_price) =
|
||||||
account_retriever.banks_mut_and_oracles(liab_token_index, INSURANCE_TOKEN_INDEX)?;
|
account_retriever.banks_mut_and_oracles(liab_token_index, insurance_token_index)?;
|
||||||
assert!(liab_is_insurance_token == opt_quote_bank_and_price.is_none());
|
assert!(liab_is_insurance_token == opt_insurance_bank_and_price.is_none());
|
||||||
|
|
||||||
let mut liab_deposit_index = liab_bank.deposit_index;
|
let mut liab_deposit_index = liab_bank.deposit_index;
|
||||||
let liab_borrow_index = liab_bank.borrow_index;
|
let liab_borrow_index = liab_bank.borrow_index;
|
||||||
|
@ -76,11 +93,12 @@ pub fn token_liq_bankruptcy(
|
||||||
// guaranteed positive
|
// guaranteed positive
|
||||||
let mut remaining_liab_loss = (-initial_liab_native).min(-liqee_liab_health_balance);
|
let mut remaining_liab_loss = (-initial_liab_native).min(-liqee_liab_health_balance);
|
||||||
|
|
||||||
// We pay for the liab token in quote. Example: SOL is at $20 and USDC is at $2, then for a liab
|
// We pay for the liab token in insurance token.
|
||||||
|
// Example: SOL is at $20 and USDC is at $2, then for a liab
|
||||||
// of 3 SOL, we'd pay 3 * 20 / 2 * (1+fee) = 30 * (1+fee) USDC.
|
// of 3 SOL, we'd pay 3 * 20 / 2 * (1+fee) = 30 * (1+fee) USDC.
|
||||||
let liab_to_quote_with_fee =
|
let liab_to_insurance_with_fee =
|
||||||
if let Some((_quote_bank, quote_price)) = opt_quote_bank_and_price.as_ref() {
|
if let Some((_insurance_bank, insurance_price)) = opt_insurance_bank_and_price.as_ref() {
|
||||||
liab_oracle_price * (I80F48::ONE + liab_bank.liquidation_fee) / quote_price
|
liab_oracle_price * (I80F48::ONE + liab_bank.liquidation_fee) / insurance_price
|
||||||
} else {
|
} else {
|
||||||
I80F48::ONE
|
I80F48::ONE
|
||||||
};
|
};
|
||||||
|
@ -93,7 +111,7 @@ pub fn token_liq_bankruptcy(
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
||||||
let insurance_transfer = (liab_transfer_unrounded * liab_to_quote_with_fee)
|
let insurance_transfer = (liab_transfer_unrounded * liab_to_insurance_with_fee)
|
||||||
.ceil()
|
.ceil()
|
||||||
.to_num::<u64>()
|
.to_num::<u64>()
|
||||||
.min(insurance_vault_amount);
|
.min(insurance_vault_amount);
|
||||||
|
@ -105,7 +123,7 @@ pub fn token_liq_bankruptcy(
|
||||||
// AUDIT: v3 does this, but it seems bad, because it can make liab_transfer
|
// AUDIT: v3 does this, but it seems bad, because it can make liab_transfer
|
||||||
// exceed max_liab_transfer due to the ceil() above! Otoh, not doing it would allow
|
// exceed max_liab_transfer due to the ceil() above! Otoh, not doing it would allow
|
||||||
// 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 = insurance_transfer_i80f48 / liab_to_quote_with_fee;
|
let liab_transfer = insurance_transfer_i80f48 / liab_to_insurance_with_fee;
|
||||||
|
|
||||||
let mut liqee_liab_active = true;
|
let mut liqee_liab_active = true;
|
||||||
if insurance_transfer > 0 {
|
if insurance_transfer > 0 {
|
||||||
|
@ -115,36 +133,36 @@ pub fn token_liq_bankruptcy(
|
||||||
// update correctly even if dusting happened
|
// update correctly even if dusting happened
|
||||||
remaining_liab_loss -= liqee_liab.native(liab_bank) - initial_liab_native;
|
remaining_liab_loss -= liqee_liab.native(liab_bank) - initial_liab_native;
|
||||||
|
|
||||||
// move insurance assets into quote bank
|
// move insurance assets into insurance bank
|
||||||
let group_seeds = group_seeds!(group);
|
let group_seeds = group_seeds!(group);
|
||||||
token::transfer(
|
token::transfer(
|
||||||
ctx.accounts.transfer_ctx().with_signer(&[group_seeds]),
|
ctx.accounts.transfer_ctx().with_signer(&[group_seeds]),
|
||||||
insurance_transfer,
|
insurance_transfer,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// move quote assets into liqor and withdraw liab assets
|
// move insurance assets into liqor and withdraw liab assets
|
||||||
if let Some((quote_bank, _)) = opt_quote_bank_and_price {
|
if let Some((insurance_bank, _)) = opt_insurance_bank_and_price {
|
||||||
// account constraint #2 a)
|
// account constraint #2 a)
|
||||||
require_keys_eq!(quote_bank.vault, ctx.accounts.quote_vault.key());
|
require_keys_eq!(insurance_bank.vault, ctx.accounts.quote_vault.key());
|
||||||
require_keys_eq!(quote_bank.mint, ctx.accounts.insurance_vault.mint);
|
require_keys_eq!(insurance_bank.mint, ctx.accounts.insurance_vault.mint);
|
||||||
|
|
||||||
let quote_deposit_index = quote_bank.deposit_index;
|
let insurance_deposit_index = insurance_bank.deposit_index;
|
||||||
let quote_borrow_index = quote_bank.borrow_index;
|
let insurance_borrow_index = insurance_bank.borrow_index;
|
||||||
|
|
||||||
// credit the liqor
|
// credit the liqor
|
||||||
let (liqor_quote, liqor_quote_raw_token_index, _) =
|
let (liqor_insurance, liqor_insurance_raw_token_index, _) =
|
||||||
liqor.ensure_token_position(INSURANCE_TOKEN_INDEX)?;
|
liqor.ensure_token_position(insurance_token_index)?;
|
||||||
let liqor_quote_active =
|
let liqor_insurance_active =
|
||||||
quote_bank.deposit(liqor_quote, insurance_transfer_i80f48, now_ts)?;
|
insurance_bank.deposit(liqor_insurance, insurance_transfer_i80f48, now_ts)?;
|
||||||
|
|
||||||
// liqor quote
|
// liqor insurance
|
||||||
emit_stack(TokenBalanceLog {
|
emit_stack(TokenBalanceLog {
|
||||||
mango_group: ctx.accounts.group.key(),
|
mango_group: ctx.accounts.group.key(),
|
||||||
mango_account: ctx.accounts.liqor.key(),
|
mango_account: ctx.accounts.liqor.key(),
|
||||||
token_index: INSURANCE_TOKEN_INDEX,
|
token_index: insurance_token_index,
|
||||||
indexed_position: liqor_quote.indexed_position.to_bits(),
|
indexed_position: liqor_insurance.indexed_position.to_bits(),
|
||||||
deposit_index: quote_deposit_index.to_bits(),
|
deposit_index: insurance_deposit_index.to_bits(),
|
||||||
borrow_index: quote_borrow_index.to_bits(),
|
borrow_index: insurance_borrow_index.to_bits(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// transfer liab from liqee to liqor
|
// transfer liab from liqee to liqor
|
||||||
|
@ -189,9 +207,9 @@ pub fn token_liq_bankruptcy(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if !liqor_quote_active {
|
if !liqor_insurance_active {
|
||||||
liqor.deactivate_token_position_and_log(
|
liqor.deactivate_token_position_and_log(
|
||||||
liqor_quote_raw_token_index,
|
liqor_insurance_raw_token_index,
|
||||||
ctx.accounts.liqor.key(),
|
ctx.accounts.liqor.key(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -202,12 +220,12 @@ pub fn token_liq_bankruptcy(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For liab_token_index == INSURANCE_TOKEN_INDEX: the insurance fund deposits directly into liqee,
|
// For liab_token_index == insurance_token_index: the insurance fund deposits directly into liqee,
|
||||||
// without a fee or the liqor being involved
|
// without a fee or the liqor being involved
|
||||||
// account constraint #2 b)
|
// account constraint #2 b)
|
||||||
require_keys_eq!(liab_bank.vault, ctx.accounts.quote_vault.key());
|
require_keys_eq!(liab_bank.vault, ctx.accounts.quote_vault.key());
|
||||||
require_eq!(liab_token_index, INSURANCE_TOKEN_INDEX);
|
require_eq!(liab_token_index, insurance_token_index);
|
||||||
require_eq!(liab_to_quote_with_fee, I80F48::ONE);
|
require_eq!(liab_to_insurance_with_fee, I80F48::ONE);
|
||||||
require_eq!(insurance_transfer_i80f48, liab_transfer);
|
require_eq!(insurance_transfer_i80f48, liab_transfer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -287,7 +305,7 @@ pub fn token_liq_bankruptcy(
|
||||||
liab_token_index,
|
liab_token_index,
|
||||||
initial_liab_native: initial_liab_native.to_bits(),
|
initial_liab_native: initial_liab_native.to_bits(),
|
||||||
liab_price: liab_oracle_price.to_bits(),
|
liab_price: liab_oracle_price.to_bits(),
|
||||||
insurance_token_index: INSURANCE_TOKEN_INDEX,
|
insurance_token_index,
|
||||||
insurance_transfer: insurance_transfer_i80f48.to_bits(),
|
insurance_transfer: insurance_transfer_i80f48.to_bits(),
|
||||||
socialized_loss: socialized_loss.to_bits(),
|
socialized_loss: socialized_loss.to_bits(),
|
||||||
starting_liab_deposit_index: starting_deposit_index.to_bits(),
|
starting_liab_deposit_index: starting_deposit_index.to_bits(),
|
||||||
|
|
|
@ -47,13 +47,6 @@ pub fn token_register(
|
||||||
disable_asset_liquidation: bool,
|
disable_asset_liquidation: bool,
|
||||||
collateral_fee_per_day: f32,
|
collateral_fee_per_day: f32,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Require token 0 to be in the insurance token
|
|
||||||
if token_index == INSURANCE_TOKEN_INDEX {
|
|
||||||
require_keys_eq!(
|
|
||||||
ctx.accounts.group.load()?.insurance_mint,
|
|
||||||
ctx.accounts.mint.key()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
require_neq!(token_index, TokenIndex::MAX);
|
require_neq!(token_index, TokenIndex::MAX);
|
||||||
|
|
||||||
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
|
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
|
||||||
|
|
|
@ -115,6 +115,12 @@ pub mod mango_v4 {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn group_change_insurance_fund(ctx: Context<GroupChangeInsuranceFund>) -> Result<()> {
|
||||||
|
#[cfg(feature = "enable-gpl")]
|
||||||
|
instructions::group_change_insurance_fund(ctx)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ix_gate_set(ctx: Context<IxGateSet>, ix_gate: u128) -> Result<()> {
|
pub fn ix_gate_set(ctx: Context<IxGateSet>, ix_gate: u128) -> Result<()> {
|
||||||
#[cfg(feature = "enable-gpl")]
|
#[cfg(feature = "enable-gpl")]
|
||||||
instructions::ix_gate_set(ctx, ix_gate)?;
|
instructions::ix_gate_set(ctx, ix_gate)?;
|
||||||
|
|
|
@ -248,6 +248,7 @@ pub enum IxGate {
|
||||||
TokenForceWithdraw = 72,
|
TokenForceWithdraw = 72,
|
||||||
SequenceCheck = 73,
|
SequenceCheck = 73,
|
||||||
HealthCheck = 74,
|
HealthCheck = 74,
|
||||||
|
GroupChangeInsuranceFund = 76,
|
||||||
// NOTE: Adding new variants requires matching changes in ts and the ix_gate_set instruction.
|
// NOTE: Adding new variants requires matching changes in ts and the ix_gate_set instruction.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -320,36 +320,18 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// deposit some funds, to the vaults aren't empty
|
// deposit some funds, to the vaults aren't empty
|
||||||
let vault_account = send_tx(
|
|
||||||
solana,
|
|
||||||
AccountCreateInstruction {
|
|
||||||
account_num: 2,
|
|
||||||
group,
|
|
||||||
owner,
|
|
||||||
payer,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.account;
|
|
||||||
let vault_amount = 100000;
|
let vault_amount = 100000;
|
||||||
for &token_account in payer_mint_accounts {
|
let vault_account = create_funded_account(
|
||||||
send_tx(
|
&solana,
|
||||||
solana,
|
group,
|
||||||
TokenDepositInstruction {
|
owner,
|
||||||
amount: vault_amount,
|
2,
|
||||||
reduce_only: false,
|
&context.users[1],
|
||||||
account: vault_account,
|
mints,
|
||||||
owner,
|
vault_amount,
|
||||||
token_account,
|
1,
|
||||||
token_authority: payer.clone(),
|
)
|
||||||
bank_index: 1,
|
.await;
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also add a tiny amount to bank0 for borrow_token1, so we can test multi-bank socialized loss.
|
// Also add a tiny amount to bank0 for borrow_token1, so we can test multi-bank socialized loss.
|
||||||
// It must be enough to not trip the borrow limits on the bank.
|
// It must be enough to not trip the borrow limits on the bank.
|
||||||
|
@ -610,3 +592,299 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_bankrupt_tokens_other_insurance_fund() -> Result<(), TransportError> {
|
||||||
|
let mut test_builder = TestContextBuilder::new();
|
||||||
|
test_builder.test().set_compute_max_units(85_000); // TokenLiqWithToken needs 84k
|
||||||
|
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,
|
||||||
|
insurance_vault,
|
||||||
|
..
|
||||||
|
} = mango_setup::GroupWithTokensConfig {
|
||||||
|
admin,
|
||||||
|
payer,
|
||||||
|
mints: mints.to_vec(),
|
||||||
|
..GroupWithTokensConfig::default()
|
||||||
|
}
|
||||||
|
.create(solana)
|
||||||
|
.await;
|
||||||
|
let borrow_token1 = &tokens[0]; // USDC
|
||||||
|
let borrow_token2 = &tokens[1];
|
||||||
|
let collateral_token1 = &tokens[2];
|
||||||
|
let collateral_token2 = &tokens[3];
|
||||||
|
let insurance_token = collateral_token2;
|
||||||
|
|
||||||
|
// fund the insurance vault
|
||||||
|
{
|
||||||
|
let mut tx = ClientTransaction::new(solana);
|
||||||
|
tx.add_instruction_direct(
|
||||||
|
spl_token::instruction::transfer(
|
||||||
|
&spl_token::ID,
|
||||||
|
&payer_mint_accounts[0],
|
||||||
|
&insurance_vault,
|
||||||
|
&payer.pubkey(),
|
||||||
|
&[&payer.pubkey()],
|
||||||
|
1051,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
tx.add_signer(payer);
|
||||||
|
tx.send().await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// TEST: switch the insurance vault mint, reclaiming the deposited tokens
|
||||||
|
//
|
||||||
|
let before_withdraw_dest = solana.token_account_balance(payer_mint_accounts[0]).await;
|
||||||
|
let insurance_vault = send_tx(
|
||||||
|
solana,
|
||||||
|
GroupChangeInsuranceFund {
|
||||||
|
group,
|
||||||
|
admin,
|
||||||
|
payer,
|
||||||
|
insurance_mint: insurance_token.mint.pubkey,
|
||||||
|
withdraw_destination: payer_mint_accounts[0],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.new_insurance_vault;
|
||||||
|
let after_withdraw_dest = solana.token_account_balance(payer_mint_accounts[0]).await;
|
||||||
|
assert_eq!(after_withdraw_dest - before_withdraw_dest, 1051);
|
||||||
|
|
||||||
|
// SETUP: Fund the new insurance vault
|
||||||
|
{
|
||||||
|
let mut tx = ClientTransaction::new(solana);
|
||||||
|
tx.add_instruction_direct(
|
||||||
|
spl_token::instruction::transfer(
|
||||||
|
&spl_token::ID,
|
||||||
|
&payer_mint_accounts[3],
|
||||||
|
&insurance_vault,
|
||||||
|
&payer.pubkey(),
|
||||||
|
&[&payer.pubkey()],
|
||||||
|
2000,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
tx.add_signer(payer);
|
||||||
|
tx.send().await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// deposit some funds, to the vaults aren't empty
|
||||||
|
let vault_amount = 100000;
|
||||||
|
let vault_account = create_funded_account(
|
||||||
|
&solana,
|
||||||
|
group,
|
||||||
|
owner,
|
||||||
|
2,
|
||||||
|
&context.users[1],
|
||||||
|
mints,
|
||||||
|
vault_amount,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
//
|
||||||
|
// SETUP: Make an account with some collateral and some borrows
|
||||||
|
//
|
||||||
|
let account = send_tx(
|
||||||
|
solana,
|
||||||
|
AccountCreateInstruction {
|
||||||
|
account_num: 0,
|
||||||
|
group,
|
||||||
|
owner,
|
||||||
|
payer,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.account;
|
||||||
|
|
||||||
|
let deposit1_amount = 20;
|
||||||
|
let deposit2_amount = 1000;
|
||||||
|
send_tx(
|
||||||
|
solana,
|
||||||
|
TokenDepositInstruction {
|
||||||
|
amount: deposit1_amount,
|
||||||
|
reduce_only: false,
|
||||||
|
account,
|
||||||
|
owner,
|
||||||
|
token_account: payer_mint_accounts[2],
|
||||||
|
token_authority: payer.clone(),
|
||||||
|
bank_index: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
send_tx(
|
||||||
|
solana,
|
||||||
|
TokenDepositInstruction {
|
||||||
|
amount: deposit2_amount,
|
||||||
|
reduce_only: false,
|
||||||
|
account,
|
||||||
|
owner,
|
||||||
|
token_account: payer_mint_accounts[3],
|
||||||
|
token_authority: payer.clone(),
|
||||||
|
bank_index: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let borrow1_amount = 50;
|
||||||
|
let borrow1_amount_bank0 = 10;
|
||||||
|
let borrow1_amount_bank1 = borrow1_amount - borrow1_amount_bank0;
|
||||||
|
let borrow2_amount = 350;
|
||||||
|
send_tx(
|
||||||
|
solana,
|
||||||
|
TokenWithdrawInstruction {
|
||||||
|
amount: borrow1_amount_bank1,
|
||||||
|
allow_borrow: true,
|
||||||
|
account,
|
||||||
|
owner,
|
||||||
|
token_account: payer_mint_accounts[0],
|
||||||
|
bank_index: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
send_tx(
|
||||||
|
solana,
|
||||||
|
TokenWithdrawInstruction {
|
||||||
|
amount: borrow1_amount_bank0,
|
||||||
|
allow_borrow: true,
|
||||||
|
account,
|
||||||
|
owner,
|
||||||
|
token_account: payer_mint_accounts[0],
|
||||||
|
bank_index: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
send_tx(
|
||||||
|
solana,
|
||||||
|
TokenWithdrawInstruction {
|
||||||
|
amount: borrow2_amount,
|
||||||
|
allow_borrow: true,
|
||||||
|
account,
|
||||||
|
owner,
|
||||||
|
token_account: payer_mint_accounts[1],
|
||||||
|
bank_index: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
//
|
||||||
|
// SETUP: Change the oracle to make health go very negative
|
||||||
|
// and change the insurance token price to verify it has an effect
|
||||||
|
//
|
||||||
|
set_bank_stub_oracle_price(solana, group, borrow_token2, admin, 20.0).await;
|
||||||
|
set_bank_stub_oracle_price(solana, group, insurance_token, admin, 1.5).await;
|
||||||
|
|
||||||
|
//
|
||||||
|
// SETUP: liquidate all the collateral against borrow2
|
||||||
|
//
|
||||||
|
|
||||||
|
// eat collateral1
|
||||||
|
send_tx(
|
||||||
|
solana,
|
||||||
|
TokenLiqWithTokenInstruction {
|
||||||
|
liqee: account,
|
||||||
|
liqor: vault_account,
|
||||||
|
liqor_owner: owner,
|
||||||
|
asset_token_index: collateral_token1.index,
|
||||||
|
asset_bank_index: 1,
|
||||||
|
liab_token_index: borrow_token2.index,
|
||||||
|
liab_bank_index: 1,
|
||||||
|
max_liab_transfer: I80F48::from_num(100000.0),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(account_position_closed(solana, account, collateral_token1.bank).await);
|
||||||
|
let liqee = get_mango_account(solana, account).await;
|
||||||
|
assert!(liqee.being_liquidated());
|
||||||
|
|
||||||
|
// eat collateral2, leaving the account bankrupt
|
||||||
|
send_tx(
|
||||||
|
solana,
|
||||||
|
TokenLiqWithTokenInstruction {
|
||||||
|
liqee: account,
|
||||||
|
liqor: vault_account,
|
||||||
|
liqor_owner: owner,
|
||||||
|
asset_token_index: collateral_token2.index,
|
||||||
|
asset_bank_index: 1,
|
||||||
|
liab_token_index: borrow_token2.index,
|
||||||
|
liab_bank_index: 1,
|
||||||
|
max_liab_transfer: I80F48::from_num(100000.0),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(account_position_closed(solana, account, collateral_token2.bank).await,);
|
||||||
|
let liqee = get_mango_account(solana, account).await;
|
||||||
|
assert!(liqee.being_liquidated());
|
||||||
|
|
||||||
|
//
|
||||||
|
// TEST: use the insurance fund to liquidate borrow1 and borrow2
|
||||||
|
//
|
||||||
|
|
||||||
|
// Change value of token that the insurance fund is in, to check that bankruptcy amounts
|
||||||
|
// are correct if it depegs
|
||||||
|
set_bank_stub_oracle_price(solana, group, borrow_token1, admin, 2.0).await;
|
||||||
|
|
||||||
|
// bankruptcy: insurance token to liqor, liability to liqee
|
||||||
|
// liquidating only a partial amount
|
||||||
|
let liab_before = account_position_f64(solana, account, borrow_token2.bank).await;
|
||||||
|
let insurance_vault_before = solana.token_account_balance(insurance_vault).await;
|
||||||
|
let liqor_before = account_position(solana, vault_account, insurance_token.bank).await;
|
||||||
|
let insurance_to_liab = 1.5 / 20.0;
|
||||||
|
let liab_transfer: f64 = 500.0 * insurance_to_liab;
|
||||||
|
send_tx(
|
||||||
|
solana,
|
||||||
|
TokenLiqBankruptcyInstruction {
|
||||||
|
liqee: account,
|
||||||
|
liqor: vault_account,
|
||||||
|
liqor_owner: owner,
|
||||||
|
liab_mint_info: borrow_token2.mint_info,
|
||||||
|
max_liab_transfer: I80F48::from_num(liab_transfer),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let liqee = get_mango_account(solana, account).await;
|
||||||
|
assert!(liqee.being_liquidated());
|
||||||
|
assert!(account_position_closed(solana, account, insurance_token.bank).await);
|
||||||
|
assert_eq!(
|
||||||
|
account_position(solana, account, borrow_token2.bank).await,
|
||||||
|
(liab_before + liab_transfer).floor() as i64
|
||||||
|
);
|
||||||
|
let usdc_amount = (liab_transfer / insurance_to_liab * 1.02).ceil() as u64;
|
||||||
|
assert_eq!(
|
||||||
|
solana.token_account_balance(insurance_vault).await,
|
||||||
|
insurance_vault_before - usdc_amount
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
account_position(solana, vault_account, insurance_token.bank).await,
|
||||||
|
liqor_before + usdc_amount as i64
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -450,3 +450,303 @@ async fn test_liq_perps_bankruptcy() -> Result<(), TransportError> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_liq_perps_bankruptcy_other_insurance_fund() -> Result<(), TransportError> {
|
||||||
|
let mut test_builder = TestContextBuilder::new();
|
||||||
|
test_builder.test().set_compute_max_units(200_000); // PerpLiqNegativePnlOrBankruptcy takes a lot of CU
|
||||||
|
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 GroupWithTokens { group, tokens, .. } = GroupWithTokensConfig {
|
||||||
|
admin,
|
||||||
|
payer,
|
||||||
|
mints: mints.to_vec(),
|
||||||
|
zero_token_is_quote: true,
|
||||||
|
..GroupWithTokensConfig::default()
|
||||||
|
}
|
||||||
|
.create(solana)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let _quote_token = &tokens[0]; // USDC, 1/1 weights, price 1, never changed
|
||||||
|
let base_token = &tokens[1]; // used for perp market
|
||||||
|
let collateral_token = &tokens[2]; // used for adjusting account health
|
||||||
|
let insurance_token = &tokens[3];
|
||||||
|
|
||||||
|
let insurance_vault = send_tx(
|
||||||
|
solana,
|
||||||
|
GroupChangeInsuranceFund {
|
||||||
|
group,
|
||||||
|
admin,
|
||||||
|
payer,
|
||||||
|
insurance_mint: insurance_token.mint.pubkey,
|
||||||
|
withdraw_destination: payer_mint_accounts[0],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.new_insurance_vault;
|
||||||
|
|
||||||
|
// An unusual price to verify the oracle is used
|
||||||
|
set_bank_stub_oracle_price(solana, group, &insurance_token, admin, 1.6).await;
|
||||||
|
|
||||||
|
send_tx(
|
||||||
|
solana,
|
||||||
|
TokenEditWeights {
|
||||||
|
group,
|
||||||
|
admin,
|
||||||
|
mint: mints[2].pubkey,
|
||||||
|
maint_liab_weight: 1.0,
|
||||||
|
maint_asset_weight: 1.0,
|
||||||
|
init_liab_weight: 1.0,
|
||||||
|
init_asset_weight: 1.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let fund_insurance = |amount: u64| async move {
|
||||||
|
let mut tx = ClientTransaction::new(solana);
|
||||||
|
tx.add_instruction_direct(
|
||||||
|
spl_token::instruction::transfer(
|
||||||
|
&spl_token::ID,
|
||||||
|
&payer_mint_accounts[3],
|
||||||
|
&insurance_vault,
|
||||||
|
&payer.pubkey(),
|
||||||
|
&[&payer.pubkey()],
|
||||||
|
amount,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
tx.add_signer(payer);
|
||||||
|
tx.send().await.unwrap();
|
||||||
|
};
|
||||||
|
|
||||||
|
// all perp markets used here default to price = 1.0, base_lot_size = 100
|
||||||
|
let price_lots = 100;
|
||||||
|
|
||||||
|
let context_ref = &context;
|
||||||
|
let mut perp_market_index: PerpMarketIndex = 0;
|
||||||
|
let setup_perp_inner = |perp_market_index: PerpMarketIndex,
|
||||||
|
health: i64,
|
||||||
|
pnl: i64,
|
||||||
|
settle_limit: i64| async move {
|
||||||
|
// price used later to produce negative pnl with a short:
|
||||||
|
// doubling the price leads to -100 pnl
|
||||||
|
let adj_price = 1.0 + pnl as f64 / -100.0;
|
||||||
|
let adj_price_lots = (price_lots as f64 * adj_price) as i64;
|
||||||
|
|
||||||
|
let fresh_liqor = create_funded_account(
|
||||||
|
&solana,
|
||||||
|
group,
|
||||||
|
owner,
|
||||||
|
200 + perp_market_index as u32,
|
||||||
|
&context_ref.users[1],
|
||||||
|
mints,
|
||||||
|
10000,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mango_v4::accounts::PerpCreateMarket { perp_market, .. } = send_tx(
|
||||||
|
solana,
|
||||||
|
PerpCreateMarketInstruction {
|
||||||
|
group,
|
||||||
|
admin,
|
||||||
|
payer,
|
||||||
|
perp_market_index,
|
||||||
|
quote_lot_size: 1,
|
||||||
|
base_lot_size: 100,
|
||||||
|
maint_base_asset_weight: 0.8,
|
||||||
|
init_base_asset_weight: 0.6,
|
||||||
|
maint_base_liab_weight: 1.2,
|
||||||
|
init_base_liab_weight: 1.4,
|
||||||
|
base_liquidation_fee: 0.05,
|
||||||
|
maker_fee: 0.0,
|
||||||
|
taker_fee: 0.0,
|
||||||
|
group_insurance_fund: true,
|
||||||
|
// adjust this factur such that we get the desired settle limit in the end
|
||||||
|
settle_pnl_limit_factor: (settle_limit as f32 - 0.1).max(0.0)
|
||||||
|
/ (1.0 * 100.0 * adj_price) as f32,
|
||||||
|
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
|
||||||
|
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, base_token).await
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
set_perp_stub_oracle_price(solana, group, perp_market, &base_token, admin, 1.0).await;
|
||||||
|
set_bank_stub_oracle_price(solana, group, &collateral_token, admin, 1.0).await;
|
||||||
|
|
||||||
|
//
|
||||||
|
// SETUP: accounts
|
||||||
|
//
|
||||||
|
let deposit_amount = 1000;
|
||||||
|
let helper_account = create_funded_account(
|
||||||
|
&solana,
|
||||||
|
group,
|
||||||
|
owner,
|
||||||
|
perp_market_index as u32 * 2,
|
||||||
|
&context_ref.users[1],
|
||||||
|
&mints[2..3],
|
||||||
|
deposit_amount,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let account = create_funded_account(
|
||||||
|
&solana,
|
||||||
|
group,
|
||||||
|
owner,
|
||||||
|
perp_market_index as u32 * 2 + 1,
|
||||||
|
&context_ref.users[1],
|
||||||
|
&mints[2..3],
|
||||||
|
deposit_amount,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
//
|
||||||
|
// SETUP: Trade perps between accounts twice to generate pnl, settle_limit
|
||||||
|
//
|
||||||
|
let mut tx = ClientTransaction::new(solana);
|
||||||
|
tx.add_instruction(PerpPlaceOrderInstruction {
|
||||||
|
account: helper_account,
|
||||||
|
perp_market,
|
||||||
|
owner,
|
||||||
|
side: Side::Bid,
|
||||||
|
price_lots,
|
||||||
|
max_base_lots: 1,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
tx.add_instruction(PerpPlaceOrderInstruction {
|
||||||
|
account: account,
|
||||||
|
perp_market,
|
||||||
|
owner,
|
||||||
|
side: Side::Ask,
|
||||||
|
price_lots,
|
||||||
|
max_base_lots: 1,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
tx.add_instruction(PerpConsumeEventsInstruction {
|
||||||
|
perp_market,
|
||||||
|
mango_accounts: vec![account, helper_account],
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
tx.send().await.unwrap();
|
||||||
|
|
||||||
|
set_perp_stub_oracle_price(solana, group, perp_market, &base_token, admin, adj_price).await;
|
||||||
|
let mut tx = ClientTransaction::new(solana);
|
||||||
|
tx.add_instruction(PerpPlaceOrderInstruction {
|
||||||
|
account: helper_account,
|
||||||
|
perp_market,
|
||||||
|
owner,
|
||||||
|
side: Side::Ask,
|
||||||
|
price_lots: adj_price_lots,
|
||||||
|
max_base_lots: 1,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
tx.add_instruction(PerpPlaceOrderInstruction {
|
||||||
|
account: account,
|
||||||
|
perp_market,
|
||||||
|
owner,
|
||||||
|
side: Side::Bid,
|
||||||
|
price_lots: adj_price_lots,
|
||||||
|
max_base_lots: 1,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
tx.add_instruction(PerpConsumeEventsInstruction {
|
||||||
|
perp_market,
|
||||||
|
mango_accounts: vec![account, helper_account],
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
tx.send().await.unwrap();
|
||||||
|
|
||||||
|
set_perp_stub_oracle_price(solana, group, perp_market, &base_token, admin, 1.0).await;
|
||||||
|
|
||||||
|
// Adjust target health:
|
||||||
|
// full health = 1000 * collat price * 1.0 + pnl
|
||||||
|
set_bank_stub_oracle_price(
|
||||||
|
solana,
|
||||||
|
group,
|
||||||
|
&collateral_token,
|
||||||
|
admin,
|
||||||
|
(health - pnl) as f64 / 1000.0,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Verify we got it right
|
||||||
|
let account_data = solana.get_account::<MangoAccount>(account).await;
|
||||||
|
assert_eq!(account_data.perps[0].quote_position_native(), pnl);
|
||||||
|
assert_eq!(
|
||||||
|
account_data.perps[0].recurring_settle_pnl_allowance,
|
||||||
|
settle_limit
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
account_init_health(solana, account).await.round(),
|
||||||
|
health as f64
|
||||||
|
);
|
||||||
|
|
||||||
|
(perp_market, account, fresh_liqor)
|
||||||
|
};
|
||||||
|
let mut setup_perp = |health: i64, pnl: i64, settle_limit: i64| {
|
||||||
|
let out = setup_perp_inner(perp_market_index, health, pnl, settle_limit);
|
||||||
|
perp_market_index += 1;
|
||||||
|
out
|
||||||
|
};
|
||||||
|
|
||||||
|
let limit_prec = |f: f64| (f * 1000.0).round() / 1000.0;
|
||||||
|
|
||||||
|
let liq_event_amounts = || {
|
||||||
|
let settlement = solana
|
||||||
|
.program_log_events::<mango_v4::logs::PerpLiqNegativePnlOrBankruptcyLog>()
|
||||||
|
.pop()
|
||||||
|
.map(|v| limit_prec(I80F48::from_bits(v.settlement).to_num::<f64>()))
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
let (insur, loss) = solana
|
||||||
|
.program_log_events::<mango_v4::logs::PerpLiqBankruptcyLog>()
|
||||||
|
.pop()
|
||||||
|
.map(|v| {
|
||||||
|
(
|
||||||
|
I80F48::from_bits(v.insurance_transfer).to_num::<u64>(),
|
||||||
|
limit_prec(I80F48::from_bits(v.socialized_loss).to_num::<f64>()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or((0, 0.0));
|
||||||
|
(settlement, insur, loss)
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let (perp_market, account, liqor) = setup_perp(-40, -50, 5).await;
|
||||||
|
fund_insurance(42).await;
|
||||||
|
|
||||||
|
send_tx(
|
||||||
|
solana,
|
||||||
|
PerpLiqNegativePnlOrBankruptcyInstruction {
|
||||||
|
liqor,
|
||||||
|
liqor_owner: owner,
|
||||||
|
liqee: account,
|
||||||
|
perp_market,
|
||||||
|
max_liab_transfer: u64::MAX,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
// 27 insurance cover 27*1.6 = 43.2, where the needs is for 40 * 1.05 = 42
|
||||||
|
assert_eq!(liq_event_amounts(), (5.0, 27, 0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -1914,6 +1914,58 @@ impl ClientInstruction for GroupEdit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct GroupChangeInsuranceFund {
|
||||||
|
pub group: Pubkey,
|
||||||
|
pub admin: TestKeypair,
|
||||||
|
pub payer: TestKeypair,
|
||||||
|
pub insurance_mint: Pubkey,
|
||||||
|
pub withdraw_destination: Pubkey,
|
||||||
|
}
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl ClientInstruction for GroupChangeInsuranceFund {
|
||||||
|
type Accounts = mango_v4::accounts::GroupChangeInsuranceFund;
|
||||||
|
type Instruction = mango_v4::instruction::GroupChangeInsuranceFund;
|
||||||
|
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 {};
|
||||||
|
|
||||||
|
let group = account_loader.load::<Group>(&self.group).await.unwrap();
|
||||||
|
|
||||||
|
let new_insurance_vault = Pubkey::find_program_address(
|
||||||
|
&[
|
||||||
|
b"InsuranceVault".as_ref(),
|
||||||
|
self.group.as_ref(),
|
||||||
|
self.insurance_mint.as_ref(),
|
||||||
|
],
|
||||||
|
&program_id,
|
||||||
|
)
|
||||||
|
.0;
|
||||||
|
|
||||||
|
let accounts = Self::Accounts {
|
||||||
|
group: self.group,
|
||||||
|
admin: self.admin.pubkey(),
|
||||||
|
insurance_vault: group.insurance_vault,
|
||||||
|
withdraw_destination: self.withdraw_destination,
|
||||||
|
new_insurance_mint: self.insurance_mint,
|
||||||
|
new_insurance_vault,
|
||||||
|
payer: self.payer.pubkey(),
|
||||||
|
token_program: Token::id(),
|
||||||
|
system_program: System::id(),
|
||||||
|
rent: sysvar::rent::Rent::id(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let instruction = make_instruction(program_id, &accounts, &instruction);
|
||||||
|
(accounts, instruction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signers(&self) -> Vec<TestKeypair> {
|
||||||
|
vec![self.admin, self.payer]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct IxGateSetInstruction {
|
pub struct IxGateSetInstruction {
|
||||||
pub group: Pubkey,
|
pub group: Pubkey,
|
||||||
pub admin: TestKeypair,
|
pub admin: TestKeypair,
|
||||||
|
@ -1957,21 +2009,17 @@ impl ClientInstruction for GroupCloseInstruction {
|
||||||
type Instruction = mango_v4::instruction::GroupClose;
|
type Instruction = mango_v4::instruction::GroupClose;
|
||||||
async fn to_instruction(
|
async fn to_instruction(
|
||||||
&self,
|
&self,
|
||||||
_account_loader: &(impl ClientAccountLoader + 'async_trait),
|
account_loader: &(impl ClientAccountLoader + 'async_trait),
|
||||||
) -> (Self::Accounts, instruction::Instruction) {
|
) -> (Self::Accounts, instruction::Instruction) {
|
||||||
let program_id = mango_v4::id();
|
let program_id = mango_v4::id();
|
||||||
let instruction = Self::Instruction {};
|
let instruction = Self::Instruction {};
|
||||||
|
|
||||||
let insurance_vault = Pubkey::find_program_address(
|
let group = account_loader.load::<Group>(&self.group).await.unwrap();
|
||||||
&[b"InsuranceVault".as_ref(), self.group.as_ref()],
|
|
||||||
&program_id,
|
|
||||||
)
|
|
||||||
.0;
|
|
||||||
|
|
||||||
let accounts = Self::Accounts {
|
let accounts = Self::Accounts {
|
||||||
group: self.group,
|
group: self.group,
|
||||||
admin: self.admin.pubkey(),
|
admin: self.admin.pubkey(),
|
||||||
insurance_vault,
|
insurance_vault: group.insurance_vault,
|
||||||
sol_destination: self.sol_destination,
|
sol_destination: self.sol_destination,
|
||||||
token_program: Token::id(),
|
token_program: Token::id(),
|
||||||
};
|
};
|
||||||
|
@ -3253,21 +3301,11 @@ impl ClientInstruction for TokenLiqBankruptcyInstruction {
|
||||||
.load_mango_account(&self.liqor)
|
.load_mango_account(&self.liqor)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let health_check_metas = derive_liquidation_remaining_account_metas(
|
|
||||||
account_loader,
|
|
||||||
&liqee,
|
|
||||||
&liqor,
|
|
||||||
QUOTE_TOKEN_INDEX,
|
|
||||||
0,
|
|
||||||
liab_mint_info.token_index,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let group_key = liqee.fixed.group;
|
let group_key = liqee.fixed.group;
|
||||||
let group: Group = account_loader.load(&group_key).await.unwrap();
|
let group: Group = account_loader.load(&group_key).await.unwrap();
|
||||||
|
|
||||||
let quote_mint_info = Pubkey::find_program_address(
|
let insurance_mint_info = Pubkey::find_program_address(
|
||||||
&[
|
&[
|
||||||
b"MintInfo".as_ref(),
|
b"MintInfo".as_ref(),
|
||||||
liqee.fixed.group.as_ref(),
|
liqee.fixed.group.as_ref(),
|
||||||
|
@ -3276,13 +3314,19 @@ impl ClientInstruction for TokenLiqBankruptcyInstruction {
|
||||||
&program_id,
|
&program_id,
|
||||||
)
|
)
|
||||||
.0;
|
.0;
|
||||||
let quote_mint_info: MintInfo = account_loader.load("e_mint_info).await.unwrap();
|
let insurance_mint_info: MintInfo =
|
||||||
|
account_loader.load(&insurance_mint_info).await.unwrap();
|
||||||
|
|
||||||
let insurance_vault = Pubkey::find_program_address(
|
let health_check_metas = derive_liquidation_remaining_account_metas(
|
||||||
&[b"InsuranceVault".as_ref(), group_key.as_ref()],
|
account_loader,
|
||||||
&program_id,
|
&liqee,
|
||||||
|
&liqor,
|
||||||
|
insurance_mint_info.token_index,
|
||||||
|
0,
|
||||||
|
liab_mint_info.token_index,
|
||||||
|
0,
|
||||||
)
|
)
|
||||||
.0;
|
.await;
|
||||||
|
|
||||||
let accounts = Self::Accounts {
|
let accounts = Self::Accounts {
|
||||||
group: group_key,
|
group: group_key,
|
||||||
|
@ -3290,8 +3334,8 @@ impl ClientInstruction for TokenLiqBankruptcyInstruction {
|
||||||
liqor: self.liqor,
|
liqor: self.liqor,
|
||||||
liqor_owner: self.liqor_owner.pubkey(),
|
liqor_owner: self.liqor_owner.pubkey(),
|
||||||
liab_mint_info: self.liab_mint_info,
|
liab_mint_info: self.liab_mint_info,
|
||||||
quote_vault: quote_mint_info.first_vault(),
|
quote_vault: insurance_mint_info.first_vault(),
|
||||||
insurance_vault,
|
insurance_vault: group.insurance_vault,
|
||||||
token_program: Token::id(),
|
token_program: Token::id(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4344,7 +4388,6 @@ impl ClientInstruction for PerpLiqNegativePnlOrBankruptcyInstruction {
|
||||||
};
|
};
|
||||||
|
|
||||||
let perp_market: PerpMarket = account_loader.load(&self.perp_market).await.unwrap();
|
let perp_market: PerpMarket = account_loader.load(&self.perp_market).await.unwrap();
|
||||||
let group_key = perp_market.group;
|
|
||||||
let liqor = account_loader
|
let liqor = account_loader
|
||||||
.load_mango_account(&self.liqor)
|
.load_mango_account(&self.liqor)
|
||||||
.await
|
.await
|
||||||
|
@ -4353,23 +4396,36 @@ impl ClientInstruction for PerpLiqNegativePnlOrBankruptcyInstruction {
|
||||||
.load_mango_account(&self.liqee)
|
.load_mango_account(&self.liqee)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let group_key = liqee.fixed.group;
|
||||||
|
let group: Group = account_loader.load(&group_key).await.unwrap();
|
||||||
|
|
||||||
|
let insurance_mint_info = Pubkey::find_program_address(
|
||||||
|
&[
|
||||||
|
b"MintInfo".as_ref(),
|
||||||
|
liqee.fixed.group.as_ref(),
|
||||||
|
group.insurance_mint.as_ref(),
|
||||||
|
],
|
||||||
|
&program_id,
|
||||||
|
)
|
||||||
|
.0;
|
||||||
|
let insurance_mint_info: MintInfo =
|
||||||
|
account_loader.load(&insurance_mint_info).await.unwrap();
|
||||||
|
|
||||||
let health_check_metas = derive_liquidation_remaining_account_metas(
|
let health_check_metas = derive_liquidation_remaining_account_metas(
|
||||||
account_loader,
|
account_loader,
|
||||||
&liqee,
|
&liqee,
|
||||||
&liqor,
|
&liqor,
|
||||||
TokenIndex::MAX,
|
insurance_mint_info.token_index,
|
||||||
0,
|
0,
|
||||||
TokenIndex::MAX,
|
TokenIndex::MAX,
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let group = account_loader.load::<Group>(&group_key).await.unwrap();
|
|
||||||
let settle_mint_info =
|
let settle_mint_info =
|
||||||
get_mint_info_by_token_index(account_loader, &liqee, perp_market.settle_token_index)
|
get_mint_info_by_token_index(account_loader, &liqee, perp_market.settle_token_index)
|
||||||
.await;
|
.await;
|
||||||
let insurance_mint_info =
|
|
||||||
get_mint_info_by_token_index(account_loader, &liqee, QUOTE_TOKEN_INDEX).await;
|
|
||||||
|
|
||||||
let accounts = Self::Accounts {
|
let accounts = Self::Accounts {
|
||||||
group: group_key,
|
group: group_key,
|
||||||
|
|
|
@ -361,6 +361,24 @@ export class MangoClient {
|
||||||
return await this.sendAndConfirmTransactionForGroup(group, [ix]);
|
return await this.sendAndConfirmTransactionForGroup(group, [ix]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async groupChangeInsuranceFund(
|
||||||
|
group: Group,
|
||||||
|
withdrawDestination: PublicKey,
|
||||||
|
newInsuranceMint: PublicKey,
|
||||||
|
): Promise<MangoSignatureStatus> {
|
||||||
|
const ix = await this.program.methods
|
||||||
|
.groupChangeInsuranceFund()
|
||||||
|
.accounts({
|
||||||
|
group: group.publicKey,
|
||||||
|
admin: (this.program.provider as AnchorProvider).wallet.publicKey,
|
||||||
|
insuranceVault: group.insuranceVault,
|
||||||
|
withdrawDestination,
|
||||||
|
newInsuranceMint,
|
||||||
|
})
|
||||||
|
.instruction();
|
||||||
|
return await this.sendAndConfirmTransactionForGroup(group, [ix]);
|
||||||
|
}
|
||||||
|
|
||||||
public async ixGateSet(
|
public async ixGateSet(
|
||||||
group: Group,
|
group: Group,
|
||||||
ixGateParams: IxGateParams,
|
ixGateParams: IxGateParams,
|
||||||
|
|
|
@ -36,7 +36,7 @@ export interface TokenRegisterParams {
|
||||||
|
|
||||||
export const DefaultTokenRegisterParams: TokenRegisterParams = {
|
export const DefaultTokenRegisterParams: TokenRegisterParams = {
|
||||||
oracleConfig: {
|
oracleConfig: {
|
||||||
confFilter: 0,
|
confFilter: 0.3,
|
||||||
maxStalenessSlots: null,
|
maxStalenessSlots: null,
|
||||||
},
|
},
|
||||||
groupInsuranceFund: false,
|
groupInsuranceFund: false,
|
||||||
|
@ -312,6 +312,7 @@ export interface IxGateParams {
|
||||||
TokenForceWithdraw: boolean;
|
TokenForceWithdraw: boolean;
|
||||||
SequenceCheck: boolean;
|
SequenceCheck: boolean;
|
||||||
HealthCheck: boolean;
|
HealthCheck: boolean;
|
||||||
|
GroupChangeInsuranceFund: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default with all ixs enabled, use with buildIxGate
|
// Default with all ixs enabled, use with buildIxGate
|
||||||
|
@ -394,6 +395,7 @@ export const TrueIxGateParams: IxGateParams = {
|
||||||
TokenForceWithdraw: true,
|
TokenForceWithdraw: true,
|
||||||
SequenceCheck: true,
|
SequenceCheck: true,
|
||||||
HealthCheck: true,
|
HealthCheck: true,
|
||||||
|
GroupChangeInsuranceFund: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// build ix gate e.g. buildIxGate(Builder(TrueIxGateParams).TokenDeposit(false).build()).toNumber(),
|
// build ix gate e.g. buildIxGate(Builder(TrueIxGateParams).TokenDeposit(false).build()).toNumber(),
|
||||||
|
@ -486,6 +488,7 @@ export function buildIxGate(p: IxGateParams): BN {
|
||||||
toggleIx(ixGate, p, 'TokenForceWithdraw', 72);
|
toggleIx(ixGate, p, 'TokenForceWithdraw', 72);
|
||||||
toggleIx(ixGate, p, 'SequenceCheck', 73);
|
toggleIx(ixGate, p, 'SequenceCheck', 73);
|
||||||
toggleIx(ixGate, p, 'HealthCheck', 74);
|
toggleIx(ixGate, p, 'HealthCheck', 74);
|
||||||
|
toggleIx(ixGate, p, 'GroupChangeInsuranceFund', 76);
|
||||||
|
|
||||||
return ixGate;
|
return ixGate;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue