gmsol_store/states/
position.rs

1use crate::{constants, CoreError};
2use anchor_lang::prelude::*;
3use borsh::{BorshDeserialize, BorshSerialize};
4use num_enum::TryFromPrimitive;
5
6use super::{Market, Seed};
7
8pub use gmsol_utils::order::PositionKind;
9
10/// Position.
11#[account(zero_copy)]
12#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub struct Position {
15    version: u8,
16    /// Bump seed.
17    pub bump: u8,
18    /// Store.
19    pub store: Pubkey,
20    /// Position kind (the representation of [`PositionKind`]).
21    pub kind: u8,
22    /// Padding.
23    #[cfg_attr(feature = "debug", debug(skip))]
24    pub padding_0: [u8; 13],
25    /// Owner.
26    pub owner: Pubkey,
27    /// The market token of the position market.
28    pub market_token: Pubkey,
29    /// Collateral token.
30    pub collateral_token: Pubkey,
31    /// Position State.
32    pub state: PositionState,
33    /// Reserved.
34    #[cfg_attr(feature = "debug", debug(skip))]
35    #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
36    reserved: [u8; 256],
37}
38
39impl Default for Position {
40    fn default() -> Self {
41        use bytemuck::Zeroable;
42
43        Self::zeroed()
44    }
45}
46
47impl Space for Position {
48    #[allow(clippy::identity_op)]
49    const INIT_SPACE: usize = std::mem::size_of::<Position>();
50}
51
52impl Seed for Position {
53    const SEED: &'static [u8] = b"position";
54}
55
56impl Position {
57    /// Get position kind.
58    ///
59    /// Note that `Uninitialized` kind will also be returned without error.
60    #[inline]
61    pub fn kind_unchecked(&self) -> Result<PositionKind> {
62        PositionKind::try_from_primitive(self.kind)
63            .map_err(|_| error!(CoreError::InvalidPositionKind))
64    }
65
66    /// Get **initialized** position kind.
67    pub fn kind(&self) -> Result<PositionKind> {
68        match self.kind_unchecked()? {
69            PositionKind::Uninitialized => Err(CoreError::InvalidPosition.into()),
70            kind => Ok(kind),
71        }
72    }
73
74    /// Returns whether the position side is long.
75    pub fn try_is_long(&self) -> Result<bool> {
76        Ok(matches!(self.kind()?, PositionKind::Long))
77    }
78
79    /// Initialize the position state.
80    ///
81    /// Returns error if
82    /// - `kind` is `Unitialized`.
83    /// - The kind of the position is not `Uninitialized`.
84    pub fn try_init(
85        &mut self,
86        kind: PositionKind,
87        bump: u8,
88        store: Pubkey,
89        owner: &Pubkey,
90        market_token: &Pubkey,
91        collateral_token: &Pubkey,
92    ) -> Result<()> {
93        let PositionKind::Uninitialized = self.kind_unchecked()? else {
94            return err!(CoreError::InvalidPosition);
95        };
96        if matches!(kind, PositionKind::Uninitialized) {
97            return err!(CoreError::InvalidPosition);
98        }
99        self.kind = kind as u8;
100        self.bump = bump;
101        self.store = store;
102        self.owner = *owner;
103        self.market_token = *market_token;
104        self.collateral_token = *collateral_token;
105        Ok(())
106    }
107
108    /// Convert to a type that implements [`Position`](gmsol_model::Position).
109    pub fn as_position<'a>(&'a self, market: &'a Market) -> Result<AsPosition<'a>> {
110        AsPosition::try_new(self, market)
111    }
112
113    pub(crate) fn validate_for_market(&self, market: &Market) -> gmsol_model::Result<()> {
114        let meta = market
115            .validated_meta(&self.store)
116            .map_err(|_| gmsol_model::Error::InvalidPosition("invalid or disabled market"))?;
117
118        if meta.market_token_mint != self.market_token {
119            return Err(gmsol_model::Error::InvalidPosition(
120                "position's market token does not match the market's",
121            ));
122        }
123
124        if !meta.is_collateral_token(&self.collateral_token) {
125            return Err(gmsol_model::Error::InvalidPosition(
126                "invalid collateral token for market",
127            ));
128        }
129
130        Ok(())
131    }
132}
133
134impl AsRef<Position> for Position {
135    fn as_ref(&self) -> &Position {
136        self
137    }
138}
139
140/// Position State.
141#[zero_copy]
142#[derive(BorshDeserialize, BorshSerialize, InitSpace)]
143#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
144#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
145pub struct PositionState {
146    /// Trade id.
147    pub trade_id: u64,
148    /// The time that the position last increased at.
149    pub increased_at: i64,
150    /// Updated at slot.
151    pub updated_at_slot: u64,
152    /// The time that the position last decreased at.
153    pub decreased_at: i64,
154    /// Size in tokens.
155    pub size_in_tokens: u128,
156    /// Collateral amount.
157    pub collateral_amount: u128,
158    /// Size in usd.
159    pub size_in_usd: u128,
160    /// Borrowing factor.
161    pub borrowing_factor: u128,
162    /// Funding fee amount per size.
163    pub funding_fee_amount_per_size: u128,
164    /// Long token claimable funding amount per size.
165    pub long_token_claimable_funding_amount_per_size: u128,
166    /// Short token claimable funding amount per size.
167    pub short_token_claimable_funding_amount_per_size: u128,
168    /// Reserved.
169    #[cfg_attr(feature = "debug", debug(skip))]
170    #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
171    reserved: [u8; 128],
172}
173
174#[cfg(feature = "utils")]
175impl From<crate::events::EventPositionState> for PositionState {
176    fn from(event: crate::events::EventPositionState) -> Self {
177        let crate::events::EventPositionState {
178            trade_id,
179            increased_at,
180            updated_at_slot,
181            decreased_at,
182            size_in_tokens,
183            collateral_amount,
184            size_in_usd,
185            borrowing_factor,
186            funding_fee_amount_per_size,
187            long_token_claimable_funding_amount_per_size,
188            short_token_claimable_funding_amount_per_size,
189            reserved,
190        } = event;
191
192        Self {
193            trade_id,
194            increased_at,
195            updated_at_slot,
196            decreased_at,
197            size_in_tokens,
198            collateral_amount,
199            size_in_usd,
200            borrowing_factor,
201            funding_fee_amount_per_size,
202            long_token_claimable_funding_amount_per_size,
203            short_token_claimable_funding_amount_per_size,
204            reserved,
205        }
206    }
207}
208
209impl gmsol_model::PositionState<{ constants::MARKET_DECIMALS }> for PositionState {
210    type Num = u128;
211
212    type Signed = i128;
213
214    fn collateral_amount(&self) -> &Self::Num {
215        &self.collateral_amount
216    }
217
218    fn size_in_usd(&self) -> &Self::Num {
219        &self.size_in_usd
220    }
221
222    fn size_in_tokens(&self) -> &Self::Num {
223        &self.size_in_tokens
224    }
225
226    fn borrowing_factor(&self) -> &Self::Num {
227        &self.borrowing_factor
228    }
229
230    fn funding_fee_amount_per_size(&self) -> &Self::Num {
231        &self.funding_fee_amount_per_size
232    }
233
234    fn claimable_funding_fee_amount_per_size(&self, is_long_collateral: bool) -> &Self::Num {
235        if is_long_collateral {
236            &self.long_token_claimable_funding_amount_per_size
237        } else {
238            &self.short_token_claimable_funding_amount_per_size
239        }
240    }
241}
242
243impl gmsol_model::PositionStateMut<{ constants::MARKET_DECIMALS }> for PositionState {
244    fn collateral_amount_mut(&mut self) -> &mut Self::Num {
245        &mut self.collateral_amount
246    }
247
248    fn size_in_usd_mut(&mut self) -> &mut Self::Num {
249        &mut self.size_in_usd
250    }
251
252    fn size_in_tokens_mut(&mut self) -> &mut Self::Num {
253        &mut self.size_in_tokens
254    }
255
256    fn borrowing_factor_mut(&mut self) -> &mut Self::Num {
257        &mut self.borrowing_factor
258    }
259
260    fn funding_fee_amount_per_size_mut(&mut self) -> &mut Self::Num {
261        &mut self.funding_fee_amount_per_size
262    }
263
264    fn claimable_funding_fee_amount_per_size_mut(
265        &mut self,
266        is_long_collateral: bool,
267    ) -> &mut Self::Num {
268        if is_long_collateral {
269            &mut self.long_token_claimable_funding_amount_per_size
270        } else {
271            &mut self.short_token_claimable_funding_amount_per_size
272        }
273    }
274}
275
276/// A helper type that implements the [`Position`](gmsol_model::Position) trait.
277pub struct AsPosition<'a> {
278    is_long: bool,
279    is_collateral_long: bool,
280    market: &'a Market,
281    position: &'a Position,
282}
283
284impl<'a> AsPosition<'a> {
285    /// Create from the position and market.
286    pub fn try_new(position: &'a Position, market: &'a Market) -> Result<Self> {
287        Ok(Self {
288            is_long: position.try_is_long()?,
289            is_collateral_long: market
290                .meta()
291                .to_token_side(&position.collateral_token)
292                .map_err(CoreError::from)?,
293            market,
294            position,
295        })
296    }
297}
298
299impl gmsol_model::PositionState<{ constants::MARKET_DECIMALS }> for AsPosition<'_> {
300    type Num = u128;
301
302    type Signed = i128;
303
304    fn collateral_amount(&self) -> &Self::Num {
305        self.position.state.collateral_amount()
306    }
307
308    fn size_in_usd(&self) -> &Self::Num {
309        self.position.state.size_in_usd()
310    }
311
312    fn size_in_tokens(&self) -> &Self::Num {
313        self.position.state.size_in_tokens()
314    }
315
316    fn borrowing_factor(&self) -> &Self::Num {
317        self.position.state.borrowing_factor()
318    }
319
320    fn funding_fee_amount_per_size(&self) -> &Self::Num {
321        self.position.state.funding_fee_amount_per_size()
322    }
323
324    fn claimable_funding_fee_amount_per_size(&self, is_long_collateral: bool) -> &Self::Num {
325        self.position
326            .state
327            .claimable_funding_fee_amount_per_size(is_long_collateral)
328    }
329}
330
331impl gmsol_model::Position<{ constants::MARKET_DECIMALS }> for AsPosition<'_> {
332    type Market = Market;
333
334    fn market(&self) -> &Self::Market {
335        self.market
336    }
337
338    fn is_long(&self) -> bool {
339        self.is_long
340    }
341
342    fn is_collateral_token_long(&self) -> bool {
343        self.is_collateral_long
344    }
345
346    fn are_pnl_and_collateral_tokens_the_same(&self) -> bool {
347        self.is_long == self.is_collateral_long || self.market.is_pure()
348    }
349
350    fn on_validate(&self) -> gmsol_model::Result<()> {
351        self.position.validate_for_market(self.market)
352    }
353}