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#[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 pub bump: u8,
18 pub store: Pubkey,
20 pub kind: u8,
22 #[cfg_attr(feature = "debug", debug(skip))]
24 pub padding_0: [u8; 5],
25 pub created_at: i64,
27 pub owner: Pubkey,
29 pub market_token: Pubkey,
31 pub collateral_token: Pubkey,
33 pub state: PositionState,
35 #[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 #[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 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 pub fn try_is_long(&self) -> Result<bool> {
78 Ok(matches!(self.kind()?, PositionKind::Long))
79 }
80
81 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 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#[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 pub trade_id: u64,
156 pub increased_at: i64,
158 pub updated_at_slot: u64,
160 pub decreased_at: i64,
162 pub size_in_tokens: u128,
164 pub collateral_amount: u128,
166 pub size_in_usd: u128,
168 pub borrowing_factor: u128,
170 pub funding_fee_amount_per_size: u128,
172 pub long_token_claimable_funding_amount_per_size: u128,
174 pub short_token_claimable_funding_amount_per_size: u128,
176 #[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
284pub 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 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}