Merge pull request #95 from blockworks-foundation/ckamm/insurance-fund
liq_token_bankruptcy for socialized loss and insurance fund
This commit is contained in:
commit
5b72e85634
|
@ -78,12 +78,12 @@ impl MangoClient {
|
|||
// Mango Account
|
||||
let mut mango_account_tuples = program.accounts::<MangoAccount>(vec![
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 40,
|
||||
offset: 8,
|
||||
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
|
||||
encoding: None,
|
||||
}),
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 72,
|
||||
offset: 40,
|
||||
bytes: MemcmpEncodedBytes::Base58(payer.pubkey().to_string()),
|
||||
encoding: None,
|
||||
}),
|
||||
|
@ -135,12 +135,12 @@ impl MangoClient {
|
|||
}
|
||||
let mango_account_tuples = program.accounts::<MangoAccount>(vec![
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 40,
|
||||
offset: 8,
|
||||
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
|
||||
encoding: None,
|
||||
}),
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 72,
|
||||
offset: 40,
|
||||
bytes: MemcmpEncodedBytes::Base58(payer.pubkey().to_string()),
|
||||
encoding: None,
|
||||
}),
|
||||
|
@ -155,7 +155,7 @@ impl MangoClient {
|
|||
let mut banks_cache = HashMap::new();
|
||||
let mut banks_cache_by_token_index = HashMap::new();
|
||||
let bank_tuples = program.accounts::<Bank>(vec![RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 24,
|
||||
offset: 8,
|
||||
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
|
||||
encoding: None,
|
||||
})])?;
|
||||
|
@ -197,7 +197,7 @@ impl MangoClient {
|
|||
let mut serum3_external_markets_cache = HashMap::new();
|
||||
let serum3_market_tuples =
|
||||
program.accounts::<Serum3Market>(vec![RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 24,
|
||||
offset: 8,
|
||||
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
|
||||
encoding: None,
|
||||
})])?;
|
||||
|
@ -221,7 +221,7 @@ impl MangoClient {
|
|||
let mut perp_markets_cache_by_perp_market_index = HashMap::new();
|
||||
let perp_market_tuples =
|
||||
program.accounts::<PerpMarket>(vec![RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 24,
|
||||
offset: 8,
|
||||
bytes: MemcmpEncodedBytes::Base58(group.to_string()),
|
||||
encoding: None,
|
||||
})])?;
|
||||
|
@ -279,12 +279,12 @@ impl MangoClient {
|
|||
pub fn get_account(&self) -> Result<(Pubkey, MangoAccount), anchor_client::ClientError> {
|
||||
let mango_accounts = self.program().accounts::<MangoAccount>(vec![
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 40,
|
||||
offset: 8,
|
||||
bytes: MemcmpEncodedBytes::Base58(self.group().to_string()),
|
||||
encoding: None,
|
||||
}),
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 72,
|
||||
offset: 40,
|
||||
bytes: MemcmpEncodedBytes::Base58(self.payer().to_string()),
|
||||
encoding: None,
|
||||
}),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token::Token;
|
||||
|
||||
use crate::error::*;
|
||||
use crate::state::*;
|
||||
|
||||
#[derive(Accounts)]
|
||||
|
@ -33,9 +34,9 @@ pub fn close_account(ctx: Context<CloseAccount>) -> Result<()> {
|
|||
}
|
||||
|
||||
let account = ctx.accounts.account.load()?;
|
||||
require_eq!(account.being_liquidated, 0);
|
||||
require!(!account.being_liquidated(), MangoError::SomeError);
|
||||
require!(!account.is_bankrupt(), MangoError::SomeError);
|
||||
require_eq!(account.delegate, Pubkey::default());
|
||||
require_eq!(account.is_bankrupt, 0);
|
||||
for ele in account.tokens.values {
|
||||
require_eq!(ele.is_active(), false);
|
||||
}
|
||||
|
|
|
@ -37,8 +37,8 @@ pub fn create_account(ctx: Context<CreateAccount>, account_num: u8, name: String
|
|||
account.tokens = MangoAccountTokenPositions::default();
|
||||
account.serum3 = MangoAccountSerum3Orders::default();
|
||||
account.perps = MangoAccountPerpPositions::default();
|
||||
account.being_liquidated = 0;
|
||||
account.is_bankrupt = 0;
|
||||
account.set_being_liquidated(false);
|
||||
account.set_bankrupt(false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token::{Mint, Token, TokenAccount};
|
||||
|
||||
use crate::error::*;
|
||||
use crate::state::*;
|
||||
|
@ -17,15 +18,31 @@ pub struct CreateGroup<'info> {
|
|||
|
||||
pub admin: Signer<'info>,
|
||||
|
||||
pub insurance_mint: Account<'info, Mint>,
|
||||
|
||||
#[account(
|
||||
init,
|
||||
seeds = [group.key().as_ref(), b"InsuranceVault".as_ref()],
|
||||
bump,
|
||||
token::authority = group,
|
||||
token::mint = insurance_mint,
|
||||
payer = payer
|
||||
)]
|
||||
pub 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>,
|
||||
}
|
||||
|
||||
pub fn create_group(ctx: Context<CreateGroup>, group_num: u32, testing: u8) -> Result<()> {
|
||||
let mut group = ctx.accounts.group.load_init()?;
|
||||
group.admin = ctx.accounts.admin.key();
|
||||
group.insurance_vault = ctx.accounts.insurance_vault.key();
|
||||
group.insurance_mint = ctx.accounts.insurance_mint.key();
|
||||
group.bump = *ctx.bumps.get("group").ok_or(MangoError::SomeError)?;
|
||||
group.group_num = group_num;
|
||||
group.testing = testing;
|
||||
|
|
|
@ -85,7 +85,7 @@ pub fn flash_loan<'key, 'accounts, 'remaining, 'info>(
|
|||
|
||||
let group = ctx.accounts.group.load()?;
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
require!(account.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
|
||||
|
||||
// Go over the banks passed as health accounts and:
|
||||
// - Ensure that all banks that are passed in have activated positions.
|
||||
|
|
|
@ -162,7 +162,7 @@ pub fn flash_loan2_end<'key, 'accounts, 'remaining, 'info>(
|
|||
let group_seeds = group_seeds!(group);
|
||||
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
require!(account.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
|
||||
|
||||
// Find index at which vaults start
|
||||
let vaults_index = ctx
|
||||
|
|
|
@ -167,7 +167,7 @@ pub fn flash_loan3_end<'key, 'accounts, 'remaining, 'info>(
|
|||
ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoan3End<'info>>,
|
||||
) -> Result<()> {
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
require!(account.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
|
||||
|
||||
// Find index at which vaults start
|
||||
let vaults_index = ctx
|
||||
|
|
|
@ -0,0 +1,218 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token;
|
||||
use anchor_spl::token::Token;
|
||||
use anchor_spl::token::TokenAccount;
|
||||
use fixed::types::I80F48;
|
||||
|
||||
use crate::accounts_zerocopy::*;
|
||||
use crate::error::*;
|
||||
use crate::state::ScanningAccountRetriever;
|
||||
use crate::state::*;
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
// Remaining accounts:
|
||||
// - all banks for liab_token_index (writable)
|
||||
// - merged health accounts for liqor+liqee
|
||||
#[derive(Accounts)]
|
||||
#[instruction(liab_token_index: TokenIndex)]
|
||||
pub struct LiqTokenBankruptcy<'info> {
|
||||
#[account(
|
||||
has_one = insurance_vault,
|
||||
)]
|
||||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
has_one = group,
|
||||
constraint = liqor.load()?.is_owner_or_delegate(liqor_owner.key()),
|
||||
)]
|
||||
pub liqor: AccountLoader<'info, MangoAccount>,
|
||||
pub liqor_owner: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
has_one = group,
|
||||
)]
|
||||
pub liqee: AccountLoader<'info, MangoAccount>,
|
||||
|
||||
#[account(
|
||||
has_one = group,
|
||||
constraint = liab_mint_info.load()?.token_index == liab_token_index,
|
||||
)]
|
||||
pub liab_mint_info: AccountLoader<'info, MintInfo>,
|
||||
|
||||
#[account(mut)]
|
||||
pub quote_vault: Account<'info, TokenAccount>,
|
||||
|
||||
#[account(mut)]
|
||||
pub insurance_vault: Account<'info, TokenAccount>,
|
||||
|
||||
pub token_program: Program<'info, Token>,
|
||||
}
|
||||
|
||||
impl<'info> LiqTokenBankruptcy<'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.quote_vault.to_account_info(),
|
||||
authority: self.group.to_account_info(),
|
||||
};
|
||||
CpiContext::new(program, accounts)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn liq_token_bankruptcy(
|
||||
ctx: Context<LiqTokenBankruptcy>,
|
||||
liab_token_index: TokenIndex,
|
||||
max_liab_transfer: I80F48,
|
||||
) -> Result<()> {
|
||||
let group = ctx.accounts.group.load()?;
|
||||
let group_pk = &ctx.accounts.group.key();
|
||||
|
||||
// split remaining accounts into banks and health
|
||||
let liab_mint_info = ctx.accounts.liab_mint_info.load()?;
|
||||
let bank_pks = liab_mint_info.banks();
|
||||
let (bank_ais, health_ais) = &ctx.remaining_accounts.split_at(bank_pks.len());
|
||||
require!(
|
||||
bank_ais.iter().map(|ai| ai.key).eq(bank_pks.iter()),
|
||||
MangoError::SomeError
|
||||
);
|
||||
|
||||
let mut liqor = ctx.accounts.liqor.load_mut()?;
|
||||
require!(!liqor.is_bankrupt(), MangoError::IsBankrupt);
|
||||
|
||||
let mut liqee = ctx.accounts.liqee.load_mut()?;
|
||||
require!(liqee.is_bankrupt(), MangoError::SomeError);
|
||||
|
||||
let liab_bank = bank_ais[0].load::<Bank>()?;
|
||||
let liab_deposit_index = liab_bank.deposit_index;
|
||||
let (liqee_liab, liqee_raw_token_index, _) =
|
||||
liqee.tokens.get_mut_or_create(liab_token_index)?;
|
||||
let mut remaining_liab_loss = -liqee_liab.native(&liab_bank);
|
||||
require_gt!(remaining_liab_loss, I80F48::ZERO);
|
||||
drop(liab_bank);
|
||||
|
||||
let mut account_retriever = ScanningAccountRetriever::new(health_ais, group_pk)?;
|
||||
|
||||
// find insurance transfer amount
|
||||
let (liab_bank, liab_price, opt_quote_bank_and_price) =
|
||||
account_retriever.banks_mut_and_oracles(liab_token_index, QUOTE_TOKEN_INDEX)?;
|
||||
let liab_fee_factor = if liab_token_index == QUOTE_TOKEN_INDEX {
|
||||
I80F48::ONE
|
||||
} else {
|
||||
cm!(I80F48::ONE + liab_bank.liquidation_fee)
|
||||
};
|
||||
let liab_price_adjusted = cm!(liab_price * liab_fee_factor);
|
||||
|
||||
let liab_transfer_unrounded = remaining_liab_loss.min(max_liab_transfer);
|
||||
let insurance_transfer = cm!(liab_transfer_unrounded * liab_price_adjusted)
|
||||
.checked_ceil()
|
||||
.unwrap()
|
||||
.checked_to_num::<u64>()
|
||||
.unwrap()
|
||||
.min(ctx.accounts.insurance_vault.amount);
|
||||
let insurance_fund_exhausted = insurance_transfer == ctx.accounts.insurance_vault.amount;
|
||||
|
||||
let insurance_transfer_i80f48 = I80F48::from(insurance_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
|
||||
// liquidators to exploit the insurance fund for 1 native token each call.
|
||||
let liab_transfer = cm!(insurance_transfer_i80f48 / liab_price_adjusted);
|
||||
|
||||
let mut liqee_liab_active = true;
|
||||
if insurance_transfer > 0 {
|
||||
// in the end, the liqee gets liab assets
|
||||
liqee_liab_active = liab_bank.deposit(liqee_liab, liab_transfer)?;
|
||||
remaining_liab_loss = -liqee_liab.native(&liab_bank);
|
||||
|
||||
// move insurance assets into quote bank
|
||||
let group_seeds = group_seeds!(group);
|
||||
token::transfer(
|
||||
ctx.accounts.transfer_ctx().with_signer(&[group_seeds]),
|
||||
insurance_transfer,
|
||||
)?;
|
||||
|
||||
// move quote assets into liqor and withdraw liab assets
|
||||
if let Some((quote_bank, _)) = opt_quote_bank_and_price {
|
||||
require_keys_eq!(quote_bank.vault, ctx.accounts.quote_vault.key());
|
||||
require_keys_eq!(quote_bank.mint, ctx.accounts.insurance_vault.mint);
|
||||
|
||||
// credit the liqor
|
||||
let (liqor_quote, liqor_quote_raw_token_index, _) =
|
||||
liqor.tokens.get_mut_or_create(QUOTE_TOKEN_INDEX)?;
|
||||
let liqor_quote_active = quote_bank.deposit(liqor_quote, insurance_transfer_i80f48)?;
|
||||
|
||||
// transfer liab from liqee to liqor
|
||||
let (liqor_liab, liqor_liab_raw_token_index, _) =
|
||||
liqor.tokens.get_mut_or_create(liab_token_index)?;
|
||||
let liqor_liab_active = liab_bank.withdraw_with_fee(liqor_liab, liab_transfer)?;
|
||||
|
||||
// Check liqor's health
|
||||
let liqor_health = compute_health(&liqor, HealthType::Init, &account_retriever)?;
|
||||
require!(liqor_health >= 0, MangoError::HealthMustBePositive);
|
||||
|
||||
if !liqor_quote_active {
|
||||
liqor.tokens.deactivate(liqor_quote_raw_token_index);
|
||||
}
|
||||
if !liqor_liab_active {
|
||||
liqor.tokens.deactivate(liqor_liab_raw_token_index);
|
||||
}
|
||||
} else {
|
||||
// For liab_token_index == QUOTE_TOKEN_INDEX: the insurance fund deposits directly into liqee,
|
||||
// without a fee or the liqor being involved
|
||||
require_eq!(liab_token_index, QUOTE_TOKEN_INDEX);
|
||||
require_eq!(liab_price_adjusted, I80F48::ONE);
|
||||
require_eq!(insurance_transfer_i80f48, liab_transfer);
|
||||
}
|
||||
}
|
||||
drop(account_retriever);
|
||||
|
||||
// Socialize loss
|
||||
if insurance_fund_exhausted && remaining_liab_loss.is_positive() {
|
||||
// find the total deposits
|
||||
let mut indexed_total_deposits = I80F48::ZERO;
|
||||
for bank_ai in bank_ais.iter() {
|
||||
let bank = bank_ai.load::<Bank>()?;
|
||||
indexed_total_deposits = cm!(indexed_total_deposits + bank.indexed_deposits);
|
||||
}
|
||||
|
||||
// This is the solution to:
|
||||
// total_indexed_deposits * (deposit_index - new_deposit_index) = remaining_liab_loss
|
||||
// AUDIT: Could it happen that remaining_liab_loss > total_indexed_deposits * deposit_index?
|
||||
// Probably not.
|
||||
let new_deposit_index =
|
||||
cm!(liab_deposit_index - remaining_liab_loss / indexed_total_deposits);
|
||||
|
||||
let mut amount_to_credit = remaining_liab_loss;
|
||||
let mut position_active = true;
|
||||
for bank_ai in bank_ais.iter() {
|
||||
let mut bank = bank_ai.load_mut::<Bank>()?;
|
||||
bank.deposit_index = new_deposit_index;
|
||||
|
||||
// credit liqee on each bank where we can offset borrows
|
||||
let amount_for_bank = amount_to_credit.min(bank.native_borrows());
|
||||
if amount_for_bank.is_positive() {
|
||||
position_active = bank.deposit(liqee_liab, amount_for_bank)?;
|
||||
amount_to_credit = cm!(amount_to_credit - amount_for_bank);
|
||||
if amount_to_credit.is_zero() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
require!(!position_active, MangoError::SomeError);
|
||||
liqee_liab_active = false;
|
||||
}
|
||||
|
||||
// If the account has no more borrows then it's no longer bankrupt
|
||||
let account_retriever = ScanningAccountRetriever::new(health_ais, group_pk)?;
|
||||
let liqee_health_cache = new_health_cache(&liqee, &account_retriever)?;
|
||||
liqee.set_bankrupt(liqee_health_cache.has_borrows());
|
||||
|
||||
if !liqee_liab_active {
|
||||
liqee.tokens.deactivate(liqee_raw_token_index);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -15,7 +15,7 @@ pub struct LiqTokenWithToken<'info> {
|
|||
#[account(
|
||||
mut,
|
||||
has_one = group,
|
||||
constraint = liqor.load()?.owner == liqor_owner.key() || liqor.load()?.delegate == liqor_owner.key(),
|
||||
constraint = liqor.load()?.is_owner_or_delegate(liqor_owner.key()),
|
||||
)]
|
||||
pub liqor: AccountLoader<'info, MangoAccount>,
|
||||
pub liqor_owner: Signer<'info>,
|
||||
|
@ -39,24 +39,24 @@ pub fn liq_token_with_token(
|
|||
let mut account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk)?;
|
||||
|
||||
let mut liqor = ctx.accounts.liqor.load_mut()?;
|
||||
require!(liqor.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!liqor.is_bankrupt(), MangoError::IsBankrupt);
|
||||
|
||||
let mut liqee = ctx.accounts.liqee.load_mut()?;
|
||||
require!(liqee.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!liqee.is_bankrupt(), MangoError::IsBankrupt);
|
||||
|
||||
// Initial liqee health check
|
||||
let mut liqee_health_cache = new_health_cache(&liqee, &account_retriever)?;
|
||||
let init_health = liqee_health_cache.health(HealthType::Init)?;
|
||||
if liqee.being_liquidated != 0 {
|
||||
if liqee.being_liquidated() {
|
||||
if init_health > I80F48::ZERO {
|
||||
liqee.being_liquidated = 0;
|
||||
liqee.set_being_liquidated(false);
|
||||
msg!("Liqee init_health above zero");
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
let maint_health = liqee_health_cache.health(HealthType::Maint)?;
|
||||
require!(maint_health < I80F48::ZERO, MangoError::SomeError);
|
||||
liqee.being_liquidated = 1;
|
||||
liqee.set_being_liquidated(true);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -68,8 +68,9 @@ pub fn liq_token_with_token(
|
|||
//
|
||||
// This must happen _after_ the health computation, since immutable borrows of
|
||||
// the bank are not allowed at the same time.
|
||||
let (asset_bank, liab_bank, asset_price, liab_price) =
|
||||
let (asset_bank, asset_price, opt_liab_bank_and_price) =
|
||||
account_retriever.banks_mut_and_oracles(asset_token_index, liab_token_index)?;
|
||||
let (liab_bank, liab_price) = opt_liab_bank_and_price.unwrap();
|
||||
|
||||
let liqee_assets_native = liqee
|
||||
.tokens
|
||||
|
@ -207,13 +208,13 @@ pub fn liq_token_with_token(
|
|||
// Check liqee health again
|
||||
let maint_health = liqee_health_cache.health(HealthType::Maint)?;
|
||||
if maint_health < I80F48::ZERO {
|
||||
// TODO: bankruptcy check?
|
||||
liqee.set_bankrupt(!liqee_health_cache.has_liquidatable_assets());
|
||||
} else {
|
||||
let init_health = liqee_health_cache.health(HealthType::Init)?;
|
||||
|
||||
// this is equivalent to one native USDC or 1e-6 USDC
|
||||
// This is used as threshold to flip flag instead of 0 because of dust issues
|
||||
liqee.being_liquidated = if init_health < -I80F48::ONE { 1 } else { 0 };
|
||||
liqee.set_being_liquidated(init_health < -I80F48::ONE);
|
||||
}
|
||||
|
||||
// Check liqor's health
|
||||
|
|
|
@ -10,6 +10,7 @@ pub use edit_account::*;
|
|||
pub use flash_loan::*;
|
||||
pub use flash_loan2::*;
|
||||
pub use flash_loan3::*;
|
||||
pub use liq_token_bankruptcy::*;
|
||||
pub use liq_token_with_token::*;
|
||||
pub use perp_cancel_all_orders::*;
|
||||
pub use perp_cancel_all_orders_by_side::*;
|
||||
|
@ -51,6 +52,7 @@ mod edit_account;
|
|||
mod flash_loan;
|
||||
mod flash_loan2;
|
||||
mod flash_loan3;
|
||||
mod liq_token_bankruptcy;
|
||||
mod liq_token_with_token;
|
||||
mod perp_cancel_all_orders;
|
||||
mod perp_cancel_all_orders_by_side;
|
||||
|
|
|
@ -30,7 +30,7 @@ pub struct PerpCancelAllOrders<'info> {
|
|||
|
||||
pub fn perp_cancel_all_orders(ctx: Context<PerpCancelAllOrders>, limit: u8) -> Result<()> {
|
||||
let mut mango_account = ctx.accounts.account.load_mut()?;
|
||||
require!(mango_account.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!mango_account.is_bankrupt(), MangoError::IsBankrupt);
|
||||
|
||||
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||
let bids = ctx.accounts.bids.load_mut()?;
|
||||
|
|
|
@ -34,7 +34,7 @@ pub fn perp_cancel_all_orders_by_side(
|
|||
limit: u8,
|
||||
) -> Result<()> {
|
||||
let mut mango_account = ctx.accounts.account.load_mut()?;
|
||||
require!(mango_account.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!mango_account.is_bankrupt(), MangoError::IsBankrupt);
|
||||
|
||||
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||
let bids = ctx.accounts.bids.load_mut()?;
|
||||
|
|
|
@ -30,7 +30,7 @@ pub struct PerpCancelOrder<'info> {
|
|||
|
||||
pub fn perp_cancel_order(ctx: Context<PerpCancelOrder>, order_id: i128) -> Result<()> {
|
||||
let mut mango_account = ctx.accounts.account.load_mut()?;
|
||||
require!(mango_account.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!mango_account.is_bankrupt(), MangoError::IsBankrupt);
|
||||
|
||||
let perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||
let bids = ctx.accounts.bids.load_mut()?;
|
||||
|
|
|
@ -33,7 +33,7 @@ pub fn perp_cancel_order_by_client_order_id(
|
|||
client_order_id: u64,
|
||||
) -> Result<()> {
|
||||
let mut mango_account = ctx.accounts.account.load_mut()?;
|
||||
require!(mango_account.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!mango_account.is_bankrupt(), MangoError::IsBankrupt);
|
||||
|
||||
let perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||
let bids = ctx.accounts.bids.load_mut()?;
|
||||
|
|
|
@ -50,7 +50,6 @@ pub fn perp_create_market(
|
|||
oracle_config: OracleConfig,
|
||||
base_token_index_opt: Option<TokenIndex>,
|
||||
base_token_decimals: u8,
|
||||
quote_token_index: TokenIndex,
|
||||
quote_lot_size: i64,
|
||||
base_lot_size: i64,
|
||||
maint_asset_weight: f32,
|
||||
|
@ -96,7 +95,8 @@ pub fn perp_create_market(
|
|||
base_token_decimals,
|
||||
perp_market_index,
|
||||
base_token_index: base_token_index_opt.ok_or(TokenIndex::MAX).unwrap(),
|
||||
quote_token_index,
|
||||
padding: Default::default(),
|
||||
reserved: Default::default(),
|
||||
};
|
||||
|
||||
let mut bids = ctx.accounts.bids.load_init()?;
|
||||
|
|
|
@ -79,7 +79,7 @@ pub fn perp_place_order(
|
|||
limit: u8,
|
||||
) -> Result<()> {
|
||||
let mut mango_account = ctx.accounts.account.load_mut()?;
|
||||
require!(mango_account.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!mango_account.is_bankrupt(), MangoError::IsBankrupt);
|
||||
let mango_account_pk = ctx.accounts.account.key();
|
||||
|
||||
{
|
||||
|
|
|
@ -50,7 +50,7 @@ pub fn serum3_cancel_all_orders(ctx: Context<Serum3CancelAllOrders>, limit: u8)
|
|||
//
|
||||
{
|
||||
let account = ctx.accounts.account.load()?;
|
||||
require!(account.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
|
||||
|
||||
let serum_market = ctx.accounts.serum_market.load()?;
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ pub fn serum3_cancel_order(
|
|||
//
|
||||
{
|
||||
let account = ctx.accounts.account.load()?;
|
||||
require!(account.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
|
||||
|
||||
// Validate open_orders
|
||||
require!(
|
||||
|
|
|
@ -41,7 +41,7 @@ pub fn serum3_close_open_orders(ctx: Context<Serum3CloseOpenOrders>) -> Result<(
|
|||
//
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
let serum_market = ctx.accounts.serum_market.load()?;
|
||||
require!(account.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
|
||||
// Validate open_orders
|
||||
require!(
|
||||
account
|
||||
|
|
|
@ -51,7 +51,7 @@ pub fn serum3_create_open_orders(ctx: Context<Serum3CreateOpenOrders>) -> Result
|
|||
|
||||
let serum_market = ctx.accounts.serum_market.load()?;
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
require!(account.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
|
||||
let serum_account = account.serum3.create(serum_market.market_index)?;
|
||||
serum_account.open_orders = ctx.accounts.open_orders.key();
|
||||
serum_account.base_token_index = serum_market.base_token_index;
|
||||
|
|
|
@ -73,7 +73,7 @@ pub fn serum3_liq_force_cancel_orders(
|
|||
//
|
||||
{
|
||||
let account = ctx.accounts.account.load()?;
|
||||
require!(account.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
|
||||
let serum_market = ctx.accounts.serum_market.load()?;
|
||||
|
||||
// Validate open_orders
|
||||
|
|
|
@ -168,7 +168,7 @@ pub fn serum3_place_order(
|
|||
//
|
||||
{
|
||||
let account = ctx.accounts.account.load()?;
|
||||
require!(account.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
|
||||
|
||||
// Validate open_orders
|
||||
require!(
|
||||
|
|
|
@ -74,6 +74,7 @@ pub fn serum3_register_market(
|
|||
base_token_index: base_bank.token_index,
|
||||
quote_token_index: quote_bank.token_index,
|
||||
bump: *ctx.bumps.get("serum_market").ok_or(MangoError::SomeError)?,
|
||||
padding: Default::default(),
|
||||
reserved: Default::default(),
|
||||
};
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
|
|||
//
|
||||
{
|
||||
let account = ctx.accounts.account.load()?;
|
||||
require!(account.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
|
||||
|
||||
// Validate open_orders
|
||||
require!(
|
||||
|
|
|
@ -60,7 +60,7 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
|
|||
|
||||
// Get the account's position for that token index
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
require!(account.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
|
||||
|
||||
let (position, raw_token_index, active_token_index) =
|
||||
account.tokens.get_mut_or_create(token_index)?;
|
||||
|
|
|
@ -40,11 +40,7 @@ pub fn token_deregister<'key, 'accounts, 'remaining, 'info>(
|
|||
) -> Result<()> {
|
||||
let mint_info = ctx.accounts.mint_info.load()?;
|
||||
{
|
||||
let total_banks = mint_info
|
||||
.banks
|
||||
.iter()
|
||||
.filter(|bank| *bank != &Pubkey::default())
|
||||
.count();
|
||||
let total_banks = mint_info.num_banks();
|
||||
require_eq!(total_banks * 2, ctx.remaining_accounts.len());
|
||||
}
|
||||
|
||||
|
|
|
@ -106,6 +106,14 @@ pub fn token_register(
|
|||
|
||||
require_eq!(bank_num, 0);
|
||||
|
||||
// Require token 0 to be in the insurance token
|
||||
if token_index == QUOTE_TOKEN_INDEX {
|
||||
require_keys_eq!(
|
||||
ctx.accounts.group.load()?.insurance_mint,
|
||||
ctx.accounts.mint.key()
|
||||
);
|
||||
}
|
||||
|
||||
let mut bank = ctx.accounts.bank.load_init()?;
|
||||
*bank = Bank {
|
||||
name: fill16_from_str(name)?,
|
||||
|
@ -162,6 +170,7 @@ pub fn token_register(
|
|||
token_index,
|
||||
address_lookup_table_bank_index: alt_previous_size as u8,
|
||||
address_lookup_table_oracle_index: alt_previous_size as u8 + 1,
|
||||
padding: Default::default(),
|
||||
reserved: Default::default(),
|
||||
};
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
|
|||
|
||||
// Get the account's position for that token index
|
||||
let mut account = ctx.accounts.account.load_mut()?;
|
||||
require!(account.is_bankrupt == 0, MangoError::IsBankrupt);
|
||||
require!(!account.is_bankrupt(), MangoError::IsBankrupt);
|
||||
|
||||
let (position, raw_token_index, active_token_index) =
|
||||
account.tokens.get_mut_or_create(token_index)?;
|
||||
|
|
|
@ -302,6 +302,14 @@ pub mod mango_v4 {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn liq_token_bankruptcy(
|
||||
ctx: Context<LiqTokenBankruptcy>,
|
||||
liab_token_index: TokenIndex,
|
||||
max_liab_transfer: I80F48,
|
||||
) -> Result<()> {
|
||||
instructions::liq_token_bankruptcy(ctx, liab_token_index, max_liab_transfer)
|
||||
}
|
||||
|
||||
///
|
||||
/// Perps
|
||||
///
|
||||
|
@ -314,7 +322,6 @@ pub mod mango_v4 {
|
|||
oracle_config: OracleConfig,
|
||||
base_token_index_opt: Option<TokenIndex>,
|
||||
base_token_decimals: u8,
|
||||
quote_token_index: TokenIndex,
|
||||
quote_lot_size: i64,
|
||||
base_lot_size: i64,
|
||||
maint_asset_weight: f32,
|
||||
|
@ -335,7 +342,6 @@ pub mod mango_v4 {
|
|||
oracle_config,
|
||||
base_token_index_opt,
|
||||
base_token_decimals,
|
||||
quote_token_index,
|
||||
quote_lot_size,
|
||||
base_lot_size,
|
||||
maint_asset_weight,
|
||||
|
|
|
@ -13,9 +13,11 @@ pub const YEAR: I80F48 = I80F48!(31536000);
|
|||
|
||||
#[account(zero_copy)]
|
||||
pub struct Bank {
|
||||
// ABI: Clients rely on this being at offset 8
|
||||
pub group: Pubkey,
|
||||
|
||||
pub name: [u8; 16],
|
||||
|
||||
pub group: Pubkey,
|
||||
pub mint: Pubkey,
|
||||
pub vault: Pubkey,
|
||||
pub oracle: Pubkey,
|
||||
|
@ -34,6 +36,14 @@ pub struct Bank {
|
|||
pub cached_indexed_total_borrows: I80F48,
|
||||
|
||||
/// deposits/borrows for this bank
|
||||
///
|
||||
/// Note that these may become negative. It's perfectly fine for users to borrow one one bank
|
||||
/// (increasing indexed_borrows there) and paying back on another (possibly decreasing indexed_borrows
|
||||
/// below zero).
|
||||
///
|
||||
/// The vault amount is not deducable from these values.
|
||||
///
|
||||
/// These become meaningful when summed over all banks (like in update_index).
|
||||
pub indexed_deposits: I80F48,
|
||||
pub indexed_borrows: I80F48,
|
||||
|
||||
|
@ -200,14 +210,28 @@ impl Bank {
|
|||
require!(native_amount >= 0, MangoError::SomeError);
|
||||
let native_position = position.native(self);
|
||||
|
||||
// Adding DELTA to amount/index helps because (amount/index)*index <= amount, but
|
||||
// we want to ensure that users can withdraw the same amount they have deposited, so
|
||||
// (amount/index + delta)*index >= amount is a better guarantee.
|
||||
// Additionally, we require that we don't adjust values if
|
||||
// (native / index) * index == native, because we sometimes call this function with
|
||||
// values that are products of index.
|
||||
let div_rounding_up = |native: I80F48, index: I80F48| {
|
||||
let indexed = cm!(native / index);
|
||||
if cm!(indexed * index) < native {
|
||||
cm!(indexed + I80F48::DELTA)
|
||||
} else {
|
||||
indexed
|
||||
}
|
||||
};
|
||||
|
||||
if native_position.is_negative() {
|
||||
let new_native_position = cm!(native_position + native_amount);
|
||||
let indexed_change = cm!(native_amount / self.borrow_index + I80F48::DELTA);
|
||||
let indexed_change = div_rounding_up(native_amount, self.borrow_index);
|
||||
// this is only correct if it's not positive, because it scales the whole amount by borrow_index
|
||||
let new_indexed_value = cm!(position.indexed_position + indexed_change);
|
||||
if new_indexed_value.is_negative() {
|
||||
// pay back borrows only, leaving a negative position
|
||||
let indexed_change = cm!(native_amount / self.borrow_index + I80F48::DELTA);
|
||||
self.indexed_borrows = cm!(self.indexed_borrows - indexed_change);
|
||||
position.indexed_position = cm!(position.indexed_position + indexed_change);
|
||||
return Ok(true);
|
||||
|
@ -227,10 +251,7 @@ impl Bank {
|
|||
}
|
||||
|
||||
// add to deposits
|
||||
// Adding DELTA to amount/index helps because (amount/index)*index <= amount, but
|
||||
// we want to ensure that users can withdraw the same amount they have deposited, so
|
||||
// (amount/index + delta)*index >= amount is a better guarantee.
|
||||
let indexed_change = cm!(native_amount / self.deposit_index + I80F48::DELTA);
|
||||
let indexed_change = div_rounding_up(native_amount, self.deposit_index);
|
||||
self.indexed_deposits = cm!(self.indexed_deposits + indexed_change);
|
||||
position.indexed_position = cm!(position.indexed_position + indexed_change);
|
||||
|
||||
|
|
|
@ -4,23 +4,30 @@ use std::mem::size_of;
|
|||
|
||||
// TODO: Assuming we allow up to 65536 different tokens
|
||||
pub type TokenIndex = u16;
|
||||
pub const QUOTE_TOKEN_INDEX: TokenIndex = 0;
|
||||
|
||||
#[account(zero_copy)]
|
||||
#[derive(Debug)]
|
||||
pub struct Group {
|
||||
// Relying on Anchor's discriminator be sufficient for our versioning needs?
|
||||
// pub meta_data: MetaData,
|
||||
// ABI: Clients rely on this being at offset 8
|
||||
pub admin: Pubkey,
|
||||
|
||||
// ABI: Clients rely on this being at offset 40
|
||||
pub group_num: u32,
|
||||
|
||||
pub padding: [u8; 4],
|
||||
|
||||
pub insurance_vault: Pubkey,
|
||||
pub insurance_mint: Pubkey,
|
||||
|
||||
pub bump: u8,
|
||||
// Only support closing/deregistering groups, stub oracles, tokens, and markets
|
||||
// if testing == 1
|
||||
pub testing: u8,
|
||||
pub padding: [u8; 2],
|
||||
pub group_num: u32,
|
||||
pub padding2: [u8; 6],
|
||||
pub reserved: [u8; 8],
|
||||
}
|
||||
const_assert_eq!(size_of::<Group>(), 48);
|
||||
const_assert_eq!(size_of::<Group>(), 32 * 3 + 4 + 4 + 1 * 2 + 6 + 8);
|
||||
const_assert_eq!(size_of::<Group>() % 8, 0);
|
||||
|
||||
#[macro_export]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
use fixed::types::I80F48;
|
||||
use fixed_macro::types::I80F48;
|
||||
use serum_dex::state::OpenOrders;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
@ -11,6 +12,8 @@ use crate::serum3_cpi;
|
|||
use crate::state::{oracle_price, Bank, MangoAccount, PerpMarket, PerpMarketIndex, TokenIndex};
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
const BANKRUPTCY_DUST_THRESHOLD: I80F48 = I80F48!(0.000001);
|
||||
|
||||
/// This trait abstracts how to find accounts needed for the health computation.
|
||||
///
|
||||
/// There are different ways they are retrieved from remainingAccounts, based
|
||||
|
@ -216,7 +219,17 @@ impl<'a, 'info> ScanningAccountRetriever<'a, 'info> {
|
|||
&mut self,
|
||||
token_index1: TokenIndex,
|
||||
token_index2: TokenIndex,
|
||||
) -> Result<(&mut Bank, &mut Bank, I80F48, I80F48)> {
|
||||
) -> Result<(&mut Bank, I80F48, Option<(&mut Bank, I80F48)>)> {
|
||||
let n_banks = self.n_banks();
|
||||
if token_index1 == token_index2 {
|
||||
let index = self.bank_index(token_index1)?;
|
||||
let (bank_part, oracle_part) = self.ais.split_at_mut(index + 1);
|
||||
let bank = bank_part[index].load_mut_fully_unchecked::<Bank>()?;
|
||||
let oracle = &oracle_part[n_banks - 1];
|
||||
require!(&bank.oracle == oracle.key, MangoError::SomeError);
|
||||
let price = oracle_price(oracle, bank.oracle_config.conf_filter, bank.mint_decimals)?;
|
||||
return Ok((bank, price, None));
|
||||
}
|
||||
let index1 = self.bank_index(token_index1)?;
|
||||
let index2 = self.bank_index(token_index2)?;
|
||||
let (first, second, swap) = if index1 < index2 {
|
||||
|
@ -224,7 +237,6 @@ impl<'a, 'info> ScanningAccountRetriever<'a, 'info> {
|
|||
} else {
|
||||
(index2, index1, true)
|
||||
};
|
||||
let n_banks = self.n_banks();
|
||||
|
||||
// split_at_mut after the first bank and after the second bank
|
||||
let (first_bank_part, second_part) = self.ais.split_at_mut(first + 1);
|
||||
|
@ -241,9 +253,9 @@ impl<'a, 'info> ScanningAccountRetriever<'a, 'info> {
|
|||
let price1 = oracle_price(oracle1, bank1.oracle_config.conf_filter, mint_decimals1)?;
|
||||
let price2 = oracle_price(oracle2, bank2.oracle_config.conf_filter, mint_decimals2)?;
|
||||
if swap {
|
||||
Ok((bank2, bank1, price2, price1))
|
||||
Ok((bank2, price2, Some((bank1, price1))))
|
||||
} else {
|
||||
Ok((bank1, bank2, price1, price2))
|
||||
Ok((bank1, price1, Some((bank2, price2))))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -493,6 +505,28 @@ impl HealthCache {
|
|||
entry.balance = cm!(entry.balance + change * entry.oracle_price);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn has_liquidatable_assets(&self) -> bool {
|
||||
let spot_liquidatable = self.token_infos.iter().any(|ti| {
|
||||
ti.balance > BANKRUPTCY_DUST_THRESHOLD || ti.serum3_max_reserved.is_positive()
|
||||
});
|
||||
let perp_liquidatable = self
|
||||
.perp_infos
|
||||
.iter()
|
||||
.any(|p| p.base != 0 || p.quote > BANKRUPTCY_DUST_THRESHOLD);
|
||||
spot_liquidatable || perp_liquidatable
|
||||
}
|
||||
|
||||
pub fn has_borrows(&self) -> bool {
|
||||
// AUDIT: Can we really guarantee that liquidation/bankruptcy resolution always leaves
|
||||
// non-negative balances?
|
||||
let spot_borrows = self.token_infos.iter().any(|ti| ti.balance.is_negative());
|
||||
let perp_borrows = self
|
||||
.perp_infos
|
||||
.iter()
|
||||
.any(|p| p.quote.is_negative() || p.base != 0);
|
||||
spot_borrows || perp_borrows
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute health contribution for a given balance
|
||||
|
@ -835,7 +869,6 @@ mod tests {
|
|||
perp1.data().group = group;
|
||||
perp1.data().perp_market_index = 9;
|
||||
perp1.data().base_token_index = 4;
|
||||
perp1.data().quote_token_index = 1;
|
||||
perp1.data().init_asset_weight = I80F48::from_num(1.0 - 0.2f64);
|
||||
perp1.data().init_liab_weight = I80F48::from_num(1.0 + 0.2f64);
|
||||
perp1.data().maint_asset_weight = I80F48::from_num(1.0 - 0.1f64);
|
||||
|
@ -914,7 +947,8 @@ mod tests {
|
|||
assert_eq!(retriever.perp_index_map.len(), 1);
|
||||
|
||||
{
|
||||
let (b1, b2, o1, o2) = retriever.banks_mut_and_oracles(1, 4).unwrap();
|
||||
let (b1, o1, opt_b2o2) = retriever.banks_mut_and_oracles(1, 4).unwrap();
|
||||
let (b2, o2) = opt_b2o2.unwrap();
|
||||
assert_eq!(b1.token_index, 1);
|
||||
assert_eq!(o1, I80F48::ONE);
|
||||
assert_eq!(b2.token_index, 4);
|
||||
|
@ -922,13 +956,21 @@ mod tests {
|
|||
}
|
||||
|
||||
{
|
||||
let (b1, b2, o1, o2) = retriever.banks_mut_and_oracles(4, 1).unwrap();
|
||||
let (b1, o1, opt_b2o2) = retriever.banks_mut_and_oracles(4, 1).unwrap();
|
||||
let (b2, o2) = opt_b2o2.unwrap();
|
||||
assert_eq!(b1.token_index, 4);
|
||||
assert_eq!(o1, 5 * I80F48::ONE);
|
||||
assert_eq!(b2.token_index, 1);
|
||||
assert_eq!(o2, I80F48::ONE);
|
||||
}
|
||||
|
||||
{
|
||||
let (b1, o1, opt_b2o2) = retriever.banks_mut_and_oracles(4, 4).unwrap();
|
||||
assert!(opt_b2o2.is_none());
|
||||
assert_eq!(b1.token_index, 4);
|
||||
assert_eq!(o1, 5 * I80F48::ONE);
|
||||
}
|
||||
|
||||
retriever.banks_mut_and_oracles(4, 2).unwrap_err();
|
||||
|
||||
let oo = retriever.serum_oo(0, &oo1key).unwrap();
|
||||
|
@ -1001,7 +1043,6 @@ mod tests {
|
|||
perp1.data().group = group;
|
||||
perp1.data().perp_market_index = 9;
|
||||
perp1.data().base_token_index = 4;
|
||||
perp1.data().quote_token_index = 1;
|
||||
perp1.data().init_asset_weight = I80F48::from_num(1.0 - 0.2f64);
|
||||
perp1.data().init_liab_weight = I80F48::from_num(1.0 + 0.2f64);
|
||||
perp1.data().maint_asset_weight = I80F48::from_num(1.0 - 0.1f64);
|
||||
|
|
|
@ -704,11 +704,14 @@ impl Default for MangoAccountPerpPositions {
|
|||
|
||||
#[account(zero_copy)]
|
||||
pub struct MangoAccount {
|
||||
pub name: [u8; 32],
|
||||
|
||||
// ABI: Clients rely on this being at offset 8
|
||||
pub group: Pubkey,
|
||||
|
||||
// ABI: Clients rely on this being at offset 40
|
||||
pub owner: Pubkey,
|
||||
|
||||
pub name: [u8; 32],
|
||||
|
||||
// Alternative authority/signer of transactions for a mango account
|
||||
pub delegate: Pubkey,
|
||||
|
||||
|
@ -723,10 +726,10 @@ pub struct MangoAccount {
|
|||
pub perps: MangoAccountPerpPositions,
|
||||
|
||||
/// This account cannot open new positions or borrow until `init_health >= 0`
|
||||
pub being_liquidated: u8,
|
||||
being_liquidated: u8,
|
||||
|
||||
/// This account cannot do anything except go through `resolve_bankruptcy`
|
||||
pub is_bankrupt: u8,
|
||||
is_bankrupt: u8,
|
||||
|
||||
pub account_num: u8,
|
||||
pub bump: u8,
|
||||
|
@ -774,6 +777,22 @@ impl MangoAccount {
|
|||
pub fn is_owner_or_delegate(&self, ix_signer: Pubkey) -> bool {
|
||||
self.owner == ix_signer || self.delegate == ix_signer
|
||||
}
|
||||
|
||||
pub fn is_bankrupt(&self) -> bool {
|
||||
self.is_bankrupt != 0
|
||||
}
|
||||
|
||||
pub fn set_bankrupt(&mut self, b: bool) {
|
||||
self.is_bankrupt = if b { 1 } else { 0 };
|
||||
}
|
||||
|
||||
pub fn being_liquidated(&self) -> bool {
|
||||
self.being_liquidated != 0
|
||||
}
|
||||
|
||||
pub fn set_being_liquidated(&mut self, b: bool) {
|
||||
self.being_liquidated = if b { 1 } else { 0 };
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MangoAccount {
|
||||
|
|
|
@ -2,9 +2,9 @@ use anchor_lang::prelude::*;
|
|||
use static_assertions::const_assert_eq;
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::{accounts_zerocopy::LoadZeroCopyRef, error::MangoError};
|
||||
use crate::error::MangoError;
|
||||
|
||||
use super::{Bank, TokenIndex};
|
||||
use super::TokenIndex;
|
||||
|
||||
pub const MAX_BANKS: usize = 6;
|
||||
|
||||
|
@ -15,25 +15,28 @@ pub const MAX_BANKS: usize = 6;
|
|||
#[account(zero_copy)]
|
||||
#[derive(Debug)]
|
||||
pub struct MintInfo {
|
||||
// TODO: none of these pubkeys are needed, remove?
|
||||
// ABI: Clients rely on this being at offset 8
|
||||
pub group: Pubkey,
|
||||
|
||||
// ABI: Clients rely on this being at offset 40
|
||||
pub token_index: TokenIndex,
|
||||
|
||||
pub padding: [u8; 6],
|
||||
pub mint: Pubkey,
|
||||
pub banks: [Pubkey; MAX_BANKS],
|
||||
pub vaults: [Pubkey; MAX_BANKS],
|
||||
pub oracle: Pubkey,
|
||||
pub address_lookup_table: Pubkey,
|
||||
|
||||
pub token_index: TokenIndex,
|
||||
|
||||
// describe what address map relevant accounts are found on
|
||||
pub address_lookup_table_bank_index: u8,
|
||||
pub address_lookup_table_oracle_index: u8,
|
||||
|
||||
pub reserved: [u8; 4],
|
||||
pub reserved: [u8; 6],
|
||||
}
|
||||
const_assert_eq!(
|
||||
size_of::<MintInfo>(),
|
||||
MAX_BANKS * 2 * 32 + 4 * 32 + 2 + 2 + 4
|
||||
MAX_BANKS * 2 * 32 + 4 * 32 + 2 + 6 + 2 + 6
|
||||
);
|
||||
const_assert_eq!(size_of::<MintInfo>() % 8, 0);
|
||||
|
||||
|
@ -47,29 +50,22 @@ impl MintInfo {
|
|||
self.vaults[0]
|
||||
}
|
||||
|
||||
pub fn verify_banks_ais(&self, all_bank_ais: &[AccountInfo]) -> Result<()> {
|
||||
let total_banks = self
|
||||
.banks
|
||||
pub fn num_banks(&self) -> usize {
|
||||
self.banks
|
||||
.iter()
|
||||
.filter(|bank| *bank != &Pubkey::default())
|
||||
.count();
|
||||
require_eq!(total_banks, all_bank_ais.len());
|
||||
.position(|&b| b == Pubkey::default())
|
||||
.unwrap_or(MAX_BANKS)
|
||||
}
|
||||
|
||||
for (idx, ai) in all_bank_ais.iter().enumerate() {
|
||||
match ai.load::<Bank>() {
|
||||
Ok(bank) => {
|
||||
if self.token_index != bank.token_index
|
||||
|| self.group != bank.group
|
||||
// todo: just below check should be enough, above 2 checks are superfluous and defensive
|
||||
|| self.banks[idx] != ai.key()
|
||||
{
|
||||
return Err(error!(MangoError::SomeError));
|
||||
}
|
||||
}
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
pub fn banks(&self) -> &[Pubkey] {
|
||||
&self.banks[..self.num_banks()]
|
||||
}
|
||||
|
||||
pub fn verify_banks_ais(&self, all_bank_ais: &[AccountInfo]) -> Result<()> {
|
||||
require!(
|
||||
all_bank_ais.iter().map(|ai| ai.key).eq(self.banks().iter()),
|
||||
MangoError::SomeError
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,9 @@ pub enum OracleType {
|
|||
|
||||
#[account(zero_copy)]
|
||||
pub struct StubOracle {
|
||||
// ABI: Clients rely on this being at offset 8
|
||||
pub group: Pubkey,
|
||||
// ABI: Clients rely on this being at offset 40
|
||||
pub mint: Pubkey,
|
||||
pub price: I80F48,
|
||||
pub last_updated: i64,
|
||||
|
|
|
@ -16,10 +16,20 @@ pub type PerpMarketIndex = u16;
|
|||
#[account(zero_copy)]
|
||||
#[derive(Debug)]
|
||||
pub struct PerpMarket {
|
||||
pub name: [u8; 16],
|
||||
|
||||
// ABI: Clients rely on this being at offset 8
|
||||
pub group: Pubkey,
|
||||
|
||||
// TODO: Remove!
|
||||
// ABI: Clients rely on this being at offset 40
|
||||
pub base_token_index: TokenIndex,
|
||||
|
||||
/// Lookup indices
|
||||
pub perp_market_index: PerpMarketIndex,
|
||||
|
||||
pub padding: [u8; 4],
|
||||
|
||||
pub name: [u8; 16],
|
||||
|
||||
pub oracle: Pubkey,
|
||||
|
||||
pub oracle_config: OracleConfig,
|
||||
|
@ -76,18 +86,12 @@ pub struct PerpMarket {
|
|||
|
||||
pub base_token_decimals: u8,
|
||||
|
||||
/// Lookup indices
|
||||
pub perp_market_index: PerpMarketIndex,
|
||||
|
||||
pub base_token_index: TokenIndex,
|
||||
|
||||
/// Cannot be chosen freely, must be the health-reference token, same for all PerpMarkets
|
||||
pub quote_token_index: TokenIndex,
|
||||
pub reserved: [u8; 6],
|
||||
}
|
||||
|
||||
const_assert_eq!(
|
||||
size_of::<PerpMarket>(),
|
||||
16 + 32 * 2 + 16 + 32 * 3 + 8 * 2 + 16 * 11 + 8 * 2 + 8 * 2 + 16 + 8
|
||||
32 + 2 + 2 + 4 + 16 + 32 + 16 + 32 * 3 + 8 * 2 + 16 * 11 + 8 * 2 + 8 * 2 + 16 + 2 + 6
|
||||
);
|
||||
const_assert_eq!(size_of::<PerpMarket>() % 8, 0);
|
||||
|
||||
|
|
|
@ -9,19 +9,26 @@ pub type Serum3MarketIndex = u16;
|
|||
#[account(zero_copy)]
|
||||
#[derive(Debug)]
|
||||
pub struct Serum3Market {
|
||||
pub name: [u8; 16],
|
||||
// ABI: Clients rely on this being at offset 8
|
||||
pub group: Pubkey,
|
||||
// ABI: Clients rely on this being at offset 40
|
||||
pub base_token_index: TokenIndex,
|
||||
// ABI: Clients rely on this being at offset 42
|
||||
pub quote_token_index: TokenIndex,
|
||||
pub padding: [u8; 4],
|
||||
pub name: [u8; 16],
|
||||
pub serum_program: Pubkey,
|
||||
pub serum_market_external: Pubkey,
|
||||
|
||||
pub market_index: Serum3MarketIndex,
|
||||
pub base_token_index: TokenIndex,
|
||||
pub quote_token_index: TokenIndex,
|
||||
|
||||
pub bump: u8,
|
||||
pub reserved: [u8; 1],
|
||||
pub reserved: [u8; 5],
|
||||
}
|
||||
const_assert_eq!(size_of::<Serum3Market>(), 16 + 32 * 3 + 3 * 2 + 1 + 1);
|
||||
const_assert_eq!(
|
||||
size_of::<Serum3Market>(),
|
||||
32 + 2 + 2 + 4 + 16 + 2 * 32 + 2 + 1 + 5
|
||||
);
|
||||
const_assert_eq!(size_of::<Serum3Market>() % 8, 0);
|
||||
|
||||
impl Serum3Market {
|
||||
|
|
|
@ -231,7 +231,9 @@ async fn derive_liquidation_remaining_account_metas(
|
|||
liqee: &MangoAccount,
|
||||
liqor: &MangoAccount,
|
||||
asset_token_index: TokenIndex,
|
||||
asset_bank_index: usize,
|
||||
liab_token_index: TokenIndex,
|
||||
liab_bank_index: usize,
|
||||
) -> Vec<AccountMeta> {
|
||||
let mut banks = vec![];
|
||||
let mut oracles = vec![];
|
||||
|
@ -243,7 +245,13 @@ async fn derive_liquidation_remaining_account_metas(
|
|||
.unique();
|
||||
for token_index in token_indexes {
|
||||
let mint_info = get_mint_info_by_token_index(account_loader, liqee, token_index).await;
|
||||
let writable_bank = token_index == asset_token_index || token_index == liab_token_index;
|
||||
let (bank_index, writable_bank) = if token_index == asset_token_index {
|
||||
(asset_bank_index, true)
|
||||
} else if token_index == liab_token_index {
|
||||
(liab_bank_index, true)
|
||||
} else {
|
||||
(0, false)
|
||||
};
|
||||
// TODO: ALTs are unavailable
|
||||
// let lookup_table = account_loader
|
||||
// .load_bytes(&mint_info.address_lookup_table)
|
||||
|
@ -255,7 +263,7 @@ async fn derive_liquidation_remaining_account_metas(
|
|||
// writable_bank,
|
||||
// ));
|
||||
// oracles.push(addresses[mint_info.address_lookup_table_oracle_index as usize]);
|
||||
banks.push((mint_info.first_bank(), writable_bank));
|
||||
banks.push((mint_info.banks[bank_index], writable_bank));
|
||||
oracles.push(mint_info.oracle);
|
||||
}
|
||||
|
||||
|
@ -306,6 +314,12 @@ pub async fn account_position(solana: &SolanaCookie, account: Pubkey, bank: Pubk
|
|||
native.round().to_num::<i64>()
|
||||
}
|
||||
|
||||
pub async fn account_position_closed(solana: &SolanaCookie, account: Pubkey, bank: Pubkey) -> bool {
|
||||
let account_data: MangoAccount = solana.get_account(account).await;
|
||||
let bank_data: Bank = solana.get_account(bank).await;
|
||||
account_data.tokens.find(bank_data.token_index).is_none()
|
||||
}
|
||||
|
||||
pub async fn account_position_f64(solana: &SolanaCookie, account: Pubkey, bank: Pubkey) -> f64 {
|
||||
let account_data: MangoAccount = solana.get_account(account).await;
|
||||
let bank_data: Bank = solana.get_account(bank).await;
|
||||
|
@ -627,6 +641,7 @@ pub struct TokenWithdrawInstruction<'keypair> {
|
|||
pub account: Pubkey,
|
||||
pub owner: &'keypair Keypair,
|
||||
pub token_account: Pubkey,
|
||||
pub bank_index: usize,
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl<'keypair> ClientInstruction for TokenWithdrawInstruction<'keypair> {
|
||||
|
@ -659,7 +674,7 @@ impl<'keypair> ClientInstruction for TokenWithdrawInstruction<'keypair> {
|
|||
let health_check_metas = derive_health_check_remaining_account_metas(
|
||||
&account_loader,
|
||||
&account,
|
||||
Some(mint_info.first_bank()),
|
||||
Some(mint_info.banks[self.bank_index]),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
|
@ -669,8 +684,8 @@ impl<'keypair> ClientInstruction for TokenWithdrawInstruction<'keypair> {
|
|||
group: account.group,
|
||||
account: self.account,
|
||||
owner: self.owner.pubkey(),
|
||||
bank: mint_info.first_bank(),
|
||||
vault: mint_info.first_vault(),
|
||||
bank: mint_info.banks[self.bank_index],
|
||||
vault: mint_info.vaults[self.bank_index],
|
||||
token_account: self.token_account,
|
||||
token_program: Token::id(),
|
||||
};
|
||||
|
@ -686,15 +701,16 @@ impl<'keypair> ClientInstruction for TokenWithdrawInstruction<'keypair> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct TokenDepositInstruction<'keypair> {
|
||||
pub struct TokenDepositInstruction {
|
||||
pub amount: u64,
|
||||
|
||||
pub account: Pubkey,
|
||||
pub token_account: Pubkey,
|
||||
pub token_authority: &'keypair Keypair,
|
||||
pub token_authority: Keypair,
|
||||
pub bank_index: usize,
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl<'keypair> ClientInstruction for TokenDepositInstruction<'keypair> {
|
||||
impl ClientInstruction for TokenDepositInstruction {
|
||||
type Accounts = mango_v4::accounts::TokenDeposit;
|
||||
type Instruction = mango_v4::instruction::TokenDeposit;
|
||||
async fn to_instruction(
|
||||
|
@ -723,7 +739,7 @@ impl<'keypair> ClientInstruction for TokenDepositInstruction<'keypair> {
|
|||
let health_check_metas = derive_health_check_remaining_account_metas(
|
||||
&account_loader,
|
||||
&account,
|
||||
Some(mint_info.first_bank()),
|
||||
Some(mint_info.banks[self.bank_index]),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
|
@ -732,8 +748,8 @@ impl<'keypair> ClientInstruction for TokenDepositInstruction<'keypair> {
|
|||
let accounts = Self::Accounts {
|
||||
group: account.group,
|
||||
account: self.account,
|
||||
bank: mint_info.first_bank(),
|
||||
vault: mint_info.first_vault(),
|
||||
bank: mint_info.banks[self.bank_index],
|
||||
vault: mint_info.vaults[self.bank_index],
|
||||
token_account: self.token_account,
|
||||
token_authority: self.token_authority.pubkey(),
|
||||
token_program: Token::id(),
|
||||
|
@ -746,7 +762,7 @@ impl<'keypair> ClientInstruction for TokenDepositInstruction<'keypair> {
|
|||
}
|
||||
|
||||
fn signers(&self) -> Vec<&Keypair> {
|
||||
vec![self.token_authority]
|
||||
vec![&self.token_authority]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -877,7 +893,6 @@ pub struct TokenAddBankInstruction<'keypair> {
|
|||
|
||||
pub group: Pubkey,
|
||||
pub admin: &'keypair Keypair,
|
||||
pub mint: Pubkey,
|
||||
pub address_lookup_table: Pubkey,
|
||||
pub payer: &'keypair Keypair,
|
||||
}
|
||||
|
@ -887,7 +902,7 @@ impl<'keypair> ClientInstruction for TokenAddBankInstruction<'keypair> {
|
|||
type Instruction = mango_v4::instruction::TokenAddBank;
|
||||
async fn to_instruction(
|
||||
&self,
|
||||
_account_loader: impl ClientAccountLoader + 'async_trait,
|
||||
account_loader: impl ClientAccountLoader + 'async_trait,
|
||||
) -> (Self::Accounts, instruction::Instruction) {
|
||||
let program_id = mango_v4::id();
|
||||
let instruction = Self::Instruction {
|
||||
|
@ -925,12 +940,12 @@ impl<'keypair> ClientInstruction for TokenAddBankInstruction<'keypair> {
|
|||
&program_id,
|
||||
)
|
||||
.0;
|
||||
|
||||
let existing_bank_data: Bank = account_loader.load(&existing_bank).await.unwrap();
|
||||
let mint = existing_bank_data.mint;
|
||||
|
||||
let mint_info = Pubkey::find_program_address(
|
||||
&[
|
||||
self.group.as_ref(),
|
||||
b"MintInfo".as_ref(),
|
||||
self.mint.as_ref(),
|
||||
],
|
||||
&[self.group.as_ref(), b"MintInfo".as_ref(), mint.as_ref()],
|
||||
&program_id,
|
||||
)
|
||||
.0;
|
||||
|
@ -938,7 +953,7 @@ impl<'keypair> ClientInstruction for TokenAddBankInstruction<'keypair> {
|
|||
let accounts = Self::Accounts {
|
||||
group: self.group,
|
||||
admin: self.admin.pubkey(),
|
||||
mint: self.mint,
|
||||
mint: mint,
|
||||
existing_bank,
|
||||
bank,
|
||||
vault,
|
||||
|
@ -1172,6 +1187,7 @@ impl<'keypair> ClientInstruction for CloseStubOracleInstruction<'keypair> {
|
|||
pub struct CreateGroupInstruction<'keypair> {
|
||||
pub admin: &'keypair Keypair,
|
||||
pub payer: &'keypair Keypair,
|
||||
pub insurance_mint: Pubkey,
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl<'keypair> ClientInstruction for CreateGroupInstruction<'keypair> {
|
||||
|
@ -1197,11 +1213,21 @@ impl<'keypair> ClientInstruction for CreateGroupInstruction<'keypair> {
|
|||
)
|
||||
.0;
|
||||
|
||||
let insurance_vault = Pubkey::find_program_address(
|
||||
&[group.as_ref(), b"InsuranceVault".as_ref()],
|
||||
&program_id,
|
||||
)
|
||||
.0;
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group,
|
||||
admin: self.admin.pubkey(),
|
||||
insurance_mint: self.insurance_mint,
|
||||
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);
|
||||
|
@ -1984,7 +2010,9 @@ pub struct LiqTokenWithTokenInstruction<'keypair> {
|
|||
pub liqor_owner: &'keypair Keypair,
|
||||
|
||||
pub asset_token_index: TokenIndex,
|
||||
pub asset_bank_index: usize,
|
||||
pub liab_token_index: TokenIndex,
|
||||
pub liab_bank_index: usize,
|
||||
pub max_liab_transfer: I80F48,
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
|
@ -2009,7 +2037,9 @@ impl<'keypair> ClientInstruction for LiqTokenWithTokenInstruction<'keypair> {
|
|||
&liqee,
|
||||
&liqor,
|
||||
self.asset_token_index,
|
||||
self.asset_bank_index,
|
||||
self.liab_token_index,
|
||||
self.liab_bank_index,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
@ -2031,6 +2061,94 @@ impl<'keypair> ClientInstruction for LiqTokenWithTokenInstruction<'keypair> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct LiqTokenBankruptcyInstruction<'keypair> {
|
||||
pub liqee: Pubkey,
|
||||
pub liqor: Pubkey,
|
||||
pub liqor_owner: &'keypair Keypair,
|
||||
|
||||
pub liab_token_index: TokenIndex,
|
||||
pub max_liab_transfer: I80F48,
|
||||
pub liab_mint_info: Pubkey,
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl<'keypair> ClientInstruction for LiqTokenBankruptcyInstruction<'keypair> {
|
||||
type Accounts = mango_v4::accounts::LiqTokenBankruptcy;
|
||||
type Instruction = mango_v4::instruction::LiqTokenBankruptcy;
|
||||
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 {
|
||||
liab_token_index: self.liab_token_index,
|
||||
max_liab_transfer: self.max_liab_transfer,
|
||||
};
|
||||
|
||||
let liab_mint_info: MintInfo = account_loader.load(&self.liab_mint_info).await.unwrap();
|
||||
let liqee: MangoAccount = account_loader.load(&self.liqee).await.unwrap();
|
||||
let liqor: MangoAccount = account_loader.load(&self.liqor).await.unwrap();
|
||||
let health_check_metas = derive_liquidation_remaining_account_metas(
|
||||
&account_loader,
|
||||
&liqee,
|
||||
&liqor,
|
||||
QUOTE_TOKEN_INDEX,
|
||||
0,
|
||||
self.liab_token_index,
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
|
||||
let group: Group = account_loader.load(&liqee.group).await.unwrap();
|
||||
|
||||
let quote_mint_info = Pubkey::find_program_address(
|
||||
&[
|
||||
liqee.group.as_ref(),
|
||||
b"MintInfo".as_ref(),
|
||||
group.insurance_mint.as_ref(),
|
||||
],
|
||||
&program_id,
|
||||
)
|
||||
.0;
|
||||
let quote_mint_info: MintInfo = account_loader.load("e_mint_info).await.unwrap();
|
||||
|
||||
let insurance_vault = Pubkey::find_program_address(
|
||||
&[liqee.group.as_ref(), b"InsuranceVault".as_ref()],
|
||||
&program_id,
|
||||
)
|
||||
.0;
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: liqee.group,
|
||||
liqee: self.liqee,
|
||||
liqor: self.liqor,
|
||||
liqor_owner: self.liqor_owner.pubkey(),
|
||||
liab_mint_info: self.liab_mint_info,
|
||||
quote_vault: quote_mint_info.first_vault(),
|
||||
insurance_vault,
|
||||
token_program: Token::id(),
|
||||
};
|
||||
|
||||
let mut instruction = make_instruction(program_id, &accounts, instruction);
|
||||
let mut bank_ams = liab_mint_info
|
||||
.banks()
|
||||
.iter()
|
||||
.map(|bank| AccountMeta {
|
||||
pubkey: *bank,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
instruction.accounts.append(&mut bank_ams);
|
||||
instruction.accounts.extend(health_check_metas.into_iter());
|
||||
|
||||
(accounts, instruction)
|
||||
}
|
||||
|
||||
fn signers(&self) -> Vec<&Keypair> {
|
||||
vec![self.liqor_owner]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PerpCreateMarketInstruction<'keypair> {
|
||||
pub group: Pubkey,
|
||||
pub admin: &'keypair Keypair,
|
||||
|
@ -2042,7 +2160,6 @@ pub struct PerpCreateMarketInstruction<'keypair> {
|
|||
pub perp_market_index: PerpMarketIndex,
|
||||
pub base_token_index: TokenIndex,
|
||||
pub base_token_decimals: u8,
|
||||
pub quote_token_index: TokenIndex,
|
||||
pub quote_lot_size: i64,
|
||||
pub base_lot_size: i64,
|
||||
pub maint_asset_weight: f32,
|
||||
|
@ -2069,7 +2186,6 @@ impl<'keypair> ClientInstruction for PerpCreateMarketInstruction<'keypair> {
|
|||
},
|
||||
perp_market_index: self.perp_market_index,
|
||||
base_token_index_opt: Option::from(self.base_token_index),
|
||||
quote_token_index: self.quote_token_index,
|
||||
quote_lot_size: self.quote_lot_size,
|
||||
base_lot_size: self.base_lot_size,
|
||||
maint_asset_weight: self.maint_asset_weight,
|
||||
|
@ -2431,8 +2547,6 @@ impl ClientInstruction for BenchmarkInstruction {
|
|||
}
|
||||
pub struct UpdateIndexInstruction {
|
||||
pub mint_info: Pubkey,
|
||||
pub oracle: Pubkey,
|
||||
pub banks: Vec<Pubkey>,
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ClientInstruction for UpdateIndexInstruction {
|
||||
|
@ -2440,20 +2554,22 @@ impl ClientInstruction for UpdateIndexInstruction {
|
|||
type Instruction = mango_v4::instruction::UpdateIndex;
|
||||
async fn to_instruction(
|
||||
&self,
|
||||
_loader: impl ClientAccountLoader + 'async_trait,
|
||||
loader: impl ClientAccountLoader + 'async_trait,
|
||||
) -> (Self::Accounts, instruction::Instruction) {
|
||||
let program_id = mango_v4::id();
|
||||
let instruction = Self::Instruction {};
|
||||
|
||||
let mint_info: MintInfo = loader.load(&self.mint_info).await.unwrap();
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
mint_info: self.mint_info,
|
||||
oracle: self.oracle,
|
||||
oracle: mint_info.oracle,
|
||||
};
|
||||
|
||||
let mut instruction = make_instruction(program_id, &accounts, instruction);
|
||||
let mut bank_ams = self
|
||||
.banks
|
||||
let mut bank_ams = mint_info
|
||||
.banks()
|
||||
.iter()
|
||||
.filter(|bank| **bank != Pubkey::default())
|
||||
.map(|bank| AccountMeta {
|
||||
pubkey: *bank,
|
||||
is_signer: false,
|
||||
|
|
|
@ -18,12 +18,14 @@ pub struct Token {
|
|||
pub mint: MintCookie,
|
||||
pub oracle: Pubkey,
|
||||
pub bank: Pubkey,
|
||||
pub bank1: Pubkey,
|
||||
pub vault: Pubkey,
|
||||
pub mint_info: Pubkey,
|
||||
}
|
||||
|
||||
pub struct GroupWithTokens {
|
||||
pub group: Pubkey,
|
||||
pub insurance_vault: Pubkey,
|
||||
pub tokens: Vec<Token>,
|
||||
}
|
||||
|
||||
|
@ -34,10 +36,18 @@ impl<'a> GroupWithTokensConfig<'a> {
|
|||
payer,
|
||||
mints,
|
||||
} = self;
|
||||
let group = send_tx(solana, CreateGroupInstruction { admin, payer })
|
||||
.await
|
||||
.unwrap()
|
||||
.group;
|
||||
let create_group_accounts = send_tx(
|
||||
solana,
|
||||
CreateGroupInstruction {
|
||||
admin,
|
||||
payer,
|
||||
insurance_mint: mints[0].pubkey,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let group = create_group_accounts.group;
|
||||
let insurance_vault = create_group_accounts.insurance_vault;
|
||||
|
||||
let address_lookup_table = solana.create_address_lookup_table(admin, payer).await;
|
||||
|
||||
|
@ -94,14 +104,13 @@ impl<'a> GroupWithTokensConfig<'a> {
|
|||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let _ = send_tx(
|
||||
let add_bank_accounts = send_tx(
|
||||
solana,
|
||||
TokenAddBankInstruction {
|
||||
token_index,
|
||||
bank_num: 1,
|
||||
group,
|
||||
admin,
|
||||
mint: mint.pubkey,
|
||||
address_lookup_table,
|
||||
payer,
|
||||
},
|
||||
|
@ -117,11 +126,16 @@ impl<'a> GroupWithTokensConfig<'a> {
|
|||
mint: mint.clone(),
|
||||
oracle,
|
||||
bank,
|
||||
bank1: add_bank_accounts.bank,
|
||||
vault,
|
||||
mint_info,
|
||||
});
|
||||
}
|
||||
|
||||
GroupWithTokens { group, tokens }
|
||||
GroupWithTokens {
|
||||
group,
|
||||
insurance_vault,
|
||||
tokens,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ use bytemuck::{bytes_of, Contiguous};
|
|||
use solana_program::program_error::ProgramError;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::Keypair;
|
||||
use std::ops::Deref;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn gen_signer_seeds<'a>(nonce: &'a u64, acc_pk: &'a Pubkey) -> [&'a [u8]; 2] {
|
||||
|
@ -32,3 +33,47 @@ pub fn create_signer_key_and_nonce(program_id: &Pubkey, acc_pk: &Pubkey) -> (Pub
|
|||
pub fn clone_keypair(keypair: &Keypair) -> Keypair {
|
||||
Keypair::from_base58_string(&keypair.to_base58_string())
|
||||
}
|
||||
|
||||
// Add clone() to Keypair, totally safe in tests
|
||||
pub trait ClonableKeypair {
|
||||
fn clone(&self) -> Self;
|
||||
}
|
||||
impl ClonableKeypair for Keypair {
|
||||
fn clone(&self) -> Self {
|
||||
clone_keypair(self)
|
||||
}
|
||||
}
|
||||
|
||||
// Make a clonable and defaultable Keypair newtype
|
||||
pub struct TestKeypair(pub Keypair);
|
||||
impl Clone for TestKeypair {
|
||||
fn clone(&self) -> Self {
|
||||
TestKeypair(self.0.clone())
|
||||
}
|
||||
}
|
||||
impl Default for TestKeypair {
|
||||
fn default() -> Self {
|
||||
TestKeypair(Keypair::from_bytes(&[0u8; 64]).unwrap())
|
||||
}
|
||||
}
|
||||
impl AsRef<Keypair> for TestKeypair {
|
||||
fn as_ref(&self) -> &Keypair {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl Deref for TestKeypair {
|
||||
type Target = Keypair;
|
||||
fn deref(&self) -> &Keypair {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl From<&Keypair> for TestKeypair {
|
||||
fn from(k: &Keypair) -> Self {
|
||||
Self(k.clone())
|
||||
}
|
||||
}
|
||||
impl From<Keypair> for TestKeypair {
|
||||
fn from(k: Keypair) -> Self {
|
||||
Self(k)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,654 @@
|
|||
#![cfg(feature = "test-bpf")]
|
||||
|
||||
use fixed::types::I80F48;
|
||||
use solana_program_test::*;
|
||||
use solana_sdk::{
|
||||
signature::{Keypair, Signer},
|
||||
transport::TransportError,
|
||||
};
|
||||
|
||||
use mango_v4::state::*;
|
||||
use program_test::*;
|
||||
|
||||
mod program_test;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
|
||||
let context = TestContext::new().await;
|
||||
let solana = &context.solana.clone();
|
||||
|
||||
let admin = &Keypair::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,
|
||||
}
|
||||
.create(solana)
|
||||
.await;
|
||||
let borrow_token1 = &tokens[0];
|
||||
let borrow_token2 = &tokens[1];
|
||||
let collateral_token1 = &tokens[2];
|
||||
let collateral_token2 = &tokens[3];
|
||||
|
||||
// deposit some funds, to the vaults aren't empty
|
||||
let vault_account = send_tx(
|
||||
solana,
|
||||
CreateAccountInstruction {
|
||||
account_num: 2,
|
||||
group,
|
||||
owner,
|
||||
payer,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
let vault_amount = 100000;
|
||||
for &token_account in payer_mint_accounts {
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: vault_amount,
|
||||
account: vault_account,
|
||||
token_account,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 1,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// also add a tiny amount to bank0 for borrow_token1, so we can test multi-bank socialized loss
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: 10,
|
||||
account: vault_account,
|
||||
token_account: payer_mint_accounts[0],
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// SETUP: Make an account with some collateral and some borrows
|
||||
//
|
||||
let account = send_tx(
|
||||
solana,
|
||||
CreateAccountInstruction {
|
||||
account_num: 0,
|
||||
group,
|
||||
owner,
|
||||
payer,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
let deposit1_amount = 1000;
|
||||
let deposit2_amount = 20;
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit1_amount,
|
||||
account,
|
||||
token_account: payer_mint_accounts[2],
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit2_amount,
|
||||
account,
|
||||
token_account: payer_mint_accounts[3],
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let borrow1_amount = 350;
|
||||
let borrow1_amount_bank0 = 10;
|
||||
let borrow1_amount_bank1 = borrow1_amount - borrow1_amount_bank0;
|
||||
let borrow2_amount = 50;
|
||||
send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: borrow1_amount_bank1,
|
||||
allow_borrow: true,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
bank_index: 1,
|
||||
},
|
||||
)
|
||||
.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: 1,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// SETUP: Change the oracle to make health go very negative
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
SetStubOracleInstruction {
|
||||
group,
|
||||
admin,
|
||||
mint: borrow_token1.mint.pubkey,
|
||||
payer,
|
||||
price: "20.0",
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// SETUP: liquidate all the collateral against borrow1
|
||||
//
|
||||
|
||||
// eat collateral1
|
||||
send_tx(
|
||||
solana,
|
||||
LiqTokenWithTokenInstruction {
|
||||
liqee: account,
|
||||
liqor: vault_account,
|
||||
liqor_owner: owner,
|
||||
asset_token_index: collateral_token1.index,
|
||||
asset_bank_index: 1,
|
||||
liab_token_index: borrow_token1.index,
|
||||
liab_bank_index: 1,
|
||||
max_liab_transfer: I80F48::from_num(100000.0),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
account_position(solana, account, collateral_token1.bank).await,
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
account_position(solana, account, borrow_token1.bank).await,
|
||||
(-350.0f64 + (1000.0 / 20.0 / 1.04)).round() as i64
|
||||
);
|
||||
let liqee: MangoAccount = solana.get_account(account).await;
|
||||
assert!(liqee.being_liquidated());
|
||||
assert!(!liqee.is_bankrupt());
|
||||
|
||||
// eat collateral2, leaving the account bankrupt
|
||||
send_tx(
|
||||
solana,
|
||||
LiqTokenWithTokenInstruction {
|
||||
liqee: account,
|
||||
liqor: vault_account,
|
||||
liqor_owner: owner,
|
||||
asset_token_index: collateral_token2.index,
|
||||
asset_bank_index: 1,
|
||||
liab_token_index: borrow_token1.index,
|
||||
liab_bank_index: 1,
|
||||
max_liab_transfer: I80F48::from_num(100000.0),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
account_position(solana, account, collateral_token2.bank).await,
|
||||
0
|
||||
);
|
||||
let borrow1_after_liq = -350.0f64 + (1000.0 / 20.0 / 1.04) + (20.0 / 20.0 / 1.04);
|
||||
assert_eq!(
|
||||
account_position(solana, account, borrow_token1.bank).await,
|
||||
borrow1_after_liq.round() as i64
|
||||
);
|
||||
let liqee: MangoAccount = solana.get_account(account).await;
|
||||
assert!(liqee.being_liquidated());
|
||||
assert!(liqee.is_bankrupt());
|
||||
|
||||
//
|
||||
// TEST: socialize loss on borrow1 and 2
|
||||
//
|
||||
|
||||
let vault_before = account_position(solana, vault_account, borrow_token1.bank).await;
|
||||
send_tx(
|
||||
solana,
|
||||
LiqTokenBankruptcyInstruction {
|
||||
liqee: account,
|
||||
liqor: vault_account,
|
||||
liqor_owner: owner,
|
||||
liab_token_index: borrow_token1.index,
|
||||
liab_mint_info: borrow_token1.mint_info,
|
||||
max_liab_transfer: I80F48::from_num(100000.0),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
account_position(solana, vault_account, borrow_token1.bank).await,
|
||||
vault_before + (borrow1_after_liq.round() as i64)
|
||||
);
|
||||
let liqee: MangoAccount = solana.get_account(account).await;
|
||||
assert!(liqee.being_liquidated());
|
||||
assert!(liqee.is_bankrupt());
|
||||
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
|
||||
// both bank's borrows were completely wiped: no one else borrowed
|
||||
let borrow1_bank0: Bank = solana.get_account(borrow_token1.bank).await;
|
||||
let borrow1_bank1: Bank = solana.get_account(borrow_token1.bank).await;
|
||||
assert_eq!(borrow1_bank0.native_borrows(), 0);
|
||||
assert_eq!(borrow1_bank1.native_borrows(), 0);
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
LiqTokenBankruptcyInstruction {
|
||||
liqee: account,
|
||||
liqor: vault_account,
|
||||
liqor_owner: owner,
|
||||
liab_token_index: borrow_token2.index,
|
||||
liab_mint_info: borrow_token2.mint_info,
|
||||
max_liab_transfer: I80F48::from_num(100000.0),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
account_position(solana, vault_account, borrow_token2.bank).await,
|
||||
(vault_amount - borrow2_amount) as i64
|
||||
);
|
||||
let liqee: MangoAccount = solana.get_account(account).await;
|
||||
assert!(liqee.being_liquidated()); // TODO: no longer being liquidated?
|
||||
assert!(!liqee.is_bankrupt());
|
||||
assert!(account_position_closed(solana, account, borrow_token2.bank).await);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
||||
let context = TestContext::new().await;
|
||||
let solana = &context.solana.clone();
|
||||
|
||||
let admin = &Keypair::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,
|
||||
}
|
||||
.create(solana)
|
||||
.await;
|
||||
let borrow_token1 = &tokens[0]; // USDC
|
||||
let borrow_token2 = &tokens[1];
|
||||
let collateral_token1 = &tokens[2];
|
||||
let collateral_token2 = &tokens[3];
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// deposit some funds, to the vaults aren't empty
|
||||
let vault_account = send_tx(
|
||||
solana,
|
||||
CreateAccountInstruction {
|
||||
account_num: 2,
|
||||
group,
|
||||
owner,
|
||||
payer,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
let vault_amount = 100000;
|
||||
for &token_account in payer_mint_accounts {
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: vault_amount,
|
||||
account: vault_account,
|
||||
token_account,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 1,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// also add a tiny amount to bank0 for borrow_token1, so we can test multi-bank socialized loss
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: 10,
|
||||
account: vault_account,
|
||||
token_account: payer_mint_accounts[0],
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// SETUP: Make an account with some collateral and some borrows
|
||||
//
|
||||
let account = send_tx(
|
||||
solana,
|
||||
CreateAccountInstruction {
|
||||
account_num: 0,
|
||||
group,
|
||||
owner,
|
||||
payer,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.account;
|
||||
|
||||
let deposit1_amount = 20;
|
||||
let deposit2_amount = 1000;
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit1_amount,
|
||||
account,
|
||||
token_account: payer_mint_accounts[2],
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit2_amount,
|
||||
account,
|
||||
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: 1,
|
||||
},
|
||||
)
|
||||
.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: 1,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// SETUP: Change the oracle to make health go very negative
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
SetStubOracleInstruction {
|
||||
group,
|
||||
admin,
|
||||
mint: borrow_token2.mint.pubkey,
|
||||
payer,
|
||||
price: "20.0",
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// SETUP: liquidate all the collateral against borrow2
|
||||
//
|
||||
|
||||
// eat collateral1
|
||||
send_tx(
|
||||
solana,
|
||||
LiqTokenWithTokenInstruction {
|
||||
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_eq!(
|
||||
account_position(solana, account, collateral_token1.bank).await,
|
||||
0
|
||||
);
|
||||
let liqee: MangoAccount = solana.get_account(account).await;
|
||||
assert!(liqee.being_liquidated());
|
||||
assert!(!liqee.is_bankrupt());
|
||||
|
||||
// eat collateral2, leaving the account bankrupt
|
||||
send_tx(
|
||||
solana,
|
||||
LiqTokenWithTokenInstruction {
|
||||
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_eq!(
|
||||
account_position(solana, account, collateral_token2.bank).await,
|
||||
0
|
||||
);
|
||||
let liqee: MangoAccount = solana.get_account(account).await;
|
||||
assert!(liqee.being_liquidated());
|
||||
assert!(liqee.is_bankrupt());
|
||||
|
||||
//
|
||||
// TEST: use the insurance fund to liquidate borrow1 and borrow2
|
||||
//
|
||||
|
||||
// bankruptcy of an USDC liability: just transfers funds from insurance vault to liqee,
|
||||
// the liqor is uninvolved
|
||||
let insurance_vault_before = solana.token_account_balance(insurance_vault).await;
|
||||
let liqor_before = account_position(solana, vault_account, borrow_token1.bank).await;
|
||||
send_tx(
|
||||
solana,
|
||||
LiqTokenBankruptcyInstruction {
|
||||
liqee: account,
|
||||
liqor: vault_account,
|
||||
liqor_owner: owner,
|
||||
liab_token_index: borrow_token1.index,
|
||||
liab_mint_info: borrow_token1.mint_info,
|
||||
max_liab_transfer: I80F48::from_num(100000.0),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let liqee: MangoAccount = solana.get_account(account).await;
|
||||
assert!(liqee.being_liquidated());
|
||||
assert!(liqee.is_bankrupt());
|
||||
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
|
||||
assert_eq!(
|
||||
solana.token_account_balance(insurance_vault).await,
|
||||
// the loan origination fees push the borrow above 50.0 and cause this rounding
|
||||
insurance_vault_before - borrow1_amount - 1
|
||||
);
|
||||
assert_eq!(
|
||||
account_position(solana, vault_account, borrow_token1.bank).await,
|
||||
liqor_before
|
||||
);
|
||||
|
||||
// bankruptcy of a non-USDC liability: USDC 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, borrow_token1.bank).await;
|
||||
let liab_transfer: f64 = 500.0 / 20.0;
|
||||
send_tx(
|
||||
solana,
|
||||
LiqTokenBankruptcyInstruction {
|
||||
liqee: account,
|
||||
liqor: vault_account,
|
||||
liqor_owner: owner,
|
||||
liab_token_index: borrow_token2.index,
|
||||
liab_mint_info: borrow_token2.mint_info,
|
||||
max_liab_transfer: I80F48::from_num(liab_transfer),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let liqee: MangoAccount = solana.get_account(account).await;
|
||||
assert!(liqee.being_liquidated());
|
||||
assert!(liqee.is_bankrupt());
|
||||
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
|
||||
assert_eq!(
|
||||
account_position(solana, account, borrow_token2.bank).await,
|
||||
(liab_before + liab_transfer) as i64
|
||||
);
|
||||
let usdc_amount = (liab_transfer * 20.0 * 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, borrow_token1.bank).await,
|
||||
liqor_before + usdc_amount as i64
|
||||
);
|
||||
|
||||
// bankruptcy of a non-USDC liability: USDC to liqor, liability to liqee
|
||||
// liquidating fully and then doing socialized loss because the insurance fund is exhausted
|
||||
let insurance_vault_before = solana.token_account_balance(insurance_vault).await;
|
||||
let liqor_before = account_position(solana, vault_account, borrow_token1.bank).await;
|
||||
send_tx(
|
||||
solana,
|
||||
LiqTokenBankruptcyInstruction {
|
||||
liqee: account,
|
||||
liqor: vault_account,
|
||||
liqor_owner: owner,
|
||||
liab_token_index: borrow_token2.index,
|
||||
liab_mint_info: borrow_token2.mint_info,
|
||||
max_liab_transfer: I80F48::from_num(1000000.0),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let liqee: MangoAccount = solana.get_account(account).await;
|
||||
assert!(liqee.being_liquidated());
|
||||
assert!(!liqee.is_bankrupt());
|
||||
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
|
||||
assert!(account_position_closed(solana, account, borrow_token2.bank).await);
|
||||
assert_eq!(solana.token_account_balance(insurance_vault).await, 0);
|
||||
assert_eq!(
|
||||
account_position(solana, vault_account, borrow_token1.bank).await,
|
||||
liqor_before + insurance_vault_before as i64
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -27,7 +27,7 @@ async fn test_basic() -> Result<(), TransportError> {
|
|||
// SETUP: Create a group, account, register a token (mint0)
|
||||
//
|
||||
|
||||
let mango_setup::GroupWithTokens { group, tokens } = mango_setup::GroupWithTokensConfig {
|
||||
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints,
|
||||
|
@ -63,7 +63,8 @@ async fn test_basic() -> Result<(), TransportError> {
|
|||
amount: deposit_amount,
|
||||
account,
|
||||
token_account: payer_mint0_account,
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -111,6 +112,7 @@ async fn test_basic() -> Result<(), TransportError> {
|
|||
account,
|
||||
owner,
|
||||
token_account: payer_mint0_account,
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -136,15 +138,11 @@ async fn test_basic() -> Result<(), TransportError> {
|
|||
// TEST: Close account and de register bank
|
||||
//
|
||||
|
||||
let mint_info: MintInfo = solana.get_account(tokens[0].mint_info).await;
|
||||
|
||||
// withdraw whatever is remaining, can't close bank vault without this
|
||||
send_tx(
|
||||
solana,
|
||||
UpdateIndexInstruction {
|
||||
mint_info: tokens[0].mint_info,
|
||||
banks: mint_info.banks.to_vec(),
|
||||
oracle: mint_info.oracle,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -158,6 +156,7 @@ async fn test_basic() -> Result<(), TransportError> {
|
|||
account,
|
||||
owner,
|
||||
token_account: payer_mint0_account,
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -24,7 +24,7 @@ async fn test_delegate() -> Result<(), TransportError> {
|
|||
// SETUP: Create a group, register a token (mint0), create an account
|
||||
//
|
||||
|
||||
let mango_setup::GroupWithTokens { group, tokens } = mango_setup::GroupWithTokensConfig {
|
||||
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints,
|
||||
|
@ -53,7 +53,8 @@ async fn test_delegate() -> Result<(), TransportError> {
|
|||
amount: 100,
|
||||
account,
|
||||
token_account: payer_mint0_account,
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -108,6 +109,7 @@ async fn test_delegate() -> Result<(), TransportError> {
|
|||
account,
|
||||
owner: delegate,
|
||||
token_account: payer_mint0_account,
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
@ -127,6 +129,7 @@ async fn test_delegate() -> Result<(), TransportError> {
|
|||
account,
|
||||
owner,
|
||||
token_account: payer_mint0_account,
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -59,7 +59,8 @@ async fn test_health_compute_tokens() -> Result<(), TransportError> {
|
|||
amount: deposit_amount,
|
||||
account,
|
||||
token_account,
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -88,7 +89,7 @@ async fn test_health_compute_serum() -> Result<(), TransportError> {
|
|||
// SETUP: Create a group and an account
|
||||
//
|
||||
|
||||
let mango_setup::GroupWithTokens { group, tokens } = mango_setup::GroupWithTokensConfig {
|
||||
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints,
|
||||
|
@ -169,7 +170,8 @@ async fn test_health_compute_serum() -> Result<(), TransportError> {
|
|||
amount: 10,
|
||||
account,
|
||||
token_account: payer_mint_accounts[0],
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -198,7 +200,7 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
|
|||
// SETUP: Create a group and an account
|
||||
//
|
||||
|
||||
let mango_setup::GroupWithTokens { group, tokens } = mango_setup::GroupWithTokensConfig {
|
||||
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints,
|
||||
|
@ -226,7 +228,8 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
|
|||
amount: 1000,
|
||||
account,
|
||||
token_account: payer_mint_accounts[0],
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -268,7 +271,6 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
|
|||
perp_market_index: perp_market_index as PerpMarketIndex,
|
||||
base_token_index: quote_token.index,
|
||||
base_token_decimals: quote_token.mint.decimals,
|
||||
quote_token_index: token.index,
|
||||
quote_lot_size: 10,
|
||||
base_lot_size: 100,
|
||||
maint_asset_weight: 0.975,
|
||||
|
@ -323,7 +325,8 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
|
|||
amount: 10,
|
||||
account,
|
||||
token_account: payer_mint_accounts[0],
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -27,7 +27,7 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> {
|
|||
// SETUP: Create a group and an account to fill the vaults
|
||||
//
|
||||
|
||||
let mango_setup::GroupWithTokens { group, tokens } = mango_setup::GroupWithTokensConfig {
|
||||
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints,
|
||||
|
@ -57,7 +57,8 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> {
|
|||
amount: 10000,
|
||||
account: vault_account,
|
||||
token_account,
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -112,7 +113,8 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> {
|
|||
amount: deposit_amount,
|
||||
account,
|
||||
token_account: payer_mint_accounts[1],
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -179,6 +181,7 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> {
|
|||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
bank_index: 0,
|
||||
}
|
||||
)
|
||||
.await
|
||||
|
@ -207,6 +210,7 @@ async fn test_liq_tokens_force_cancel() -> Result<(), TransportError> {
|
|||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -230,7 +234,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
|||
// SETUP: Create a group and an account to fill the vaults
|
||||
//
|
||||
|
||||
let mango_setup::GroupWithTokens { group, tokens } = mango_setup::GroupWithTokensConfig {
|
||||
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints,
|
||||
|
@ -262,7 +266,8 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
|||
amount: 100000,
|
||||
account: vault_account,
|
||||
token_account,
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -293,7 +298,8 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
|||
amount: deposit1_amount,
|
||||
account,
|
||||
token_account: payer_mint_accounts[2],
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -304,7 +310,8 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
|||
amount: deposit2_amount,
|
||||
account,
|
||||
token_account: payer_mint_accounts[3],
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -320,6 +327,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
|||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -332,6 +340,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
|||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -365,6 +374,8 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
|||
liqor_owner: owner,
|
||||
asset_token_index: collateral_token2.index,
|
||||
liab_token_index: borrow_token2.index,
|
||||
asset_bank_index: 0,
|
||||
liab_bank_index: 0,
|
||||
max_liab_transfer: I80F48::from_num(10000.0),
|
||||
},
|
||||
)
|
||||
|
@ -381,7 +392,8 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
|||
0
|
||||
);
|
||||
let liqee: MangoAccount = solana.get_account(account).await;
|
||||
assert_eq!(liqee.being_liquidated, 1);
|
||||
assert!(liqee.being_liquidated());
|
||||
assert!(!liqee.is_bankrupt());
|
||||
|
||||
//
|
||||
// TEST: liquidate the remaining borrow2 against collateral1,
|
||||
|
@ -396,6 +408,8 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
|||
asset_token_index: collateral_token1.index,
|
||||
liab_token_index: borrow_token2.index,
|
||||
max_liab_transfer: I80F48::from_num(10000.0),
|
||||
asset_bank_index: 0,
|
||||
liab_bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -411,7 +425,8 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
|||
1000 - 32
|
||||
);
|
||||
let liqee: MangoAccount = solana.get_account(account).await;
|
||||
assert_eq!(liqee.being_liquidated, 1);
|
||||
assert!(liqee.being_liquidated());
|
||||
assert!(!liqee.is_bankrupt());
|
||||
|
||||
//
|
||||
// TEST: liquidate borrow1 with collateral1, but place a limit
|
||||
|
@ -425,6 +440,8 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
|||
asset_token_index: collateral_token1.index,
|
||||
liab_token_index: borrow_token1.index,
|
||||
max_liab_transfer: I80F48::from_num(10.0),
|
||||
asset_bank_index: 0,
|
||||
liab_bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -440,7 +457,8 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
|||
1000 - 32 - 21
|
||||
);
|
||||
let liqee: MangoAccount = solana.get_account(account).await;
|
||||
assert_eq!(liqee.being_liquidated, 1);
|
||||
assert!(liqee.being_liquidated());
|
||||
assert!(!liqee.is_bankrupt());
|
||||
|
||||
//
|
||||
// TEST: liquidate borrow1 with collateral1, making the account healthy again
|
||||
|
@ -454,6 +472,8 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
|||
asset_token_index: collateral_token1.index,
|
||||
liab_token_index: borrow_token1.index,
|
||||
max_liab_transfer: I80F48::from_num(10000.0),
|
||||
asset_bank_index: 0,
|
||||
liab_bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -472,7 +492,8 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
|||
1000 - 32 - 535 - 1
|
||||
);
|
||||
let liqee: MangoAccount = solana.get_account(account).await;
|
||||
assert_eq!(liqee.being_liquidated, 0);
|
||||
assert!(!liqee.being_liquidated());
|
||||
assert!(!liqee.is_bankrupt());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ async fn test_margin_trade1() -> Result<(), BanksClientError> {
|
|||
// SETUP: Create a group, account, register a token (mint0)
|
||||
//
|
||||
|
||||
let mango_setup::GroupWithTokens { group, tokens } = mango_setup::GroupWithTokensConfig {
|
||||
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints,
|
||||
|
@ -68,7 +68,8 @@ async fn test_margin_trade1() -> Result<(), BanksClientError> {
|
|||
amount: provided_amount,
|
||||
account: provider_account,
|
||||
token_account: payer_mint0_account,
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -79,7 +80,8 @@ async fn test_margin_trade1() -> Result<(), BanksClientError> {
|
|||
amount: provided_amount,
|
||||
account: provider_account,
|
||||
token_account: payer_mint1_account,
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -115,7 +117,8 @@ async fn test_margin_trade1() -> Result<(), BanksClientError> {
|
|||
amount: deposit_amount_initial,
|
||||
account,
|
||||
token_account: payer_mint0_account,
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -347,7 +350,7 @@ async fn test_margin_trade2() -> Result<(), BanksClientError> {
|
|||
// SETUP: Create a group, account, register a token (mint0)
|
||||
//
|
||||
|
||||
let mango_setup::GroupWithTokens { group, tokens } = mango_setup::GroupWithTokensConfig {
|
||||
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints,
|
||||
|
@ -381,7 +384,8 @@ async fn test_margin_trade2() -> Result<(), BanksClientError> {
|
|||
amount: provided_amount,
|
||||
account: provider_account,
|
||||
token_account: payer_mint0_account,
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -392,7 +396,8 @@ async fn test_margin_trade2() -> Result<(), BanksClientError> {
|
|||
amount: provided_amount,
|
||||
account: provider_account,
|
||||
token_account: payer_mint1_account,
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -428,7 +433,8 @@ async fn test_margin_trade2() -> Result<(), BanksClientError> {
|
|||
amount: deposit_amount_initial,
|
||||
account,
|
||||
token_account: payer_mint0_account,
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -605,7 +611,7 @@ async fn test_margin_trade3() -> Result<(), BanksClientError> {
|
|||
// SETUP: Create a group, account, register a token (mint0)
|
||||
//
|
||||
|
||||
let mango_setup::GroupWithTokens { group, tokens } = mango_setup::GroupWithTokensConfig {
|
||||
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints,
|
||||
|
@ -639,7 +645,8 @@ async fn test_margin_trade3() -> Result<(), BanksClientError> {
|
|||
amount: provided_amount,
|
||||
account: provider_account,
|
||||
token_account: payer_mint0_account,
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -650,7 +657,8 @@ async fn test_margin_trade3() -> Result<(), BanksClientError> {
|
|||
amount: provided_amount,
|
||||
account: provider_account,
|
||||
token_account: payer_mint1_account,
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -686,7 +694,8 @@ async fn test_margin_trade3() -> Result<(), BanksClientError> {
|
|||
amount: deposit_amount_initial,
|
||||
account,
|
||||
token_account: payer_mint0_account,
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -24,7 +24,7 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
// SETUP: Create a group and an account
|
||||
//
|
||||
|
||||
let mango_setup::GroupWithTokens { group, tokens } = mango_setup::GroupWithTokensConfig {
|
||||
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints,
|
||||
|
@ -70,7 +70,8 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
amount: deposit_amount,
|
||||
account: account_0,
|
||||
token_account: payer_mint_accounts[0],
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -82,7 +83,8 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
amount: deposit_amount,
|
||||
account: account_0,
|
||||
token_account: payer_mint_accounts[1],
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -98,7 +100,8 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
amount: deposit_amount,
|
||||
account: account_1,
|
||||
token_account: payer_mint_accounts[0],
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -110,7 +113,8 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
amount: deposit_amount,
|
||||
account: account_1,
|
||||
token_account: payer_mint_accounts[1],
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -150,7 +154,6 @@ async fn test_perp() -> Result<(), TransportError> {
|
|||
perp_market_index: 0,
|
||||
base_token_index: tokens[0].index,
|
||||
base_token_decimals: tokens[0].mint.decimals,
|
||||
quote_token_index: tokens[1].index,
|
||||
quote_lot_size: 10,
|
||||
base_lot_size: 100,
|
||||
maint_asset_weight: 0.975,
|
||||
|
|
|
@ -26,7 +26,7 @@ async fn test_position_lifetime() -> Result<()> {
|
|||
// SETUP: Create a group and accounts
|
||||
//
|
||||
|
||||
let mango_setup::GroupWithTokens { group, tokens } = mango_setup::GroupWithTokensConfig {
|
||||
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints,
|
||||
|
@ -72,7 +72,8 @@ async fn test_position_lifetime() -> Result<()> {
|
|||
amount: funding_amount,
|
||||
account: funding_account,
|
||||
token_account: payer_token,
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -95,7 +96,8 @@ async fn test_position_lifetime() -> Result<()> {
|
|||
amount: deposit_amount,
|
||||
account,
|
||||
token_account: payer_token,
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -112,6 +114,7 @@ async fn test_position_lifetime() -> Result<()> {
|
|||
account,
|
||||
owner,
|
||||
token_account: payer_token,
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -145,7 +148,8 @@ async fn test_position_lifetime() -> Result<()> {
|
|||
amount: collateral_amount,
|
||||
account,
|
||||
token_account: payer_mint_accounts[0],
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -161,6 +165,7 @@ async fn test_position_lifetime() -> Result<()> {
|
|||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -179,7 +184,8 @@ async fn test_position_lifetime() -> Result<()> {
|
|||
amount: borrow_amount + 2,
|
||||
account,
|
||||
token_account: payer_mint_accounts[1],
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -193,6 +199,7 @@ async fn test_position_lifetime() -> Result<()> {
|
|||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -208,6 +215,7 @@ async fn test_position_lifetime() -> Result<()> {
|
|||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -26,7 +26,7 @@ async fn test_serum() -> Result<(), TransportError> {
|
|||
// SETUP: Create a group and an account
|
||||
//
|
||||
|
||||
let mango_setup::GroupWithTokens { group, tokens } = mango_setup::GroupWithTokensConfig {
|
||||
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints,
|
||||
|
@ -69,7 +69,8 @@ async fn test_serum() -> Result<(), TransportError> {
|
|||
amount: deposit_amount,
|
||||
account,
|
||||
token_account: payer_mint_accounts[0],
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -81,7 +82,8 @@ async fn test_serum() -> Result<(), TransportError> {
|
|||
amount: deposit_amount,
|
||||
account,
|
||||
token_account: payer_mint_accounts[1],
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#![cfg(feature = "test-bpf")]
|
||||
|
||||
use mango_v4::state::{Bank, MintInfo};
|
||||
use mango_v4::state::*;
|
||||
use solana_program_test::*;
|
||||
use solana_sdk::{signature::Keypair, transport::TransportError};
|
||||
|
||||
|
@ -23,7 +23,7 @@ async fn test_update_index() -> Result<(), TransportError> {
|
|||
// SETUP: Create a group and an account to fill the vaults
|
||||
//
|
||||
|
||||
let mango_setup::GroupWithTokens { group, tokens } = mango_setup::GroupWithTokensConfig {
|
||||
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints,
|
||||
|
@ -51,7 +51,8 @@ async fn test_update_index() -> Result<(), TransportError> {
|
|||
amount: 10000,
|
||||
account: deposit_account,
|
||||
token_account,
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -77,7 +78,8 @@ async fn test_update_index() -> Result<(), TransportError> {
|
|||
amount: 100000,
|
||||
account: withdraw_account,
|
||||
token_account: payer_mint_accounts[1],
|
||||
token_authority: payer,
|
||||
token_authority: payer.clone(),
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -91,6 +93,7 @@ async fn test_update_index() -> Result<(), TransportError> {
|
|||
account: withdraw_account,
|
||||
owner,
|
||||
token_account: context.users[0].token_accounts[0],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
@ -100,14 +103,10 @@ async fn test_update_index() -> Result<(), TransportError> {
|
|||
|
||||
solana.advance_clock().await;
|
||||
|
||||
let mint_info: MintInfo = solana.get_account(tokens[0].mint_info).await;
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
UpdateIndexInstruction {
|
||||
mint_info: tokens[0].mint_info,
|
||||
banks: mint_info.banks.to_vec(),
|
||||
oracle: mint_info.oracle,
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -15,7 +15,7 @@ anchor build --skip-lint
|
|||
# update types in ts client package
|
||||
cp -v ./target/types/mango_v4.ts ./ts/client/src/mango_v4.ts
|
||||
|
||||
(cd ./ts/client && tsc)
|
||||
(cd ./ts/client && yarn tsc)
|
||||
|
||||
if [[ -z "${NO_DEPLOY}" ]]; then
|
||||
# publish program
|
||||
|
|
|
@ -15,7 +15,7 @@ anchor build --skip-lint
|
|||
# update types in ts client package
|
||||
cp -v ./target/types/mango_v4.ts ./ts/client/src/mango_v4.ts
|
||||
|
||||
(cd ./ts/client && tsc)
|
||||
(cd ./ts/client && yarn tsc)
|
||||
|
||||
if [[ -z "${NO_DEPLOY}" ]]; then
|
||||
# publish program
|
||||
|
@ -31,4 +31,4 @@ fi
|
|||
|
||||
|
||||
# build npm package
|
||||
(cd ./ts/client && tsc)
|
||||
(cd ./ts/client && yarn tsc)
|
||||
|
|
|
@ -67,6 +67,7 @@ export class MangoClient {
|
|||
public async createGroup(
|
||||
groupNum: number,
|
||||
testing: boolean,
|
||||
insuranceMintPk: PublicKey,
|
||||
): Promise<TransactionSignature> {
|
||||
const adminPk = (this.program.provider as AnchorProvider).wallet.publicKey;
|
||||
return await this.program.methods
|
||||
|
@ -74,6 +75,7 @@ export class MangoClient {
|
|||
.accounts({
|
||||
admin: adminPk,
|
||||
payer: adminPk,
|
||||
insuranceMint: insuranceMintPk,
|
||||
})
|
||||
.rpc();
|
||||
}
|
||||
|
@ -117,7 +119,7 @@ export class MangoClient {
|
|||
filters.push({
|
||||
memcmp: {
|
||||
bytes: bs58.encode(bbuf),
|
||||
offset: 44,
|
||||
offset: 40,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -286,7 +288,7 @@ export class MangoClient {
|
|||
{
|
||||
memcmp: {
|
||||
bytes: group.publicKey.toBase58(),
|
||||
offset: 24,
|
||||
offset: 8,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
@ -325,7 +327,7 @@ export class MangoClient {
|
|||
{
|
||||
memcmp: {
|
||||
bytes: bs58.encode(tokenIndexBuf),
|
||||
offset: 200,
|
||||
offset: 40,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
@ -443,6 +445,7 @@ export class MangoClient {
|
|||
|
||||
public async editMangoAccount(
|
||||
group: Group,
|
||||
mangoAccount: MangoAccount,
|
||||
name?: string,
|
||||
delegate?: PublicKey,
|
||||
): Promise<TransactionSignature> {
|
||||
|
@ -450,6 +453,7 @@ export class MangoClient {
|
|||
.editAccount(name, delegate)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
account: mangoAccount.publicKey,
|
||||
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
|
||||
})
|
||||
.rpc();
|
||||
|
@ -471,13 +475,13 @@ export class MangoClient {
|
|||
{
|
||||
memcmp: {
|
||||
bytes: group.publicKey.toBase58(),
|
||||
offset: 40,
|
||||
offset: 8,
|
||||
},
|
||||
},
|
||||
{
|
||||
memcmp: {
|
||||
bytes: ownerPk.toBase58(),
|
||||
offset: 72,
|
||||
offset: 40,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
@ -724,7 +728,7 @@ export class MangoClient {
|
|||
{
|
||||
memcmp: {
|
||||
bytes: group.publicKey.toBase58(),
|
||||
offset: 24,
|
||||
offset: 8,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@ -735,7 +739,7 @@ export class MangoClient {
|
|||
filters.push({
|
||||
memcmp: {
|
||||
bytes: bs58.encode(bbuf),
|
||||
offset: 122,
|
||||
offset: 40,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -746,7 +750,7 @@ export class MangoClient {
|
|||
filters.push({
|
||||
memcmp: {
|
||||
bytes: bs58.encode(qbuf),
|
||||
offset: 124,
|
||||
offset: 42,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -1052,7 +1056,6 @@ export class MangoClient {
|
|||
} as any, // future: nested custom types dont typecheck, fix if possible?
|
||||
baseTokenIndex,
|
||||
baseTokenDecimals,
|
||||
quoteTokenIndex,
|
||||
new BN(quoteLotSize),
|
||||
new BN(baseLotSize),
|
||||
maintAssetWeight,
|
||||
|
@ -1188,7 +1191,6 @@ export class MangoClient {
|
|||
public async perpGetMarkets(
|
||||
group: Group,
|
||||
baseTokenIndex?: number,
|
||||
quoteTokenIndex?: number,
|
||||
): Promise<PerpMarket[]> {
|
||||
const bumpfbuf = Buffer.alloc(1);
|
||||
bumpfbuf.writeUInt8(255);
|
||||
|
@ -1197,7 +1199,7 @@ export class MangoClient {
|
|||
{
|
||||
memcmp: {
|
||||
bytes: group.publicKey.toBase58(),
|
||||
offset: 24,
|
||||
offset: 8,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@ -1208,18 +1210,7 @@ export class MangoClient {
|
|||
filters.push({
|
||||
memcmp: {
|
||||
bytes: bs58.encode(bbuf),
|
||||
offset: 444,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (quoteTokenIndex) {
|
||||
const qbuf = Buffer.alloc(2);
|
||||
qbuf.writeUInt16LE(quoteTokenIndex);
|
||||
filters.push({
|
||||
memcmp: {
|
||||
bytes: bs58.encode(qbuf),
|
||||
offset: 446,
|
||||
offset: 40,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -34,15 +34,49 @@ export type MangoV4 = {
|
|||
"isMut": false,
|
||||
"isSigner": true
|
||||
},
|
||||
{
|
||||
"name": "insuranceMint",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "insuranceVault",
|
||||
"isMut": true,
|
||||
"isSigner": false,
|
||||
"pda": {
|
||||
"seeds": [
|
||||
{
|
||||
"kind": "account",
|
||||
"type": "publicKey",
|
||||
"path": "group"
|
||||
},
|
||||
{
|
||||
"kind": "const",
|
||||
"type": "string",
|
||||
"value": "InsuranceVault"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": [
|
||||
|
@ -1769,6 +1803,63 @@ export type MangoV4 = {
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "liqTokenBankruptcy",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"name": "liabMintInfo",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "quoteVault",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "insuranceVault",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "tokenProgram",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "liabTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "maxLiabTransfer",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "perpCreateMarket",
|
||||
"accounts": [
|
||||
|
@ -1862,10 +1953,6 @@ export type MangoV4 = {
|
|||
"name": "baseTokenDecimals",
|
||||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "quoteTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "quoteLotSize",
|
||||
"type": "i64"
|
||||
|
@ -2405,6 +2492,10 @@ export type MangoV4 = {
|
|||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "group",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": {
|
||||
|
@ -2414,10 +2505,6 @@ export type MangoV4 = {
|
|||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "group",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "mint",
|
||||
"type": "publicKey"
|
||||
|
@ -2605,6 +2692,27 @@ export type MangoV4 = {
|
|||
"name": "admin",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "groupNum",
|
||||
"type": "u32"
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
4
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "insuranceVault",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "insuranceMint",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "bump",
|
||||
"type": "u8"
|
||||
|
@ -2614,18 +2722,14 @@ export type MangoV4 = {
|
|||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"name": "padding2",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
2
|
||||
6
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "groupNum",
|
||||
"type": "u32"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
|
@ -2643,6 +2747,14 @@ export type MangoV4 = {
|
|||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "group",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "owner",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": {
|
||||
|
@ -2652,14 +2764,6 @@ export type MangoV4 = {
|
|||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "group",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "owner",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "delegate",
|
||||
"type": "publicKey"
|
||||
|
@ -2719,6 +2823,19 @@ export type MangoV4 = {
|
|||
"name": "group",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "tokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
6
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mint",
|
||||
"type": "publicKey"
|
||||
|
@ -2749,10 +2866,6 @@ export type MangoV4 = {
|
|||
"name": "addressLookupTable",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "tokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "addressLookupTableBankIndex",
|
||||
"type": "u8"
|
||||
|
@ -2766,7 +2879,7 @@ export type MangoV4 = {
|
|||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
4
|
||||
6
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -2892,6 +3005,27 @@ export type MangoV4 = {
|
|||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "group",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "baseTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "perpMarketIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
4
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": {
|
||||
|
@ -2901,10 +3035,6 @@ export type MangoV4 = {
|
|||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "group",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "oracle",
|
||||
"type": "publicKey"
|
||||
|
@ -3032,16 +3162,13 @@ export type MangoV4 = {
|
|||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "perpMarketIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "baseTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "quoteTokenIndex",
|
||||
"type": "u16"
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
6
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -3051,6 +3178,27 @@ export type MangoV4 = {
|
|||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "group",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "baseTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "quoteTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
4
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": {
|
||||
|
@ -3060,10 +3208,6 @@ export type MangoV4 = {
|
|||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "group",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "serumProgram",
|
||||
"type": "publicKey"
|
||||
|
@ -3076,14 +3220,6 @@ export type MangoV4 = {
|
|||
"name": "marketIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "baseTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "quoteTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "bump",
|
||||
"type": "u8"
|
||||
|
@ -3093,7 +3229,7 @@ export type MangoV4 = {
|
|||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
1
|
||||
5
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -4426,15 +4562,49 @@ export const IDL: MangoV4 = {
|
|||
"isMut": false,
|
||||
"isSigner": true
|
||||
},
|
||||
{
|
||||
"name": "insuranceMint",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "insuranceVault",
|
||||
"isMut": true,
|
||||
"isSigner": false,
|
||||
"pda": {
|
||||
"seeds": [
|
||||
{
|
||||
"kind": "account",
|
||||
"type": "publicKey",
|
||||
"path": "group"
|
||||
},
|
||||
{
|
||||
"kind": "const",
|
||||
"type": "string",
|
||||
"value": "InsuranceVault"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": [
|
||||
|
@ -6161,6 +6331,63 @@ export const IDL: MangoV4 = {
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "liqTokenBankruptcy",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"name": "liabMintInfo",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "quoteVault",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "insuranceVault",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "tokenProgram",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "liabTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "maxLiabTransfer",
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "perpCreateMarket",
|
||||
"accounts": [
|
||||
|
@ -6254,10 +6481,6 @@ export const IDL: MangoV4 = {
|
|||
"name": "baseTokenDecimals",
|
||||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "quoteTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "quoteLotSize",
|
||||
"type": "i64"
|
||||
|
@ -6797,6 +7020,10 @@ export const IDL: MangoV4 = {
|
|||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "group",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": {
|
||||
|
@ -6806,10 +7033,6 @@ export const IDL: MangoV4 = {
|
|||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "group",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "mint",
|
||||
"type": "publicKey"
|
||||
|
@ -6997,6 +7220,27 @@ export const IDL: MangoV4 = {
|
|||
"name": "admin",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "groupNum",
|
||||
"type": "u32"
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
4
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "insuranceVault",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "insuranceMint",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "bump",
|
||||
"type": "u8"
|
||||
|
@ -7006,18 +7250,14 @@ export const IDL: MangoV4 = {
|
|||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"name": "padding2",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
2
|
||||
6
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "groupNum",
|
||||
"type": "u32"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
|
@ -7035,6 +7275,14 @@ export const IDL: MangoV4 = {
|
|||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "group",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "owner",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": {
|
||||
|
@ -7044,14 +7292,6 @@ export const IDL: MangoV4 = {
|
|||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "group",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "owner",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "delegate",
|
||||
"type": "publicKey"
|
||||
|
@ -7111,6 +7351,19 @@ export const IDL: MangoV4 = {
|
|||
"name": "group",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "tokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
6
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mint",
|
||||
"type": "publicKey"
|
||||
|
@ -7141,10 +7394,6 @@ export const IDL: MangoV4 = {
|
|||
"name": "addressLookupTable",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "tokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "addressLookupTableBankIndex",
|
||||
"type": "u8"
|
||||
|
@ -7158,7 +7407,7 @@ export const IDL: MangoV4 = {
|
|||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
4
|
||||
6
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -7284,6 +7533,27 @@ export const IDL: MangoV4 = {
|
|||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "group",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "baseTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "perpMarketIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
4
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": {
|
||||
|
@ -7293,10 +7563,6 @@ export const IDL: MangoV4 = {
|
|||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "group",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "oracle",
|
||||
"type": "publicKey"
|
||||
|
@ -7424,16 +7690,13 @@ export const IDL: MangoV4 = {
|
|||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "perpMarketIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "baseTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "quoteTokenIndex",
|
||||
"type": "u16"
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
6
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -7443,6 +7706,27 @@ export const IDL: MangoV4 = {
|
|||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "group",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "baseTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "quoteTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
4
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": {
|
||||
|
@ -7452,10 +7736,6 @@ export const IDL: MangoV4 = {
|
|||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "group",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "serumProgram",
|
||||
"type": "publicKey"
|
||||
|
@ -7468,14 +7748,6 @@ export const IDL: MangoV4 = {
|
|||
"name": "marketIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "baseTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "quoteTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "bump",
|
||||
"type": "u8"
|
||||
|
@ -7485,7 +7757,7 @@ export const IDL: MangoV4 = {
|
|||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
1
|
||||
5
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,15 +53,16 @@ async function main() {
|
|||
|
||||
// group
|
||||
console.log(`Creating Group...`);
|
||||
const insuranceMint = new PublicKey(DEVNET_MINTS.get('USDC')!);
|
||||
try {
|
||||
await client.createGroup(0, true);
|
||||
await client.createGroup(0, true, insuranceMint);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
const group = await client.getGroupForAdmin(admin.publicKey);
|
||||
console.log(`...registered group ${group.publicKey}`);
|
||||
|
||||
// register token 0
|
||||
// register token 1
|
||||
console.log(`Registering BTC...`);
|
||||
const btcDevnetMint = new PublicKey(DEVNET_MINTS.get('BTC')!);
|
||||
const btcDevnetOracle = new PublicKey(DEVNET_ORACLES.get('BTC')!);
|
||||
|
@ -71,7 +72,7 @@ async function main() {
|
|||
btcDevnetMint,
|
||||
btcDevnetOracle,
|
||||
0.1,
|
||||
0,
|
||||
1, // tokenIndex
|
||||
'BTC',
|
||||
0.4,
|
||||
0.07,
|
||||
|
@ -91,7 +92,7 @@ async function main() {
|
|||
console.log(error);
|
||||
}
|
||||
|
||||
// stub oracle + register token 1
|
||||
// stub oracle + register token 0
|
||||
console.log(`Registering USDC...`);
|
||||
const usdcDevnetMint = new PublicKey(DEVNET_MINTS.get('USDC')!);
|
||||
try {
|
||||
|
@ -109,7 +110,7 @@ async function main() {
|
|||
usdcDevnetMint,
|
||||
usdcDevnetOracle.publicKey,
|
||||
0.1,
|
||||
1,
|
||||
0, // tokenIndex
|
||||
'USDC',
|
||||
0.4,
|
||||
0.07,
|
||||
|
@ -228,7 +229,7 @@ async function main() {
|
|||
0,
|
||||
'BTC-PERP',
|
||||
0.1,
|
||||
0,
|
||||
1,
|
||||
6,
|
||||
1,
|
||||
10,
|
||||
|
@ -251,7 +252,6 @@ async function main() {
|
|||
const perpMarkets = await client.perpGetMarkets(
|
||||
group,
|
||||
group.banksMap.get('BTC')?.tokenIndex,
|
||||
group.banksMap.get('USDC')?.tokenIndex,
|
||||
);
|
||||
console.log(`...created perp market ${perpMarkets[0].publicKey}`);
|
||||
|
||||
|
@ -319,7 +319,7 @@ async function main() {
|
|||
'BTC-PERP',
|
||||
btcDevnetOracle,
|
||||
0.2,
|
||||
1,
|
||||
0,
|
||||
6,
|
||||
0.9,
|
||||
0.9,
|
||||
|
@ -345,7 +345,7 @@ async function main() {
|
|||
'BTC-PERP',
|
||||
btcDevnetOracle,
|
||||
0.1,
|
||||
0,
|
||||
1,
|
||||
6,
|
||||
1,
|
||||
0.95,
|
||||
|
|
|
@ -67,12 +67,12 @@ async function main() {
|
|||
const randomKey = new PublicKey(
|
||||
'4ZkS7ZZkxfsC3GtvvsHP3DFcUeByU9zzZELS4r8HCELo',
|
||||
);
|
||||
await client.editMangoAccount(group, 'my_changed_name', randomKey);
|
||||
await client.editMangoAccount(group, mangoAccount, 'my_changed_name', randomKey);
|
||||
await mangoAccount.reload(client, group);
|
||||
console.log(mangoAccount.toString());
|
||||
|
||||
console.log(`...resetting mango account name, and re-setting a delegate`);
|
||||
await client.editMangoAccount(group, 'my_mango_account', PublicKey.default);
|
||||
await client.editMangoAccount(group, mangoAccount, 'my_mango_account', PublicKey.default);
|
||||
await mangoAccount.reload(client, group);
|
||||
console.log(mangoAccount.toString());
|
||||
}
|
||||
|
|
|
@ -12,4 +12,4 @@ anchor build --skip-lint
|
|||
# update types in ts client package
|
||||
cp -v ./target/types/mango_v4.ts ./ts/client/src/mango_v4.ts
|
||||
|
||||
(cd ./ts/client && tsc)
|
||||
(cd ./ts/client && yarn tsc)
|
||||
|
|
Loading…
Reference in New Issue