2022-09-05 07:18:40 -07:00
|
|
|
use anchor_lang::prelude::*;
|
|
|
|
use checked_math as cm;
|
|
|
|
use fixed::types::I80F48;
|
|
|
|
|
|
|
|
use crate::accounts_zerocopy::*;
|
|
|
|
use crate::error::*;
|
2022-12-08 04:12:43 -08:00
|
|
|
use crate::health::{compute_health, new_fixed_order_account_retriever, HealthType};
|
2022-09-05 07:18:40 -07:00
|
|
|
use crate::state::Bank;
|
2023-01-26 11:27:39 -08:00
|
|
|
use crate::state::IxGate;
|
2022-12-29 02:48:46 -08:00
|
|
|
use crate::state::{Group, MangoAccountFixed, MangoAccountLoader, PerpMarket};
|
2022-09-05 07:18:40 -07:00
|
|
|
|
2022-10-07 12:12:55 -07:00
|
|
|
use crate::logs::{emit_perp_balances, PerpSettleFeesLog, TokenBalanceLog};
|
|
|
|
|
2022-09-05 07:18:40 -07:00
|
|
|
#[derive(Accounts)]
|
|
|
|
pub struct PerpSettleFees<'info> {
|
2023-01-12 00:12:55 -08:00
|
|
|
#[account(
|
2023-01-26 11:27:39 -08:00
|
|
|
constraint = group.load()?.is_ix_enabled(IxGate::PerpSettleFees) @ MangoError::IxIsDisabled,
|
2023-01-12 00:12:55 -08:00
|
|
|
)]
|
2022-09-05 07:18:40 -07:00
|
|
|
pub group: AccountLoader<'info, Group>,
|
|
|
|
|
|
|
|
#[account(mut, has_one = group, has_one = oracle)]
|
|
|
|
pub perp_market: AccountLoader<'info, PerpMarket>,
|
|
|
|
|
|
|
|
// This account MUST have a loss
|
2023-01-12 04:08:10 -08:00
|
|
|
#[account(
|
|
|
|
mut,
|
|
|
|
has_one = group,
|
|
|
|
constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen
|
|
|
|
)]
|
2022-12-29 02:48:46 -08:00
|
|
|
pub account: AccountLoader<'info, MangoAccountFixed>,
|
2022-09-05 07:18:40 -07:00
|
|
|
|
2022-09-15 00:24:35 -07:00
|
|
|
/// CHECK: Oracle can have different account types, constrained by address in perp_market
|
2022-09-05 07:18:40 -07:00
|
|
|
pub oracle: UncheckedAccount<'info>,
|
|
|
|
|
2023-01-18 04:19:10 -08:00
|
|
|
// bank correctness is checked at #2
|
2022-09-05 07:18:40 -07:00
|
|
|
#[account(mut, has_one = group)]
|
2022-09-29 05:13:28 -07:00
|
|
|
pub settle_bank: AccountLoader<'info, Bank>,
|
|
|
|
|
|
|
|
/// CHECK: Oracle can have different account types
|
|
|
|
#[account(address = settle_bank.load()?.oracle)]
|
|
|
|
pub settle_oracle: UncheckedAccount<'info>,
|
2022-09-05 07:18:40 -07:00
|
|
|
}
|
|
|
|
|
2022-09-09 03:29:02 -07:00
|
|
|
pub fn perp_settle_fees(ctx: Context<PerpSettleFees>, max_settle_amount: u64) -> Result<()> {
|
2022-09-05 07:18:40 -07:00
|
|
|
// max_settle_amount must greater than zero
|
|
|
|
require!(
|
|
|
|
max_settle_amount > 0,
|
|
|
|
MangoError::MaxSettleAmountMustBeGreaterThanZero
|
|
|
|
);
|
|
|
|
|
2022-12-29 02:48:46 -08:00
|
|
|
let mut account = ctx.accounts.account.load_full_mut()?;
|
2023-01-18 04:19:10 -08:00
|
|
|
let mut settle_bank = ctx.accounts.settle_bank.load_mut()?;
|
2022-09-05 07:18:40 -07:00
|
|
|
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
|
|
|
|
2023-01-18 04:19:10 -08:00
|
|
|
// Verify that the bank is the quote currency bank (#2)
|
2022-09-29 05:13:28 -07:00
|
|
|
require_eq!(
|
2023-01-18 04:19:10 -08:00
|
|
|
settle_bank.token_index,
|
2022-09-29 05:13:28 -07:00
|
|
|
perp_market.settle_token_index,
|
2022-09-05 07:18:40 -07:00
|
|
|
MangoError::InvalidBank
|
|
|
|
);
|
|
|
|
|
|
|
|
// Get oracle price for market. Price is validated inside
|
2022-11-10 06:47:11 -08:00
|
|
|
let oracle_price = perp_market.oracle_price(
|
|
|
|
&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?,
|
|
|
|
None, // staleness checked in health
|
|
|
|
)?;
|
2022-09-05 07:18:40 -07:00
|
|
|
|
|
|
|
// Fetch perp positions for accounts
|
|
|
|
let perp_position = account.perp_position_mut(perp_market.perp_market_index)?;
|
|
|
|
|
|
|
|
// Settle funding before settling any PnL
|
|
|
|
perp_position.settle_funding(&perp_market);
|
|
|
|
|
2022-12-08 01:16:06 -08:00
|
|
|
// Calculate PnL
|
2023-01-17 05:07:58 -08:00
|
|
|
let pnl = perp_position.unsettled_pnl(&perp_market, oracle_price)?;
|
2022-09-05 07:18:40 -07:00
|
|
|
|
|
|
|
// Account perp position must have a loss to be able to settle against the fee account
|
|
|
|
require!(pnl.is_negative(), MangoError::ProfitabilityMismatch);
|
|
|
|
require!(
|
|
|
|
perp_market.fees_accrued.is_positive(),
|
|
|
|
MangoError::ProfitabilityMismatch
|
|
|
|
);
|
|
|
|
|
2023-01-11 05:32:15 -08:00
|
|
|
let settleable_pnl = perp_position.apply_pnl_settle_limit(&perp_market, pnl);
|
|
|
|
require!(
|
|
|
|
settleable_pnl.is_negative(),
|
|
|
|
MangoError::ProfitabilityMismatch
|
|
|
|
);
|
|
|
|
|
2022-09-05 07:18:40 -07:00
|
|
|
// Settle for the maximum possible capped to max_settle_amount
|
2023-01-11 05:32:15 -08:00
|
|
|
let settlement = settleable_pnl
|
2022-09-05 07:18:40 -07:00
|
|
|
.abs()
|
|
|
|
.min(perp_market.fees_accrued.abs())
|
2022-09-09 03:29:02 -07:00
|
|
|
.min(I80F48::from(max_settle_amount));
|
2023-01-11 05:32:15 -08:00
|
|
|
require!(settlement >= 0, MangoError::SettlementAmountMustBePositive);
|
|
|
|
|
2022-11-30 04:20:19 -08:00
|
|
|
perp_position.record_settle(-settlement); // settle the negative pnl on the user perp position
|
2022-09-05 07:18:40 -07:00
|
|
|
perp_market.fees_accrued = cm!(perp_market.fees_accrued - settlement);
|
|
|
|
|
2022-10-07 12:12:55 -07:00
|
|
|
emit_perp_balances(
|
|
|
|
ctx.accounts.group.key(),
|
|
|
|
ctx.accounts.account.key(),
|
|
|
|
perp_position,
|
|
|
|
&perp_market,
|
|
|
|
);
|
|
|
|
|
|
|
|
// Update the account's perp_spot_transfers with the new PnL
|
2022-09-05 07:18:40 -07:00
|
|
|
let settlement_i64 = settlement.round().checked_to_num::<i64>().unwrap();
|
2022-11-17 23:58:56 -08:00
|
|
|
|
|
|
|
// Safety check to prevent any accidental negative transfer
|
|
|
|
require!(
|
|
|
|
settlement_i64 >= 0,
|
|
|
|
MangoError::SettlementAmountMustBePositive
|
|
|
|
);
|
|
|
|
|
2022-10-07 12:12:55 -07:00
|
|
|
cm!(perp_position.perp_spot_transfers -= settlement_i64);
|
|
|
|
cm!(account.fixed.perp_spot_transfers -= settlement_i64);
|
2022-09-05 07:18:40 -07:00
|
|
|
|
|
|
|
// Transfer token balances
|
2022-09-29 05:13:28 -07:00
|
|
|
let token_position = account
|
|
|
|
.token_position_mut(perp_market.settle_token_index)?
|
|
|
|
.0;
|
2023-01-18 04:19:10 -08:00
|
|
|
settle_bank.withdraw_without_fee(
|
2022-11-25 04:45:17 -08:00
|
|
|
token_position,
|
|
|
|
settlement,
|
|
|
|
Clock::get()?.unix_timestamp.try_into().unwrap(),
|
2022-12-02 03:24:11 -08:00
|
|
|
oracle_price,
|
2022-11-25 04:45:17 -08:00
|
|
|
)?;
|
2022-09-05 07:18:40 -07:00
|
|
|
// Update the settled balance on the market itself
|
|
|
|
perp_market.fees_settled = cm!(perp_market.fees_settled + settlement);
|
|
|
|
|
2022-10-07 12:12:55 -07:00
|
|
|
emit!(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(),
|
2023-01-18 04:19:10 -08:00
|
|
|
deposit_index: settle_bank.deposit_index.to_bits(),
|
|
|
|
borrow_index: settle_bank.borrow_index.to_bits(),
|
2022-10-07 12:12:55 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
emit!(PerpSettleFeesLog {
|
|
|
|
mango_group: ctx.accounts.group.key(),
|
|
|
|
mango_account: ctx.accounts.account.key(),
|
|
|
|
perp_market_index: perp_market.perp_market_index,
|
|
|
|
settlement: settlement.to_bits(),
|
|
|
|
});
|
|
|
|
|
2022-09-05 07:18:40 -07:00
|
|
|
// Bank & perp_market are dropped to prevent re-borrow from remaining_accounts
|
2023-01-18 04:19:10 -08:00
|
|
|
drop(settle_bank);
|
2022-09-05 07:18:40 -07:00
|
|
|
drop(perp_market);
|
|
|
|
|
|
|
|
// Verify that the result of settling did not violate the health of the account that lost money
|
|
|
|
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
|
|
|
|
let health = compute_health(&account.borrow(), HealthType::Init, &retriever)?;
|
|
|
|
require!(health >= 0, MangoError::HealthMustBePositive);
|
|
|
|
|
|
|
|
msg!("settled fees = {}", settlement);
|
|
|
|
Ok(())
|
|
|
|
}
|