mango-v4/programs/mango-v4/src/state/mango_account_components.rs

297 lines
9.4 KiB
Rust

use anchor_lang::prelude::*;
use checked_math as cm;
use fixed::types::I80F48;
use static_assertions::const_assert_eq;
use std::cmp::Ordering;
use std::mem::size_of;
use crate::state::*;
pub const FREE_ORDER_SLOT: PerpMarketIndex = PerpMarketIndex::MAX;
#[zero_copy]
#[derive(AnchorDeserialize, AnchorSerialize, Debug)]
pub struct TokenPosition {
// TODO: Why did we have deposits and borrows as two different values
// if only one of them was allowed to be != 0 at a time?
// todo: maybe we want to split collateral and lending?
// todo: see https://github.com/blockworks-foundation/mango-v4/issues/1
// todo: how does ftx do this?
/// The deposit_index (if positive) or borrow_index (if negative) scaled position
pub indexed_position: I80F48,
/// index into Group.tokens
pub token_index: TokenIndex,
/// incremented when a market requires this position to stay alive
pub in_use_count: u8,
pub reserved: [u8; 5],
}
unsafe impl bytemuck::Pod for TokenPosition {}
unsafe impl bytemuck::Zeroable for TokenPosition {}
const_assert_eq!(size_of::<TokenPosition>(), 24);
const_assert_eq!(size_of::<TokenPosition>() % 8, 0);
impl Default for TokenPosition {
fn default() -> Self {
TokenPosition {
indexed_position: I80F48::ZERO,
token_index: TokenIndex::MAX,
in_use_count: 0,
reserved: Default::default(),
}
}
}
impl TokenPosition {
pub fn is_active(&self) -> bool {
self.token_index != TokenIndex::MAX
}
pub fn is_active_for_token(&self, token_index: TokenIndex) -> bool {
self.token_index == token_index
}
pub fn native(&self, bank: &Bank) -> I80F48 {
if self.indexed_position.is_positive() {
self.indexed_position * bank.deposit_index
} else {
self.indexed_position * bank.borrow_index
}
}
pub fn ui(&self, bank: &Bank) -> I80F48 {
if self.indexed_position.is_positive() {
(self.indexed_position * bank.deposit_index)
/ I80F48::from_num(10u64.pow(bank.mint_decimals as u32))
} else {
(self.indexed_position * bank.borrow_index)
/ I80F48::from_num(10u64.pow(bank.mint_decimals as u32))
}
}
pub fn is_in_use(&self) -> bool {
self.in_use_count > 0
}
}
#[zero_copy]
#[derive(AnchorSerialize, AnchorDeserialize, Debug)]
pub struct Serum3Orders {
pub open_orders: Pubkey,
// tracks reserved funds in open orders account,
// used for bookkeeping of potentital loans which
// can be charged with loan origination fees
// e.g. serum3 settle funds ix
pub previous_native_coin_reserved: u64,
pub previous_native_pc_reserved: u64,
pub market_index: Serum3MarketIndex,
/// Store the base/quote token index, so health computations don't need
/// to get passed the static SerumMarket to find which tokens a market
/// uses and look up the correct oracles.
pub base_token_index: TokenIndex,
pub quote_token_index: TokenIndex,
pub reserved: [u8; 2],
}
const_assert_eq!(size_of::<Serum3Orders>(), 32 + 8 * 2 + 2 * 3 + 2); // 56
const_assert_eq!(size_of::<Serum3Orders>() % 8, 0);
unsafe impl bytemuck::Pod for Serum3Orders {}
unsafe impl bytemuck::Zeroable for Serum3Orders {}
impl Serum3Orders {
pub fn is_active(&self) -> bool {
self.market_index != Serum3MarketIndex::MAX
}
pub fn is_active_for_market(&self, market_index: Serum3MarketIndex) -> bool {
self.market_index == market_index
}
}
impl Default for Serum3Orders {
fn default() -> Self {
Self {
open_orders: Pubkey::default(),
market_index: Serum3MarketIndex::MAX,
base_token_index: TokenIndex::MAX,
quote_token_index: TokenIndex::MAX,
reserved: Default::default(),
previous_native_coin_reserved: 0,
previous_native_pc_reserved: 0,
}
}
}
#[zero_copy]
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct PerpPositions {
pub market_index: PerpMarketIndex,
pub reserved: [u8; 6],
/// Active position size, measured in base lots
pub base_position_lots: i64,
/// Active position in quote (conversation rate is that of the time the order was settled)
/// measured in native quote
pub quote_position_native: I80F48,
/// Already settled funding
pub long_settled_funding: I80F48,
pub short_settled_funding: I80F48,
/// Base lots in bids
pub bids_base_lots: i64,
/// Base lots in asks
pub asks_base_lots: i64,
/// Liquidity mining rewards
// pub mngo_accrued: u64,
/// Amount that's on EventQueue waiting to be processed
pub taker_base_lots: i64,
pub taker_quote_lots: i64,
}
impl std::fmt::Debug for PerpPositions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PerpAccount")
.field("market_index", &self.market_index)
.field("base_position_lots", &self.base_position_lots)
.field("quote_position_native", &self.quote_position_native)
.field("bids_base_lots", &self.bids_base_lots)
.field("asks_base_lots", &self.asks_base_lots)
.field("taker_base_lots", &self.taker_base_lots)
.field("taker_quote_lots", &self.taker_quote_lots)
.finish()
}
}
const_assert_eq!(size_of::<PerpPositions>(), 8 + 8 * 5 + 3 * 16); // 96
const_assert_eq!(size_of::<PerpPositions>() % 8, 0);
unsafe impl bytemuck::Pod for PerpPositions {}
unsafe impl bytemuck::Zeroable for PerpPositions {}
impl Default for PerpPositions {
fn default() -> Self {
Self {
market_index: PerpMarketIndex::MAX,
base_position_lots: 0,
quote_position_native: I80F48::ZERO,
bids_base_lots: 0,
asks_base_lots: 0,
taker_base_lots: 0,
taker_quote_lots: 0,
reserved: Default::default(),
long_settled_funding: I80F48::ZERO,
short_settled_funding: I80F48::ZERO,
}
}
}
impl PerpPositions {
/// Add taker trade after it has been matched but before it has been process on EventQueue
pub fn add_taker_trade(&mut self, side: Side, base_lots: i64, quote_lots: i64) {
match side {
Side::Bid => {
self.taker_base_lots = cm!(self.taker_base_lots + base_lots);
self.taker_quote_lots = cm!(self.taker_quote_lots - quote_lots);
}
Side::Ask => {
self.taker_base_lots = cm!(self.taker_base_lots - base_lots);
self.taker_quote_lots = cm!(self.taker_quote_lots + quote_lots);
}
}
}
/// Remove taker trade after it has been processed on EventQueue
pub fn remove_taker_trade(&mut self, base_change: i64, quote_change: i64) {
self.taker_base_lots = cm!(self.taker_base_lots - base_change);
self.taker_quote_lots = cm!(self.taker_quote_lots - quote_change);
}
pub fn is_active(&self) -> bool {
self.market_index != PerpMarketIndex::MAX
}
pub fn is_active_for_market(&self, market_index: PerpMarketIndex) -> bool {
self.market_index == market_index
}
/// This assumes settle_funding was already called
pub fn change_base_position(&mut self, perp_market: &mut PerpMarket, base_change: i64) {
let start = self.base_position_lots;
self.base_position_lots += base_change;
perp_market.open_interest += self.base_position_lots.abs() - start.abs();
}
/// Move unrealized funding payments into the quote_position
pub fn settle_funding(&mut self, perp_market: &PerpMarket) {
match self.base_position_lots.cmp(&0) {
Ordering::Greater => {
self.quote_position_native -= (perp_market.long_funding
- self.long_settled_funding)
* I80F48::from_num(self.base_position_lots);
}
Ordering::Less => {
self.quote_position_native -= (perp_market.short_funding
- self.short_settled_funding)
* I80F48::from_num(self.base_position_lots);
}
Ordering::Equal => (),
}
self.long_settled_funding = perp_market.long_funding;
self.short_settled_funding = perp_market.short_funding;
}
}
#[zero_copy]
#[derive(AnchorSerialize, AnchorDeserialize, Debug)]
pub struct PerpOpenOrders {
pub order_side: Side, // TODO: storing enums isn't POD
pub reserved1: [u8; 1],
pub order_market: PerpMarketIndex,
pub reserved2: [u8; 4],
pub client_order_id: u64,
pub order_id: i128,
}
impl Default for PerpOpenOrders {
fn default() -> Self {
Self {
order_side: Side::Bid,
reserved1: Default::default(),
order_market: FREE_ORDER_SLOT,
reserved2: Default::default(),
client_order_id: 0,
order_id: 0,
}
}
}
unsafe impl bytemuck::Pod for PerpOpenOrders {}
unsafe impl bytemuck::Zeroable for PerpOpenOrders {}
const_assert_eq!(size_of::<PerpOpenOrders>(), 1 + 1 + 2 + 4 + 8 + 16);
const_assert_eq!(size_of::<PerpOpenOrders>() % 8, 0);
#[macro_export]
macro_rules! account_seeds {
( $account:expr ) => {
&[
$account.group.as_ref(),
b"MangoAccount".as_ref(),
$account.owner.as_ref(),
&$account.account_num.to_le_bytes(),
&[$account.bump],
]
};
}
pub use account_seeds;