use crate::{constants, CoreError};
use anchor_lang::prelude::*;
use borsh::{BorshDeserialize, BorshSerialize};
use num_enum::TryFromPrimitive;
use super::{Market, Seed};
pub use gmsol_utils::order::PositionKind;
#[account(zero_copy)]
#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Position {
version: u8,
pub bump: u8,
pub store: Pubkey,
pub kind: u8,
#[cfg_attr(feature = "debug", debug(skip))]
pub padding_0: [u8; 13],
pub owner: Pubkey,
pub market_token: Pubkey,
pub collateral_token: Pubkey,
pub state: PositionState,
#[cfg_attr(feature = "debug", debug(skip))]
#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
reserved: [u8; 256],
}
impl Default for Position {
fn default() -> Self {
use bytemuck::Zeroable;
Self::zeroed()
}
}
impl Space for Position {
#[allow(clippy::identity_op)]
const INIT_SPACE: usize = std::mem::size_of::<Position>();
}
impl Seed for Position {
const SEED: &'static [u8] = b"position";
}
impl Position {
#[inline]
pub fn kind_unchecked(&self) -> Result<PositionKind> {
PositionKind::try_from_primitive(self.kind)
.map_err(|_| error!(CoreError::InvalidPositionKind))
}
pub fn kind(&self) -> Result<PositionKind> {
match self.kind_unchecked()? {
PositionKind::Uninitialized => Err(CoreError::InvalidPosition.into()),
kind => Ok(kind),
}
}
pub fn try_is_long(&self) -> Result<bool> {
Ok(matches!(self.kind()?, PositionKind::Long))
}
pub fn try_init(
&mut self,
kind: PositionKind,
bump: u8,
store: Pubkey,
owner: &Pubkey,
market_token: &Pubkey,
collateral_token: &Pubkey,
) -> Result<()> {
let PositionKind::Uninitialized = self.kind_unchecked()? else {
return err!(CoreError::InvalidPosition);
};
if matches!(kind, PositionKind::Uninitialized) {
return err!(CoreError::InvalidPosition);
}
self.kind = kind as u8;
self.bump = bump;
self.store = store;
self.owner = *owner;
self.market_token = *market_token;
self.collateral_token = *collateral_token;
Ok(())
}
pub fn as_position<'a>(&'a self, market: &'a Market) -> Result<AsPosition<'a>> {
AsPosition::try_new(self, market)
}
pub(crate) fn validate_for_market(&self, market: &Market) -> gmsol_model::Result<()> {
let meta = market
.validated_meta(&self.store)
.map_err(|_| gmsol_model::Error::InvalidPosition("invalid or disabled market"))?;
if meta.market_token_mint != self.market_token {
return Err(gmsol_model::Error::InvalidPosition(
"position's market token does not match the market's",
));
}
if !meta.is_collateral_token(&self.collateral_token) {
return Err(gmsol_model::Error::InvalidPosition(
"invalid collateral token for market",
));
}
Ok(())
}
}
impl AsRef<Position> for Position {
fn as_ref(&self) -> &Position {
self
}
}
#[zero_copy]
#[derive(BorshDeserialize, BorshSerialize, InitSpace)]
#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PositionState {
pub trade_id: u64,
pub increased_at: i64,
pub updated_at_slot: u64,
pub decreased_at: i64,
pub size_in_tokens: u128,
pub collateral_amount: u128,
pub size_in_usd: u128,
pub borrowing_factor: u128,
pub funding_fee_amount_per_size: u128,
pub long_token_claimable_funding_amount_per_size: u128,
pub short_token_claimable_funding_amount_per_size: u128,
#[cfg_attr(feature = "debug", debug(skip))]
#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
reserved: [u8; 128],
}
#[cfg(feature = "utils")]
impl From<crate::events::EventPositionState> for PositionState {
fn from(event: crate::events::EventPositionState) -> Self {
let crate::events::EventPositionState {
trade_id,
increased_at,
updated_at_slot,
decreased_at,
size_in_tokens,
collateral_amount,
size_in_usd,
borrowing_factor,
funding_fee_amount_per_size,
long_token_claimable_funding_amount_per_size,
short_token_claimable_funding_amount_per_size,
reserved,
} = event;
Self {
trade_id,
increased_at,
updated_at_slot,
decreased_at,
size_in_tokens,
collateral_amount,
size_in_usd,
borrowing_factor,
funding_fee_amount_per_size,
long_token_claimable_funding_amount_per_size,
short_token_claimable_funding_amount_per_size,
reserved,
}
}
}
impl gmsol_model::PositionState<{ constants::MARKET_DECIMALS }> for PositionState {
type Num = u128;
type Signed = i128;
fn collateral_amount(&self) -> &Self::Num {
&self.collateral_amount
}
fn size_in_usd(&self) -> &Self::Num {
&self.size_in_usd
}
fn size_in_tokens(&self) -> &Self::Num {
&self.size_in_tokens
}
fn borrowing_factor(&self) -> &Self::Num {
&self.borrowing_factor
}
fn funding_fee_amount_per_size(&self) -> &Self::Num {
&self.funding_fee_amount_per_size
}
fn claimable_funding_fee_amount_per_size(&self, is_long_collateral: bool) -> &Self::Num {
if is_long_collateral {
&self.long_token_claimable_funding_amount_per_size
} else {
&self.short_token_claimable_funding_amount_per_size
}
}
}
impl gmsol_model::PositionStateMut<{ constants::MARKET_DECIMALS }> for PositionState {
fn collateral_amount_mut(&mut self) -> &mut Self::Num {
&mut self.collateral_amount
}
fn size_in_usd_mut(&mut self) -> &mut Self::Num {
&mut self.size_in_usd
}
fn size_in_tokens_mut(&mut self) -> &mut Self::Num {
&mut self.size_in_tokens
}
fn borrowing_factor_mut(&mut self) -> &mut Self::Num {
&mut self.borrowing_factor
}
fn funding_fee_amount_per_size_mut(&mut self) -> &mut Self::Num {
&mut self.funding_fee_amount_per_size
}
fn claimable_funding_fee_amount_per_size_mut(
&mut self,
is_long_collateral: bool,
) -> &mut Self::Num {
if is_long_collateral {
&mut self.long_token_claimable_funding_amount_per_size
} else {
&mut self.short_token_claimable_funding_amount_per_size
}
}
}
pub struct AsPosition<'a> {
is_long: bool,
is_collateral_long: bool,
market: &'a Market,
position: &'a Position,
}
impl<'a> AsPosition<'a> {
pub fn try_new(position: &'a Position, market: &'a Market) -> Result<Self> {
Ok(Self {
is_long: position.try_is_long()?,
is_collateral_long: market
.meta()
.to_token_side(&position.collateral_token)
.map_err(CoreError::from)?,
market,
position,
})
}
}
impl gmsol_model::PositionState<{ constants::MARKET_DECIMALS }> for AsPosition<'_> {
type Num = u128;
type Signed = i128;
fn collateral_amount(&self) -> &Self::Num {
self.position.state.collateral_amount()
}
fn size_in_usd(&self) -> &Self::Num {
self.position.state.size_in_usd()
}
fn size_in_tokens(&self) -> &Self::Num {
self.position.state.size_in_tokens()
}
fn borrowing_factor(&self) -> &Self::Num {
self.position.state.borrowing_factor()
}
fn funding_fee_amount_per_size(&self) -> &Self::Num {
self.position.state.funding_fee_amount_per_size()
}
fn claimable_funding_fee_amount_per_size(&self, is_long_collateral: bool) -> &Self::Num {
self.position
.state
.claimable_funding_fee_amount_per_size(is_long_collateral)
}
}
impl gmsol_model::Position<{ constants::MARKET_DECIMALS }> for AsPosition<'_> {
type Market = Market;
fn market(&self) -> &Self::Market {
self.market
}
fn is_long(&self) -> bool {
self.is_long
}
fn is_collateral_token_long(&self) -> bool {
self.is_collateral_long
}
fn are_pnl_and_collateral_tokens_the_same(&self) -> bool {
self.is_long == self.is_collateral_long || self.market.is_pure()
}
fn on_validate(&self) -> gmsol_model::Result<()> {
self.position.validate_for_market(self.market)
}
}