diff --git a/lib/client/src/client.rs b/lib/client/src/client.rs index 7e1bc7a83..6fa8bb8e7 100644 --- a/lib/client/src/client.rs +++ b/lib/client/src/client.rs @@ -2599,8 +2599,7 @@ impl TransactionBuilder { } pub fn append(&mut self, prepared_instructions: PreparedInstructions) { - self.instructions - .extend(prepared_instructions.instructions); + self.instructions.extend(prepared_instructions.instructions); } } diff --git a/programs/mango-v4/src/accounts_ix/mod.rs b/programs/mango-v4/src/accounts_ix/mod.rs index 9da159875..1cd1b2a4e 100644 --- a/programs/mango-v4/src/accounts_ix/mod.rs +++ b/programs/mango-v4/src/accounts_ix/mod.rs @@ -44,6 +44,8 @@ pub use perp_liq_base_or_positive_pnl::*; pub use perp_liq_force_cancel_orders::*; pub use perp_liq_negative_pnl_or_bankruptcy::*; pub use perp_place_order::*; +pub use perp_purge_orders::*; +pub use perp_purge_position::*; pub use perp_settle_fees::*; pub use perp_settle_pnl::*; pub use perp_update_funding::*; @@ -125,6 +127,8 @@ mod perp_liq_base_or_positive_pnl; mod perp_liq_force_cancel_orders; mod perp_liq_negative_pnl_or_bankruptcy; mod perp_place_order; +mod perp_purge_orders; +mod perp_purge_position; mod perp_settle_fees; mod perp_settle_pnl; mod perp_update_funding; diff --git a/programs/mango-v4/src/accounts_ix/perp_purge_orders.rs b/programs/mango-v4/src/accounts_ix/perp_purge_orders.rs new file mode 100644 index 000000000..0248848e7 --- /dev/null +++ b/programs/mango-v4/src/accounts_ix/perp_purge_orders.rs @@ -0,0 +1,27 @@ +use crate::state::{BookSide, Group, MangoAccountFixed, PerpMarket}; +use anchor_lang::prelude::*; + +#[derive(Accounts)] +pub struct PerpPurgeOrders<'info> { + #[account()] + pub group: AccountLoader<'info, Group>, + + #[account( + mut, + has_one = group, + // owner is not checked on purpose + )] + pub account: AccountLoader<'info, MangoAccountFixed>, + + #[account( + mut, + has_one = group, + has_one = bids, + has_one = asks, + )] + pub perp_market: AccountLoader<'info, PerpMarket>, + #[account(mut)] + pub bids: AccountLoader<'info, BookSide>, + #[account(mut)] + pub asks: AccountLoader<'info, BookSide>, +} diff --git a/programs/mango-v4/src/accounts_ix/perp_purge_position.rs b/programs/mango-v4/src/accounts_ix/perp_purge_position.rs new file mode 100644 index 000000000..a4122cd71 --- /dev/null +++ b/programs/mango-v4/src/accounts_ix/perp_purge_position.rs @@ -0,0 +1,26 @@ +use anchor_lang::prelude::*; + +use crate::state::*; + +#[derive(Accounts)] +pub struct PerpPurgePosition<'info> { + #[account()] + pub group: AccountLoader<'info, Group>, + + #[account( + mut, + has_one = group, + // owner is not checked on purpose + )] + pub account: AccountLoader<'info, MangoAccountFixed>, + + #[account(has_one = group)] + pub perp_market: AccountLoader<'info, PerpMarket>, + + #[account(mut, has_one = group)] + pub settle_bank: AccountLoader<'info, Bank>, + + /// CHECK: Oracle can have different account types + #[account(address = settle_bank.load()?.oracle)] + pub settle_oracle: UncheckedAccount<'info>, +} diff --git a/programs/mango-v4/src/instructions/mod.rs b/programs/mango-v4/src/instructions/mod.rs index 68b8e9b4c..0ecc0225f 100644 --- a/programs/mango-v4/src/instructions/mod.rs +++ b/programs/mango-v4/src/instructions/mod.rs @@ -34,6 +34,8 @@ pub use perp_liq_base_or_positive_pnl::*; pub use perp_liq_force_cancel_orders::*; pub use perp_liq_negative_pnl_or_bankruptcy::*; pub use perp_place_order::*; +pub use perp_purge_orders::*; +pub use perp_purge_position::*; pub use perp_settle_fees::*; pub use perp_settle_pnl::*; pub use perp_update_funding::*; @@ -106,6 +108,8 @@ mod perp_liq_base_or_positive_pnl; mod perp_liq_force_cancel_orders; mod perp_liq_negative_pnl_or_bankruptcy; mod perp_place_order; +mod perp_purge_orders; +mod perp_purge_position; mod perp_settle_fees; mod perp_settle_pnl; mod perp_update_funding; diff --git a/programs/mango-v4/src/instructions/perp_purge_orders.rs b/programs/mango-v4/src/instructions/perp_purge_orders.rs new file mode 100644 index 000000000..11d1d8cbf --- /dev/null +++ b/programs/mango-v4/src/instructions/perp_purge_orders.rs @@ -0,0 +1,25 @@ +use anchor_lang::prelude::*; + +use crate::accounts_ix::*; +use crate::error::MangoError; +use crate::state::*; + +pub fn perp_purge_orders(ctx: Context, limit: u8) -> Result<()> { + let mut perp_market = ctx.accounts.perp_market.load_mut()?; + // only allow pruning orders when market is in force-close + require!(perp_market.is_force_close(), MangoError::SomeError); + + let mut account = ctx.accounts.account.load_full_mut()?; + let mut book = Orderbook { + bids: ctx.accounts.bids.load_mut()?, + asks: ctx.accounts.asks.load_mut()?, + }; + + book.cancel_all_orders( + &mut account.borrow_mut(), + ctx.accounts.account.as_ref().key, + &mut perp_market, + limit, + None, + ) +} diff --git a/programs/mango-v4/src/instructions/perp_purge_position.rs b/programs/mango-v4/src/instructions/perp_purge_position.rs new file mode 100644 index 000000000..a8cac19a7 --- /dev/null +++ b/programs/mango-v4/src/instructions/perp_purge_position.rs @@ -0,0 +1,92 @@ +use anchor_lang::prelude::*; +use fixed::types::I80F48; + +use crate::accounts_ix::*; +use crate::error::*; +use crate::logs::emit_perp_balances; +use crate::logs::emit_stack; +use crate::logs::TokenBalanceLog; +use crate::require_msg; +use crate::state::*; + +pub fn perp_purge_position(ctx: Context) -> Result<()> { + let perp_market = ctx.accounts.perp_market.load()?; + // only allow purging positions when market is in force-close + require!(perp_market.is_force_close(), MangoError::SomeError); + + let mut settle_bank = ctx.accounts.settle_bank.load_mut()?; + // Verify that the bank is the quote currency bank + require!( + settle_bank.token_index == perp_market.settle_token_index, + MangoError::InvalidBank + ); + + let mut account = ctx.accounts.account.load_full_mut()?; + let perp_position = account.perp_position_mut(perp_market.perp_market_index)?; + + // Base position needs to be zero'd out already and all orders closed + require_msg!( + perp_position.base_position_lots() == 0, + "perp position still has base lots" + ); + require_msg!( + perp_position.bids_base_lots == 0 && perp_position.asks_base_lots == 0, + "perp position still has open orders" + ); + require_msg!( + perp_position.taker_base_lots == 0 && perp_position.taker_quote_lots == 0, + "perp position still has events on event queue" + ); + + // Flush funding so that all remaining quote position is accounted for + perp_position.settle_funding(&perp_market); + let settlement = -perp_position.quote_position_native(); + + // only if there's negative pnl we settle directly with bank + // note: the positive pnl case is not implemented + if settlement != 0 { + require_msg!( + settlement > I80F48::ZERO, + "can only purge negative quote positions" + ); + + // Set perp quote to 0 + perp_position.record_settle(settlement, &perp_market); + emit_perp_balances( + ctx.accounts.group.key(), + ctx.accounts.account.key(), + perp_position, + &perp_market, + ); + + // Update the accounts' perp_spot_transfer statistics. + let settlement_i64 = settlement.round_to_zero().to_num::(); + perp_position.perp_spot_transfers += settlement_i64; + account.fixed.perp_spot_transfers += settlement_i64; + + // Settle quote token balance + let token_position = account + .token_position_mut(perp_market.settle_token_index)? + .0; + let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap(); + settle_bank.withdraw_without_fee(token_position, settlement, now_ts)?; + + emit_stack(TokenBalanceLog { + mango_group: ctx.accounts.group.key(), + mango_account: ctx.accounts.account.key(), + token_index: perp_market.settle_token_index, + indexed_position: token_position.indexed_position.to_bits(), + deposit_index: settle_bank.deposit_index.to_bits(), + borrow_index: settle_bank.borrow_index.to_bits(), + }); + } + + // clean up perp position to free up oracles for users and allow closing market + account.deactivate_perp_position_and_log( + perp_market.perp_market_index, + perp_market.settle_token_index, + ctx.accounts.account.key(), + )?; + + Ok(()) +} diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index 32fec2077..8a4f7bffc 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -1015,6 +1015,12 @@ pub mod mango_v4 { Ok(()) } + pub fn perp_purge_position(ctx: Context) -> Result<()> { + #[cfg(feature = "enable-gpl")] + instructions::perp_purge_position(ctx)?; + Ok(()) + } + #[allow(clippy::too_many_arguments)] pub fn perp_place_order( ctx: Context, @@ -1320,6 +1326,12 @@ pub mod mango_v4 { Ok(()) } + pub fn perp_purge_orders(ctx: Context, limit: u8) -> Result<()> { + #[cfg(feature = "enable-gpl")] + instructions::perp_purge_orders(ctx, limit)?; + Ok(()) + } + pub fn perp_consume_events(ctx: Context, limit: usize) -> Result<()> { #[cfg(feature = "enable-gpl")] instructions::perp_consume_events(ctx, limit)?;