group halt (#370)

* group halt

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* Fixes from review

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* format

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
microwavedcola1 2023-01-12 09:12:55 +01:00 committed by GitHub
parent 93d33edb74
commit 5ef04d6d08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 705 additions and 33 deletions

View File

@ -69,6 +69,8 @@ pub enum MangoError {
TokenInReduceOnlyMode,
#[msg("market is in reduce only mode")]
MarketInReduceOnlyMode,
#[msg("group is halted")]
GroupIsHalted,
#[msg("the perp position has non-zero base lots")]
PerpHasBaseLots,
#[msg("there are open or unsettled serum3 orders")]

View File

@ -6,6 +6,9 @@ use crate::state::*;
#[derive(Accounts)]
pub struct AccountClose<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(

View File

@ -7,6 +7,9 @@ use crate::util::fill_from_str;
#[derive(Accounts)]
#[instruction(account_num: u32, token_count: u8, serum3_count: u8, perp_count: u8, perp_oo_count: u8)]
pub struct AccountCreate<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(

View File

@ -6,6 +6,9 @@ use crate::util::fill_from_str;
#[derive(Accounts)]
pub struct AccountEdit<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(

View File

@ -1,10 +1,14 @@
use anchor_lang::prelude::*;
use crate::error::MangoError;
use crate::state::*;
use crate::util::checked_math as cm;
#[derive(Accounts)]
pub struct AccountExpand<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(

View File

@ -2,12 +2,14 @@ use anchor_lang::prelude::*;
use solana_address_lookup_table_program as solana_alt;
use crate::address_lookup_table_program;
use crate::error::MangoError;
use crate::state::*;
#[derive(Accounts)]
pub struct AltExtend<'info> {
#[account(
has_one = admin,
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
pub admin: Signer<'info>,

View File

@ -9,6 +9,7 @@ pub struct AltSet<'info> {
#[account(
mut,
has_one = admin,
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
pub admin: Signer<'info>,

View File

@ -1,4 +1,4 @@
use crate::state::*;
use crate::{error::MangoError, state::*};
use anchor_lang::prelude::*;
use anchor_spl::token::{self, CloseAccount, Token, TokenAccount};
@ -6,9 +6,10 @@ use anchor_spl::token::{self, CloseAccount, Token, TokenAccount};
pub struct GroupClose<'info> {
#[account(
mut,
constraint = group.load()?.is_testing(),
has_one = admin,
has_one = insurance_vault,
constraint = group.load()?.is_testing(),
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted,
close = sol_destination
)]
pub group: AccountLoader<'info, Group>,

View File

@ -18,6 +18,7 @@ pub fn group_edit(
ctx: Context<GroupEdit>,
admin_opt: Option<Pubkey>,
fast_listing_admin_opt: Option<Pubkey>,
security_admin_opt: Option<Pubkey>,
testing_opt: Option<u8>,
version_opt: Option<u8>,
) -> Result<()> {
@ -31,6 +32,10 @@ pub fn group_edit(
group.fast_listing_admin = fast_listing_admin;
}
if let Some(security_admin) = security_admin_opt {
group.security_admin = security_admin;
}
if let Some(testing) = testing_opt {
group.testing = testing;
}

View File

@ -0,0 +1,19 @@
use anchor_lang::prelude::*;
use crate::state::*;
#[derive(Accounts)]
pub struct GroupToggleHalt<'info> {
#[account(
mut,
constraint = group.load()?.admin == admin.key() || group.load()?.security_admin == admin.key(),
)]
pub group: AccountLoader<'info, Group>,
pub admin: Signer<'info>,
}
pub fn group_toggle_halt(ctx: Context<GroupToggleHalt>, halted: bool) -> Result<()> {
let mut group = ctx.accounts.group.load_mut()?;
group.halted = if halted { 1 } else { 0 };
Ok(())
}

View File

@ -10,6 +10,7 @@ pub use flash_loan::*;
pub use group_close::*;
pub use group_create::*;
pub use group_edit::*;
pub use group_toggle_halt::*;
pub use health_region::*;
pub use perp_cancel_all_orders::*;
pub use perp_cancel_all_orders_by_side::*;
@ -63,6 +64,7 @@ mod flash_loan;
mod group_close;
mod group_create;
mod group_edit;
mod group_toggle_halt;
mod health_region;
mod perp_cancel_all_orders;
mod perp_cancel_all_orders_by_side;

View File

@ -5,6 +5,9 @@ use crate::state::{BookSide, Group, MangoAccountFixed, MangoAccountLoader, Order
#[derive(Accounts)]
pub struct PerpCancelAllOrders<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group)]

View File

@ -7,6 +7,9 @@ use crate::state::{
#[derive(Accounts)]
pub struct PerpCancelAllOrdersBySide<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group)]

View File

@ -5,6 +5,9 @@ use crate::state::{BookSide, Group, MangoAccountFixed, MangoAccountLoader, Order
#[derive(Accounts)]
pub struct PerpCancelOrder<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group)]

View File

@ -5,6 +5,9 @@ use crate::state::{BookSide, Group, MangoAccountFixed, MangoAccountLoader, Order
#[derive(Accounts)]
pub struct PerpCancelOrderByClientOrderId<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group)]

View File

@ -1,12 +1,13 @@
use anchor_lang::prelude::*;
use anchor_spl::token::Token;
use crate::state::*;
use crate::{error::MangoError, state::*};
#[derive(Accounts)]
pub struct PerpCloseMarket<'info> {
#[account(
constraint = group.load()?.is_testing(),
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted,
has_one = admin,
)]
pub group: AccountLoader<'info, Group>,

View File

@ -9,6 +9,9 @@ use crate::logs::{emit_perp_balances, FillLog};
#[derive(Accounts)]
pub struct PerpConsumeEvents<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(

View File

@ -13,7 +13,8 @@ use crate::logs::PerpMarketMetaDataLog;
pub struct PerpCreateMarket<'info> {
#[account(
has_one = admin,
constraint = group.load()?.perps_supported()
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted,
constraint = group.load()?.perps_supported(),
)]
pub group: AccountLoader<'info, Group>,
pub admin: Signer<'info>,

View File

@ -5,6 +5,9 @@ use crate::state::*;
#[derive(Accounts)]
pub struct PerpDeactivatePosition<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(

View File

@ -7,7 +7,7 @@ use crate::logs::PerpMarketMetaDataLog;
#[derive(Accounts)]
pub struct PerpEditMarket<'info> {
#[account(
has_one = admin,
has_one = admin
)]
pub group: AccountLoader<'info, Group>,
pub admin: Signer<'info>,

View File

@ -0,0 +1,239 @@
use anchor_lang::prelude::*;
use anchor_spl::token;
use anchor_spl::token::Token;
use anchor_spl::token::TokenAccount;
use fixed::types::I80F48;
use crate::error::*;
use crate::health::*;
use crate::state::*;
use crate::util::checked_math as cm;
use crate::logs::{emit_perp_balances, PerpLiqBankruptcyLog, TokenBalanceLog};
// Remaining accounts:
// - merged health accounts for liqor+liqee
#[derive(Accounts)]
pub struct PerpLiqBankruptcy<'info> {
#[account(
has_one = insurance_vault,
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group)]
pub perp_market: AccountLoader<'info, PerpMarket>,
#[account(
mut,
has_one = group
// liqor_owner is checked at #1
)]
pub liqor: AccountLoader<'info, MangoAccountFixed>,
pub liqor_owner: Signer<'info>,
#[account(
mut,
has_one = group
)]
pub liqee: AccountLoader<'info, MangoAccountFixed>,
#[account(
mut,
has_one = group,
// address is checked at #2
)]
pub settle_bank: AccountLoader<'info, Bank>,
#[account(
mut,
address = settle_bank.load()?.vault
)]
pub settle_vault: Account<'info, TokenAccount>,
/// CHECK: Oracle can have different account types
#[account(address = settle_bank.load()?.oracle)]
pub settle_oracle: UncheckedAccount<'info>,
// future: this would be an insurance fund vault specific to a
// trustless token, separate from the shared one on the group
#[account(mut)]
pub insurance_vault: Account<'info, TokenAccount>,
pub token_program: Program<'info, Token>,
}
impl<'info> PerpLiqBankruptcy<'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.settle_vault.to_account_info(),
authority: self.group.to_account_info(),
};
CpiContext::new(program, accounts)
}
}
pub fn perp_liq_bankruptcy(ctx: Context<PerpLiqBankruptcy>, max_liab_transfer: u64) -> Result<()> {
let group = ctx.accounts.group.load()?;
let group_pk = &ctx.accounts.group.key();
let mut liqor = ctx.accounts.liqor.load_full_mut()?;
// account constraint #1
require!(
liqor
.fixed
.is_owner_or_delegate(ctx.accounts.liqor_owner.key()),
MangoError::SomeError
);
require!(!liqor.fixed.being_liquidated(), MangoError::BeingLiquidated);
let mut liqee = ctx.accounts.liqee.load_full_mut()?;
let mut liqee_health_cache = {
let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk)?;
new_health_cache(&liqee.borrow(), &account_retriever)
.context("create liqee health cache")?
};
// Check if liqee is bankrupt
require!(
!liqee_health_cache.has_liquidatable_assets(),
MangoError::IsNotBankrupt
);
liqee.fixed.set_being_liquidated(true);
// Find bankrupt liab amount
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
let settle_token_index = perp_market.settle_token_index;
let liqee_perp_position = liqee.perp_position_mut(perp_market.perp_market_index)?;
require_msg!(
liqee_perp_position.base_position_lots() == 0,
"liqee must have zero base position"
);
require!(
!liqee_perp_position.has_open_orders(),
MangoError::HasOpenPerpOrders
);
let liqee_pnl = liqee_perp_position.quote_position_native();
require_msg!(
liqee_pnl.is_negative(),
"liqee pnl must be negative, was {}",
liqee_pnl
);
let liab_transfer = (-liqee_pnl).min(I80F48::from(max_liab_transfer));
// Preparation for covering it with the insurance fund
let insurance_vault_amount = if perp_market.elligible_for_group_insurance_fund() {
ctx.accounts.insurance_vault.amount
} else {
0
};
let liquidation_fee_factor = cm!(I80F48::ONE + perp_market.liquidation_fee);
let insurance_transfer = cm!(liab_transfer * liquidation_fee_factor)
.checked_ceil()
.unwrap()
.checked_to_num::<u64>()
.unwrap()
.min(insurance_vault_amount);
let insurance_transfer_i80f48 = I80F48::from(insurance_transfer);
let insurance_fund_exhausted = insurance_transfer == insurance_vault_amount;
let insurance_liab_transfer =
cm!(insurance_transfer_i80f48 / liquidation_fee_factor).min(liab_transfer);
// Try using the insurance fund if possible
if insurance_transfer > 0 {
let mut settle_bank = ctx.accounts.settle_bank.load_mut()?;
require_eq!(settle_bank.token_index, settle_token_index);
require_keys_eq!(settle_bank.mint, ctx.accounts.insurance_vault.mint);
// move insurance assets into quote bank
let group_seeds = group_seeds!(group);
token::transfer(
ctx.accounts.transfer_ctx().with_signer(&[group_seeds]),
insurance_transfer,
)?;
// credit the liqor with quote tokens
let (liqor_quote, _, _) = liqor.ensure_token_position(settle_token_index)?;
settle_bank.deposit(
liqor_quote,
insurance_transfer_i80f48,
Clock::get()?.unix_timestamp.try_into().unwrap(),
)?;
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(),
token_index: settle_token_index,
indexed_position: liqor_quote.indexed_position.to_bits(),
deposit_index: settle_bank.deposit_index.to_bits(),
borrow_index: settle_bank.borrow_index.to_bits(),
});
// transfer perp quote loss from the liqee to the liqor
let liqor_perp_position = liqor
.ensure_perp_position(perp_market.perp_market_index, settle_token_index)?
.0;
liqee_perp_position.record_settle(-insurance_liab_transfer);
liqor_perp_position.record_liquidation_quote_change(-insurance_liab_transfer);
emit_perp_balances(
ctx.accounts.group.key(),
ctx.accounts.liqor.key(),
perp_market.perp_market_index,
liqor_perp_position,
&perp_market,
);
}
// Socialize loss if the insurance fund is exhausted
let remaining_liab = liab_transfer - insurance_liab_transfer;
let mut socialized_loss = I80F48::ZERO;
if insurance_fund_exhausted && remaining_liab.is_positive() {
perp_market.socialize_loss(-remaining_liab)?;
liqee_perp_position.record_settle(-remaining_liab);
require_eq!(liqee_perp_position.quote_position_native(), 0);
socialized_loss = remaining_liab;
}
emit_perp_balances(
ctx.accounts.group.key(),
ctx.accounts.liqee.key(),
perp_market.perp_market_index,
liqee_perp_position,
&perp_market,
);
emit!(PerpLiqBankruptcyLog {
mango_group: ctx.accounts.group.key(),
liqee: ctx.accounts.liqee.key(),
liqor: ctx.accounts.liqor.key(),
perp_market_index: perp_market.perp_market_index,
insurance_transfer: insurance_transfer_i80f48.to_bits(),
socialized_loss: socialized_loss.to_bits()
});
// Check liqee health again
liqee_health_cache.recompute_perp_info(liqee_perp_position, &perp_market)?;
let liqee_init_health = liqee_health_cache.health(HealthType::Init);
liqee
.fixed
.maybe_recover_from_being_liquidated(liqee_init_health);
drop(perp_market);
// Check liqor's health
if !liqor.fixed.is_in_health_region() {
let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk)?;
let liqor_health = compute_health(&liqor.borrow(), HealthType::Init, &account_retriever)
.context("compute liqor health")?;
require!(liqor_health >= 0, MangoError::HealthMustBePositive);
}
Ok(())
}

View File

@ -11,6 +11,9 @@ use crate::logs::{emit_perp_balances, PerpLiqBasePositionLog};
#[derive(Accounts)]
pub struct PerpLiqBasePosition<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group, has_one = oracle)]

View File

@ -7,6 +7,9 @@ use crate::state::*;
#[derive(Accounts)]
pub struct PerpLiqForceCancelOrders<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group)]

View File

@ -11,6 +11,9 @@ use crate::state::{
#[derive(Accounts)]
pub struct PerpPlaceOrder<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group)]

View File

@ -12,6 +12,9 @@ use crate::logs::{emit_perp_balances, PerpSettleFeesLog, TokenBalanceLog};
#[derive(Accounts)]
pub struct PerpSettleFees<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group, has_one = oracle)]

View File

@ -11,6 +11,9 @@ use crate::state::{Group, MangoAccountFixed, MangoAccountLoader, PerpMarket};
#[derive(Accounts)]
pub struct PerpSettlePnl<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(

View File

@ -1,10 +1,14 @@
use anchor_lang::prelude::*;
use crate::accounts_zerocopy::*;
use crate::error::MangoError;
use crate::state::{BookSide, Group, Orderbook, PerpMarket};
#[derive(Accounts)]
pub struct PerpUpdateFunding<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>, // Required for group metadata parsing
#[account(

View File

@ -8,6 +8,9 @@ use crate::state::*;
#[derive(Accounts)]
pub struct Serum3CancelAllOrders<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(

View File

@ -13,6 +13,9 @@ use crate::serum3_cpi::load_open_orders_ref;
#[derive(Accounts)]
pub struct Serum3CancelOrder<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(

View File

@ -5,6 +5,9 @@ use crate::state::*;
#[derive(Accounts)]
pub struct Serum3CloseOpenOrders<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(

View File

@ -5,6 +5,9 @@ use crate::state::*;
#[derive(Accounts)]
pub struct Serum3CreateOpenOrders<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(

View File

@ -1,14 +1,15 @@
use anchor_lang::prelude::*;
use anchor_spl::token::Token;
use crate::state::*;
use crate::{error::MangoError, state::*};
#[derive(Accounts)]
pub struct Serum3DeregisterMarket<'info> {
#[account(
mut,
constraint = group.load()?.is_testing(),
has_one = admin,
constraint = group.load()?.is_testing(),
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
pub admin: Signer<'info>,

View File

@ -14,6 +14,9 @@ use crate::state::*;
#[derive(Accounts)]
pub struct Serum3LiqForceCancelOrders<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group)]

View File

@ -135,6 +135,9 @@ pub enum Serum3Side {
#[derive(Accounts)]
pub struct Serum3PlaceOrder<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(

View File

@ -13,6 +13,7 @@ pub struct Serum3RegisterMarket<'info> {
#[account(
mut,
has_one = admin,
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted,
constraint = group.load()?.serum3_supported()
)]
pub group: AccountLoader<'info, Group>,

View File

@ -13,6 +13,9 @@ use crate::logs::{LoanOriginationFeeInstruction, WithdrawLoanOriginationFeeLog};
#[derive(Accounts)]
pub struct Serum3SettleFunds<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(

View File

@ -1,13 +1,14 @@
use anchor_lang::prelude::*;
use anchor_spl::token::Token;
use crate::state::*;
use crate::{error::MangoError, state::*};
#[derive(Accounts)]
pub struct StubOracleClose<'info> {
#[account(
constraint = group.load()?.is_testing(),
has_one = admin,
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted,
constraint = group.load()?.is_testing(),
)]
pub group: AccountLoader<'info, Group>,
pub admin: Signer<'info>,

View File

@ -2,12 +2,13 @@ use anchor_lang::prelude::*;
use anchor_spl::token::Mint;
use fixed::types::I80F48;
use crate::state::*;
use crate::{error::MangoError, state::*};
#[derive(Accounts)]
pub struct StubOracleCreate<'info> {
#[account(
has_one = admin,
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,

View File

@ -1,12 +1,13 @@
use anchor_lang::prelude::*;
use fixed::types::I80F48;
use crate::state::*;
use crate::{error::MangoError, state::*};
#[derive(Accounts)]
pub struct StubOracleSet<'info> {
#[account(
has_one = admin,
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,

View File

@ -9,6 +9,7 @@ use crate::state::*;
pub struct TokenAddBank<'info> {
#[account(
has_one = admin,
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted,
constraint = group.load()?.multiple_banks_supported()
)]
pub group: AccountLoader<'info, Group>,

View File

@ -15,6 +15,9 @@ use crate::logs::{DepositLog, TokenBalanceLog};
// Same as TokenDeposit, but without the owner signing
#[derive(Accounts)]
pub struct TokenDepositIntoExisting<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group)]
@ -45,6 +48,9 @@ pub struct TokenDepositIntoExisting<'info> {
#[derive(Accounts)]
pub struct TokenDeposit<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group, has_one = owner)]

View File

@ -1,7 +1,7 @@
use anchor_lang::prelude::*;
use anchor_spl::token::{self, CloseAccount, Token, TokenAccount};
use crate::{accounts_zerocopy::LoadZeroCopyRef, state::*};
use crate::{accounts_zerocopy::LoadZeroCopyRef, error::MangoError, state::*};
use anchor_lang::AccountsClose;
/// In addition to these accounts, there must be remaining_accounts:
@ -9,8 +9,9 @@ use anchor_lang::AccountsClose;
#[derive(Accounts)]
pub struct TokenDeregister<'info> {
#[account(
constraint = group.load()?.is_testing(),
has_one = admin,
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted,
constraint = group.load()?.is_testing(),
)]
pub group: AccountLoader<'info, Group>,
pub admin: Signer<'info>,

View File

@ -16,7 +16,7 @@ use crate::logs::TokenMetaDataLog;
#[derive(Accounts)]
pub struct TokenEdit<'info> {
#[account(
has_one = admin,
has_one = admin
)]
pub group: AccountLoader<'info, Group>,
pub admin: Signer<'info>,

View File

@ -22,6 +22,7 @@ use crate::logs::{
pub struct TokenLiqBankruptcy<'info> {
#[account(
has_one = insurance_vault,
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,

View File

@ -13,6 +13,9 @@ use crate::util::checked_math as cm;
#[derive(Accounts)]
pub struct TokenLiqWithToken<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(

View File

@ -19,6 +19,7 @@ const FIRST_BANK_NUM: u32 = 0;
pub struct TokenRegister<'info> {
#[account(
has_one = admin,
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
pub admin: Signer<'info>,

View File

@ -17,6 +17,7 @@ const FIRST_BANK_NUM: u32 = 0;
pub struct TokenRegisterTrustless<'info> {
#[account(
has_one = fast_listing_admin,
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
pub fast_listing_admin: Signer<'info>,

View File

@ -26,6 +26,9 @@ pub mod compute_budget {
/// or ComputeBudget instructions.
#[derive(Accounts)]
pub struct TokenUpdateIndexAndRate<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>, // Required for group metadata parsing
#[account(

View File

@ -15,6 +15,9 @@ use crate::util::checked_math as cm;
#[derive(Accounts)]
pub struct TokenWithdraw<'info> {
#[account(
constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted
)]
pub group: AccountLoader<'info, Group>,
#[account(mut, has_one = group, has_one = owner)]

View File

@ -45,6 +45,7 @@ pub mod mango_v4 {
ctx: Context<GroupEdit>,
admin_opt: Option<Pubkey>,
fast_listing_admin_opt: Option<Pubkey>,
security_admin_opt: Option<Pubkey>,
testing_opt: Option<u8>,
version_opt: Option<u8>,
) -> Result<()> {
@ -52,11 +53,16 @@ pub mod mango_v4 {
ctx,
admin_opt,
fast_listing_admin_opt,
security_admin_opt,
testing_opt,
version_opt,
)
}
pub fn group_toggle_halt(ctx: Context<GroupToggleHalt>, halted: bool) -> Result<()> {
instructions::group_toggle_halt(ctx, halted)
}
pub fn group_close(ctx: Context<GroupClose>) -> Result<()> {
instructions::group_close(ctx)
}

View File

@ -31,15 +31,19 @@ pub struct Group {
pub version: u8,
pub padding2: [u8; 5],
pub halted: u8,
pub padding2: [u8; 4],
pub address_lookup_tables: [Pubkey; 20],
pub reserved: [u8; 1920],
pub security_admin: Pubkey,
pub reserved: [u8; 1888],
}
const_assert_eq!(
size_of::<Group>(),
32 * 5 + 4 + 4 + 1 + 1 + 6 + 20 * 32 + 1920
32 + 4 + 32 * 2 + 4 + 32 * 2 + 4 + 4 + 20 * 32 + 32 + 1888
);
const_assert_eq!(size_of::<Group>(), 2736);
const_assert_eq!(size_of::<Group>() % 8, 0);
@ -60,6 +64,10 @@ impl Group {
pub fn perps_supported(&self) -> bool {
self.is_testing() || self.version > 1
}
pub fn is_operational(&self) -> bool {
self.halted == 0
}
}
// note: using creator instead of admin, since admin can be changed

View File

@ -129,6 +129,7 @@ export class MangoClient {
group: Group,
admin?: PublicKey,
fastListingAdmin?: PublicKey,
securityAdmin?: PublicKey,
testing?: number,
version?: number,
): Promise<TransactionSignature> {
@ -136,6 +137,7 @@ export class MangoClient {
.groupEdit(
admin ?? null,
fastListingAdmin ?? null,
securityAdmin ?? null,
testing ?? null,
version ?? null,
)
@ -146,6 +148,19 @@ export class MangoClient {
.rpc();
}
public async groupToggleHalt(
group: Group,
halt: boolean,
): Promise<TransactionSignature> {
return await this.program.methods
.groupToggleHalt(halt)
.accounts({
group: group.publicKey,
admin: (this.program.provider as AnchorProvider).wallet.publicKey,
})
.rpc();
}
public async groupClose(group: Group): Promise<TransactionSignature> {
const adminPk = (this.program.provider as AnchorProvider).wallet.publicKey;
return await this.program.methods

View File

@ -121,6 +121,12 @@ export type MangoV4 = {
"option": "publicKey"
}
},
{
"name": "securityAdminOpt",
"type": {
"option": "publicKey"
}
},
{
"name": "testingOpt",
"type": {
@ -135,6 +141,27 @@ export type MangoV4 = {
}
]
},
{
"name": "groupToggleHalt",
"accounts": [
{
"name": "group",
"isMut": true,
"isSigner": false
},
{
"name": "admin",
"isMut": false,
"isSigner": true
}
],
"args": [
{
"name": "halted",
"type": "bool"
}
]
},
{
"name": "groupClose",
"accounts": [
@ -3364,18 +3391,13 @@ export type MangoV4 = {
]
},
{
"name": "perpLiqBankruptcy",
"name": "perpLiqQuoteAndBankruptcy",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "perpMarket",
"isMut": true,
"isSigner": false
},
{
"name": "liqor",
"isMut": true,
@ -3391,6 +3413,16 @@ export type MangoV4 = {
"isMut": true,
"isSigner": false
},
{
"name": "perpMarket",
"isMut": true,
"isSigner": false
},
{
"name": "oracle",
"isMut": false,
"isSigner": false
},
{
"name": "settleBank",
"isMut": true,
@ -3850,12 +3882,16 @@ export type MangoV4 = {
"name": "version",
"type": "u8"
},
{
"name": "halted",
"type": "u8"
},
{
"name": "padding2",
"type": {
"array": [
"u8",
5
4
]
}
},
@ -3868,12 +3904,16 @@ export type MangoV4 = {
]
}
},
{
"name": "securityAdmin",
"type": "publicKey"
},
{
"name": "reserved",
"type": {
"array": [
"u8",
1920
1888
]
}
}
@ -4778,6 +4818,13 @@ export type MangoV4 = {
{
"name": "marketIndex",
"type": "u16"
},
{
"name": "hasZeroFunds",
"docs": [
"The open orders account has no free or reserved funds"
],
"type": "bool"
}
]
}
@ -7042,6 +7089,16 @@ export type MangoV4 = {
"name": "socializedLoss",
"type": "i128",
"index": false
},
{
"name": "startingLiabDepositIndex",
"type": "i128",
"index": false
},
{
"name": "endingLiabDepositIndex",
"type": "i128",
"index": false
}
]
},
@ -7307,6 +7364,56 @@ export type MangoV4 = {
"name": "socializedLoss",
"type": "i128",
"index": false
},
{
"name": "startingLongFunding",
"type": "i128",
"index": false
},
{
"name": "startingShortFunding",
"type": "i128",
"index": false
},
{
"name": "endingLongFunding",
"type": "i128",
"index": false
},
{
"name": "endingShortFunding",
"type": "i128",
"index": false
}
]
},
{
"name": "PerpLiqQuoteAndBankruptcyLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",
"index": false
},
{
"name": "liqor",
"type": "publicKey",
"index": false
},
{
"name": "perpMarketIndex",
"type": "u16",
"index": false
},
{
"name": "settlement",
"type": "i128",
"index": false
}
]
},
@ -7536,6 +7643,36 @@ export type MangoV4 = {
"code": 6031,
"name": "MarketInReduceOnlyMode",
"msg": "market is in reduce only mode"
},
{
"code": 6032,
"name": "GroupIsHalted",
"msg": "group is halted"
},
{
"code": 6033,
"name": "PerpHasBaseLots",
"msg": "the perp position has non-zero base lots"
},
{
"code": 6034,
"name": "HasOpenOrUnsettledSerum3Orders",
"msg": "there are open or unsettled serum3 orders"
},
{
"code": 6035,
"name": "HasLiquidatableTokenPosition",
"msg": "has liquidatable token position"
},
{
"code": 6036,
"name": "HasLiquidatablePerpBasePosition",
"msg": "has liquidatable perp base position"
},
{
"code": 6037,
"name": "HasLiquidatableTrustedPerpPnl",
"msg": "has liquidatable trusted perp pnl"
}
]
};
@ -7663,6 +7800,12 @@ export const IDL: MangoV4 = {
"option": "publicKey"
}
},
{
"name": "securityAdminOpt",
"type": {
"option": "publicKey"
}
},
{
"name": "testingOpt",
"type": {
@ -7677,6 +7820,27 @@ export const IDL: MangoV4 = {
}
]
},
{
"name": "groupToggleHalt",
"accounts": [
{
"name": "group",
"isMut": true,
"isSigner": false
},
{
"name": "admin",
"isMut": false,
"isSigner": true
}
],
"args": [
{
"name": "halted",
"type": "bool"
}
]
},
{
"name": "groupClose",
"accounts": [
@ -10906,18 +11070,13 @@ export const IDL: MangoV4 = {
]
},
{
"name": "perpLiqBankruptcy",
"name": "perpLiqQuoteAndBankruptcy",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "perpMarket",
"isMut": true,
"isSigner": false
},
{
"name": "liqor",
"isMut": true,
@ -10933,6 +11092,16 @@ export const IDL: MangoV4 = {
"isMut": true,
"isSigner": false
},
{
"name": "perpMarket",
"isMut": true,
"isSigner": false
},
{
"name": "oracle",
"isMut": false,
"isSigner": false
},
{
"name": "settleBank",
"isMut": true,
@ -11392,12 +11561,16 @@ export const IDL: MangoV4 = {
"name": "version",
"type": "u8"
},
{
"name": "halted",
"type": "u8"
},
{
"name": "padding2",
"type": {
"array": [
"u8",
5
4
]
}
},
@ -11410,12 +11583,16 @@ export const IDL: MangoV4 = {
]
}
},
{
"name": "securityAdmin",
"type": "publicKey"
},
{
"name": "reserved",
"type": {
"array": [
"u8",
1920
1888
]
}
}
@ -12320,6 +12497,13 @@ export const IDL: MangoV4 = {
{
"name": "marketIndex",
"type": "u16"
},
{
"name": "hasZeroFunds",
"docs": [
"The open orders account has no free or reserved funds"
],
"type": "bool"
}
]
}
@ -14584,6 +14768,16 @@ export const IDL: MangoV4 = {
"name": "socializedLoss",
"type": "i128",
"index": false
},
{
"name": "startingLiabDepositIndex",
"type": "i128",
"index": false
},
{
"name": "endingLiabDepositIndex",
"type": "i128",
"index": false
}
]
},
@ -14849,6 +15043,56 @@ export const IDL: MangoV4 = {
"name": "socializedLoss",
"type": "i128",
"index": false
},
{
"name": "startingLongFunding",
"type": "i128",
"index": false
},
{
"name": "startingShortFunding",
"type": "i128",
"index": false
},
{
"name": "endingLongFunding",
"type": "i128",
"index": false
},
{
"name": "endingShortFunding",
"type": "i128",
"index": false
}
]
},
{
"name": "PerpLiqQuoteAndBankruptcyLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",
"index": false
},
{
"name": "liqor",
"type": "publicKey",
"index": false
},
{
"name": "perpMarketIndex",
"type": "u16",
"index": false
},
{
"name": "settlement",
"type": "i128",
"index": false
}
]
},
@ -15078,6 +15322,36 @@ export const IDL: MangoV4 = {
"code": 6031,
"name": "MarketInReduceOnlyMode",
"msg": "market is in reduce only mode"
},
{
"code": 6032,
"name": "GroupIsHalted",
"msg": "group is halted"
},
{
"code": 6033,
"name": "PerpHasBaseLots",
"msg": "the perp position has non-zero base lots"
},
{
"code": 6034,
"name": "HasOpenOrUnsettledSerum3Orders",
"msg": "there are open or unsettled serum3 orders"
},
{
"code": 6035,
"name": "HasLiquidatableTokenPosition",
"msg": "has liquidatable token position"
},
{
"code": 6036,
"name": "HasLiquidatablePerpBasePosition",
"msg": "has liquidatable perp base position"
},
{
"code": 6037,
"name": "HasLiquidatableTrustedPerpPnl",
"msg": "has liquidatable trusted perp pnl"
}
]
};