1use anchor_lang::prelude::*;
2use borsh::{BorshDeserialize, BorshSerialize};
3use bytemuck::Zeroable;
4use gmsol_model::{
5 action::{
6 decrease_position::DecreasePositionReport, increase_position::IncreasePositionReport,
7 },
8 params::fee::PositionFees,
9 price::Prices,
10};
11use gmsol_utils::InitSpace;
12
13use crate::{
14 states::{order::TransferOut, position::PositionState, Position, Seed},
15 utils::pubkey::DEFAULT_PUBKEY,
16 CoreError,
17};
18
19#[allow(clippy::enum_variant_names)]
21#[derive(num_enum::IntoPrimitive)]
22#[repr(u8)]
23pub enum TradeFlag {
24 IsLong,
26 IsCollateralLong,
28 IsIncrease,
30 }
32
33gmsol_utils::flags!(TradeFlag, 8, u8);
34
35#[account(zero_copy)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
39#[derive(BorshSerialize, BorshDeserialize, InitSpace)]
40pub struct TradeData {
41 flags: u8,
45 #[cfg_attr(feature = "debug", debug(skip))]
46 padding_0: [u8; 7],
47 pub trade_id: u64,
49 #[cfg_attr(
51 feature = "serde",
52 serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
53 )]
54 pub authority: Pubkey,
55 #[cfg_attr(
57 feature = "serde",
58 serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
59 )]
60 pub store: Pubkey,
61 #[cfg_attr(
63 feature = "serde",
64 serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
65 )]
66 pub market_token: Pubkey,
67 #[cfg_attr(
69 feature = "serde",
70 serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
71 )]
72 pub user: Pubkey,
73 #[cfg_attr(
75 feature = "serde",
76 serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
77 )]
78 pub position: Pubkey,
79 #[cfg_attr(
81 feature = "serde",
82 serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
83 )]
84 pub order: Pubkey,
85 #[cfg_attr(
87 feature = "serde",
88 serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
89 )]
90 pub final_output_token: Pubkey,
91 pub ts: i64,
93 pub slot: u64,
95 pub before: PositionState,
97 pub after: PositionState,
99 pub transfer_out: TransferOut,
101 #[cfg_attr(feature = "debug", debug(skip))]
102 padding_1: [u8; 8],
103 pub prices: TradePrices,
105 pub execution_price: u128,
107 pub price_impact_value: i128,
109 pub price_impact_diff: u128,
111 pub pnl: TradePnl,
113 pub fees: TradeFees,
115 #[cfg_attr(feature = "serde", serde(default))]
117 pub output_amounts: TradeOutputAmounts,
118}
119
120impl InitSpace for TradeData {
121 const INIT_SPACE: usize = std::mem::size_of::<Self>();
122}
123
124impl Seed for TradeData {
125 const SEED: &'static [u8] = b"trade_event_data";
126}
127
128#[zero_copy]
130#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
131#[cfg_attr(feature = "debug", derive(Debug))]
132#[derive(BorshSerialize, BorshDeserialize, InitSpace)]
133pub struct TradePrice {
134 pub min: u128,
136 pub max: u128,
138}
139
140#[zero_copy]
142#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
143#[cfg_attr(feature = "debug", derive(Debug))]
144#[derive(BorshSerialize, BorshDeserialize, InitSpace)]
145pub struct TradePrices {
146 pub index: TradePrice,
148 pub long: TradePrice,
150 pub short: TradePrice,
152}
153
154impl TradePrices {
155 fn set_with_prices(&mut self, prices: &Prices<u128>) {
156 self.index.min = prices.index_token_price.min;
157 self.index.max = prices.index_token_price.max;
158 self.long.min = prices.long_token_price.min;
159 self.long.max = prices.long_token_price.max;
160 self.short.min = prices.short_token_price.min;
161 self.short.max = prices.short_token_price.max;
162 }
163}
164
165#[zero_copy]
167#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
168#[cfg_attr(feature = "debug", derive(Debug))]
169#[derive(BorshSerialize, BorshDeserialize, InitSpace)]
170pub struct TradePnl {
171 pub pnl: i128,
173 pub uncapped_pnl: i128,
175}
176
177#[zero_copy]
179#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
180#[cfg_attr(feature = "debug", derive(Debug))]
181#[derive(BorshSerialize, BorshDeserialize, InitSpace)]
182pub struct TradeFees {
183 pub order_fee_for_receiver_amount: u128,
185 pub order_fee_for_pool_amount: u128,
187 pub liquidation_fee_amount: u128,
189 pub liquidation_fee_for_receiver_amount: u128,
191 pub total_borrowing_fee_amount: u128,
193 pub borrowing_fee_for_receiver_amount: u128,
195 pub funding_fee_amount: u128,
197 pub claimable_funding_fee_long_token_amount: u128,
199 pub claimable_funding_fee_short_token_amount: u128,
201}
202
203impl TradeFees {
204 fn set_with_position_fees(&mut self, fees: &PositionFees<u128>) {
205 self.order_fee_for_receiver_amount =
206 *fees.order_fees().fee_amounts().fee_amount_for_receiver();
207 self.order_fee_for_pool_amount = *fees.order_fees().fee_amounts().fee_amount_for_pool();
208 if let Some(fees) = fees.liquidation_fees() {
209 self.liquidation_fee_amount = *fees.fee_amount();
210 self.liquidation_fee_for_receiver_amount = *fees.fee_amount_for_receiver();
211 }
212 self.total_borrowing_fee_amount = *fees.borrowing_fees().fee_amount();
213 self.borrowing_fee_for_receiver_amount = *fees.borrowing_fees().fee_amount_for_receiver();
214 self.funding_fee_amount = *fees.funding_fees().amount();
215 self.claimable_funding_fee_long_token_amount =
216 *fees.funding_fees().claimable_long_token_amount();
217 self.claimable_funding_fee_short_token_amount =
218 *fees.funding_fees().claimable_short_token_amount();
219 }
220}
221
222#[zero_copy]
224#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
225#[cfg_attr(feature = "debug", derive(Debug))]
226#[derive(BorshSerialize, BorshDeserialize, Default, InitSpace)]
227pub struct TradeOutputAmounts {
228 pub output_amount: u128,
230 pub secondary_output_amount: u128,
232}
233
234impl TradeData {
235 pub(crate) fn init(
236 &mut self,
237 is_increase: bool,
238 is_collateral_long: bool,
239 pubkey: Pubkey,
240 position: &Position,
241 order: Pubkey,
242 ) -> Result<&mut Self> {
243 let clock = Clock::get()?;
244 self.set_flags(position.try_is_long()?, is_collateral_long, is_increase);
245 self.trade_id = 0;
246 require_keys_eq!(self.store, position.store, CoreError::PermissionDenied);
247 self.market_token = position.market_token;
248 self.user = position.owner;
249 self.position = pubkey;
250 self.order = order;
251 self.final_output_token = DEFAULT_PUBKEY;
252 self.ts = clock.unix_timestamp;
253 self.slot = clock.slot;
254 self.before = position.state;
255 self.after = position.state;
256 self.transfer_out = TransferOut::zeroed();
257 self.prices = TradePrices::zeroed();
258 self.execution_price = 0;
259 self.price_impact_value = 0;
260 self.price_impact_diff = 0;
261 self.pnl = TradePnl::zeroed();
262 self.fees = TradeFees::zeroed();
263 self.output_amounts = TradeOutputAmounts::zeroed();
264 Ok(self)
265 }
266
267 fn set_flags(
268 &mut self,
269 is_long: bool,
270 is_collateral_long: bool,
271 is_increase: bool,
272 ) -> &mut Self {
273 let mut flags = TradeFlagContainer::default();
274 flags.set_flag(TradeFlag::IsLong, is_long);
275 flags.set_flag(TradeFlag::IsCollateralLong, is_collateral_long);
276 flags.set_flag(TradeFlag::IsIncrease, is_increase);
277 self.flags = flags.into_value();
278 self
279 }
280
281 fn get_flag(&self, flag: TradeFlag) -> bool {
282 let map = TradeFlagContainer::from_value(self.flags);
283 map.get_flag(flag)
284 }
285
286 pub fn is_long(&self) -> bool {
288 self.get_flag(TradeFlag::IsLong)
289 }
290
291 pub fn is_collateral_long(&self) -> bool {
293 self.get_flag(TradeFlag::IsCollateralLong)
294 }
295
296 pub fn is_increase(&self) -> bool {
298 self.get_flag(TradeFlag::IsIncrease)
299 }
300
301 fn validate(&self) -> Result<()> {
302 require_gt!(
303 self.trade_id,
304 self.before.trade_id,
305 CoreError::InvalidTradeID
306 );
307 if self.is_increase() {
308 require_gte!(
309 self.after.size_in_usd,
310 self.before.size_in_usd,
311 CoreError::InvalidTradeDeltaSize
312 );
313 require_gte!(
314 self.after.size_in_tokens,
315 self.before.size_in_tokens,
316 CoreError::InvalidTradeDeltaTokens
317 );
318 } else {
319 require_gte!(
320 self.before.size_in_usd,
321 self.after.size_in_usd,
322 CoreError::InvalidTradeDeltaSize
323 );
324 require_gte!(
325 self.before.size_in_tokens,
326 self.after.size_in_tokens,
327 CoreError::InvalidTradeDeltaTokens
328 );
329 }
330 require_gte!(
331 self.after.borrowing_factor,
332 self.before.borrowing_factor,
333 CoreError::InvalidBorrowingFactor
334 );
335 require_gte!(
336 self.after.funding_fee_amount_per_size,
337 self.before.funding_fee_amount_per_size,
338 CoreError::InvalidFundingFactors
339 );
340 require_gte!(
341 self.after.long_token_claimable_funding_amount_per_size,
342 self.before.long_token_claimable_funding_amount_per_size,
343 CoreError::InvalidFundingFactors
344 );
345 require_gte!(
346 self.after.short_token_claimable_funding_amount_per_size,
347 self.before.short_token_claimable_funding_amount_per_size,
348 CoreError::InvalidFundingFactors
349 );
350 Ok(())
351 }
352
353 pub(crate) fn update_with_state(&mut self, new_state: &PositionState) -> Result<()> {
355 self.trade_id = new_state.trade_id;
356 self.after = *new_state;
357 self.validate()?;
358 Ok(())
359 }
360
361 #[inline(never)]
363 pub(crate) fn update_with_transfer_out(&mut self, transfer_out: &TransferOut) -> Result<()> {
364 self.transfer_out = *transfer_out;
365 self.transfer_out.set_executed(true);
366 Ok(())
367 }
368
369 pub(crate) fn set_final_output_token(&mut self, token: &Pubkey) {
370 self.final_output_token = *token;
371 }
372
373 #[inline(never)]
375 pub(crate) fn update_with_increase_report(
376 &mut self,
377 report: &IncreasePositionReport<u128, i128>,
378 ) -> Result<()> {
379 self.prices.set_with_prices(report.params().prices());
380 self.execution_price = *report.execution().execution_price();
381 self.price_impact_value = *report.execution().price_impact_value();
382 self.fees.set_with_position_fees(report.fees());
383 Ok(())
384 }
385
386 pub(crate) fn update_with_decrease_report(
388 &mut self,
389 report: &DecreasePositionReport<u128, i128>,
390 prices: &Prices<u128>,
391 ) -> Result<()> {
392 self.prices.set_with_prices(prices);
393 self.execution_price = *report.execution_price();
394 self.price_impact_value = *report.price_impact_value();
395 self.price_impact_diff = *report.price_impact_diff();
396 self.pnl.pnl = *report.pnl().pnl();
397 self.pnl.uncapped_pnl = *report.pnl().uncapped_pnl();
398 self.fees.set_with_position_fees(report.fees());
399 self.output_amounts.output_amount = *report.output_amounts().output_amount();
400 self.output_amounts.secondary_output_amount =
401 *report.output_amounts().secondary_output_amount();
402 Ok(())
403 }
404}
405
406#[cfg(test)]
407mod tests {
408
409 #[test]
410 #[cfg(feature = "utils")]
411 fn test_trade_event() {
412 use crate::events::{
413 EventPositionState, EventTradeFees, EventTradeOutputAmounts, EventTradePnl,
414 EventTradePrice, EventTradePrices, EventTransferOut, TradeEvent, TradeEventRef,
415 };
416
417 use super::*;
418
419 let state = EventPositionState {
420 trade_id: u64::MAX,
421 increased_at: i64::MAX,
422 updated_at_slot: u64::MAX,
423 decreased_at: i64::MAX,
424 size_in_tokens: u128::MAX,
425 collateral_amount: u128::MAX,
426 size_in_usd: u128::MAX,
427 borrowing_factor: u128::MAX,
428 funding_fee_amount_per_size: u128::MAX,
429 long_token_claimable_funding_amount_per_size: u128::MAX,
430 short_token_claimable_funding_amount_per_size: u128::MAX,
431 reserved: [0; 128],
432 };
433
434 let transfer_out = EventTransferOut {
435 executed: u8::MAX,
436 padding_0: Default::default(),
437 final_output_token: u64::MAX,
438 secondary_output_token: u64::MAX,
439 long_token: u64::MAX,
440 short_token: u64::MAX,
441 long_token_for_claimable_account_of_user: u64::MAX,
442 short_token_for_claimable_account_of_user: u64::MAX,
443 long_token_for_claimable_account_of_holding: u64::MAX,
444 short_token_for_claimable_account_of_holding: u64::MAX,
445 };
446
447 let price = EventTradePrice {
448 min: u128::MAX,
449 max: u128::MAX,
450 };
451
452 let event = TradeEvent {
453 flags: u8::MAX,
454 padding_0: Default::default(),
455 trade_id: u64::MAX,
456 authority: Pubkey::new_unique(),
457 store: Pubkey::new_unique(),
458 market_token: Pubkey::new_unique(),
459 user: Pubkey::new_unique(),
460 position: Pubkey::new_unique(),
461 order: Pubkey::new_unique(),
462 final_output_token: Pubkey::new_unique(),
463 ts: i64::MAX,
464 slot: u64::MAX,
465 before: state.clone(),
466 after: state,
467 transfer_out,
468 padding_1: Default::default(),
469 prices: EventTradePrices {
470 index: price.clone(),
471 long: price.clone(),
472 short: price.clone(),
473 },
474 execution_price: u128::MAX,
475 price_impact_value: i128::MAX,
476 price_impact_diff: u128::MAX,
477 pnl: EventTradePnl {
478 pnl: i128::MAX,
479 uncapped_pnl: i128::MAX,
480 },
481 fees: EventTradeFees {
482 order_fee_for_receiver_amount: u128::MAX,
483 order_fee_for_pool_amount: u128::MAX,
484 liquidation_fee_amount: u128::MAX,
485 liquidation_fee_for_receiver_amount: u128::MAX,
486 total_borrowing_fee_amount: u128::MAX,
487 borrowing_fee_for_receiver_amount: u128::MAX,
488 funding_fee_amount: u128::MAX,
489 claimable_funding_fee_long_token_amount: u128::MAX,
490 claimable_funding_fee_short_token_amount: u128::MAX,
491 },
492 output_amounts: EventTradeOutputAmounts {
493 output_amount: u128::MAX,
494 secondary_output_amount: u128::MAX,
495 },
496 };
497
498 let TradeEvent {
499 flags,
500 padding_0,
501 trade_id,
502 authority,
503 store,
504 market_token,
505 user,
506 position,
507 order,
508 final_output_token,
509 ts,
510 slot,
511 before,
512 after,
513 transfer_out,
514 padding_1,
515 prices: _,
516 execution_price,
517 price_impact_value,
518 price_impact_diff,
519 pnl,
520 fees,
521 output_amounts,
522 } = event.clone();
523
524 let price = TradePrice {
525 min: price.min,
526 max: price.max,
527 };
528 let data = TradeData {
529 flags,
530 padding_0,
531 trade_id,
532 authority,
533 store,
534 market_token,
535 user,
536 position,
537 order,
538 final_output_token,
539 ts,
540 slot,
541 before: before.into(),
542 after: after.into(),
543 transfer_out: transfer_out.into(),
544 padding_1,
545 prices: TradePrices {
546 index: price,
547 long: price,
548 short: price,
549 },
550 execution_price,
551 price_impact_value,
552 price_impact_diff,
553 pnl: TradePnl {
554 pnl: pnl.pnl,
555 uncapped_pnl: pnl.uncapped_pnl,
556 },
557 fees: TradeFees {
558 order_fee_for_receiver_amount: fees.order_fee_for_receiver_amount,
559 order_fee_for_pool_amount: fees.order_fee_for_pool_amount,
560 liquidation_fee_amount: fees.liquidation_fee_amount,
561 liquidation_fee_for_receiver_amount: fees.liquidation_fee_for_receiver_amount,
562 total_borrowing_fee_amount: fees.total_borrowing_fee_amount,
563 borrowing_fee_for_receiver_amount: fees.borrowing_fee_for_receiver_amount,
564 funding_fee_amount: fees.funding_fee_amount,
565 claimable_funding_fee_long_token_amount: fees
566 .claimable_funding_fee_long_token_amount,
567 claimable_funding_fee_short_token_amount: fees
568 .claimable_funding_fee_short_token_amount,
569 },
570 output_amounts: TradeOutputAmounts {
571 output_amount: output_amounts.output_amount,
572 secondary_output_amount: output_amounts.secondary_output_amount,
573 },
574 };
575
576 let mut serialized_event = Vec::with_capacity(TradeEvent::INIT_SPACE);
577 event
578 .serialize(&mut serialized_event)
579 .expect("serializing `TradeEvent`");
580
581 let mut serialized_data = Vec::with_capacity(<TradeData as anchor_lang::Space>::INIT_SPACE);
582 TradeEventRef::from(&data)
583 .serialize(&mut serialized_data)
584 .expect("serializing `TradeEventRef`");
585
586 assert_eq!(serialized_event, serialized_data);
587 }
588}