Skip to main content

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