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; 13],
25 pub owner: Pubkey,
27 pub market_token: Pubkey,
29 pub collateral_token: Pubkey,
31 pub state: PositionState,
33 #[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 #[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 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 pub fn try_is_long(&self) -> Result<bool> {
76 Ok(matches!(self.kind()?, PositionKind::Long))
77 }
78
79 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 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#[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 pub trade_id: u64,
148 pub increased_at: i64,
150 pub updated_at_slot: u64,
152 pub decreased_at: i64,
154 pub size_in_tokens: u128,
156 pub collateral_amount: u128,
158 pub size_in_usd: u128,
160 pub borrowing_factor: u128,
162 pub funding_fee_amount_per_size: u128,
164 pub long_token_claimable_funding_amount_per_size: u128,
166 pub short_token_claimable_funding_amount_per_size: u128,
168 #[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
276pub 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 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}