gmsol_store/events/trade/
data.rs

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/// Trade Data Flags.
20#[allow(clippy::enum_variant_names)]
21#[derive(num_enum::IntoPrimitive)]
22#[repr(u8)]
23pub enum TradeFlag {
24    /// Is long.
25    IsLong,
26    /// Is collateral long.
27    IsCollateralLong,
28    /// Is increase.
29    IsIncrease,
30    // CHECK: cannot have more than `8` flags.
31}
32
33gmsol_utils::flags!(TradeFlag, 8, u8);
34
35/// Trade event data.
36#[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    /// Trade flag.
42    // Note: The concrete type can be replaced with the type alias `TradeFlag`.
43    // However, this will cause the IDL build to fail in `anchor v0.30.1`.
44    flags: u8,
45    #[cfg_attr(feature = "debug", debug(skip))]
46    padding_0: [u8; 7],
47    /// Trade id.
48    pub trade_id: u64,
49    /// Authority.
50    #[cfg_attr(
51        feature = "serde",
52        serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
53    )]
54    pub authority: Pubkey,
55    /// Store address.
56    #[cfg_attr(
57        feature = "serde",
58        serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
59    )]
60    pub store: Pubkey,
61    /// Market token.
62    #[cfg_attr(
63        feature = "serde",
64        serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
65    )]
66    pub market_token: Pubkey,
67    /// User.
68    #[cfg_attr(
69        feature = "serde",
70        serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
71    )]
72    pub user: Pubkey,
73    /// Position address.
74    #[cfg_attr(
75        feature = "serde",
76        serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
77    )]
78    pub position: Pubkey,
79    /// Order address.
80    #[cfg_attr(
81        feature = "serde",
82        serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
83    )]
84    pub order: Pubkey,
85    /// Final output token.
86    #[cfg_attr(
87        feature = "serde",
88        serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
89    )]
90    pub final_output_token: Pubkey,
91    /// Trade ts.
92    pub ts: i64,
93    /// Trade slot.
94    pub slot: u64,
95    /// Before state.
96    pub before: PositionState,
97    /// After state.
98    pub after: PositionState,
99    /// Transfer out.
100    pub transfer_out: TransferOut,
101    #[cfg_attr(feature = "debug", debug(skip))]
102    padding_1: [u8; 8],
103    /// Prices.
104    pub prices: TradePrices,
105    /// Execution price.
106    pub execution_price: u128,
107    /// Price impact value.
108    pub price_impact_value: i128,
109    /// Price impact diff.
110    pub price_impact_diff: u128,
111    /// Processed pnl.
112    pub pnl: TradePnl,
113    /// Fees.
114    pub fees: TradeFees,
115    /// Output amounts.
116    #[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/// Price.
129#[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    /// Min price.
135    pub min: u128,
136    /// Max price.
137    pub max: u128,
138}
139
140/// Prices.
141#[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    /// Index token price.
147    pub index: TradePrice,
148    /// Long token price.
149    pub long: TradePrice,
150    /// Short token price.
151    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/// Trade PnL.
166#[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    /// Final PnL value.
172    pub pnl: i128,
173    /// Uncapped PnL value.
174    pub uncapped_pnl: i128,
175}
176
177/// Trade Fees.
178#[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    /// Order fee for receiver amount.
184    pub order_fee_for_receiver_amount: u128,
185    /// Order fee for pool amount.
186    pub order_fee_for_pool_amount: u128,
187    /// Total liquidation fee amount.
188    pub liquidation_fee_amount: u128,
189    /// Liquidation fee for pool amount.
190    pub liquidation_fee_for_receiver_amount: u128,
191    /// Total borrowing fee amount.
192    pub total_borrowing_fee_amount: u128,
193    /// Borrowing fee for receiver amount.
194    pub borrowing_fee_for_receiver_amount: u128,
195    /// Funding fee amount.
196    pub funding_fee_amount: u128,
197    /// Claimable funding fee long token amount.
198    pub claimable_funding_fee_long_token_amount: u128,
199    /// Claimable funding fee short token amount.
200    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/// Output amounts.
223#[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    /// Output amount.
229    pub output_amount: u128,
230    /// Secondary output amount.
231    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    /// Return whether the position side is long.
287    pub fn is_long(&self) -> bool {
288        self.get_flag(TradeFlag::IsLong)
289    }
290
291    /// Return whether the collateral side is long.
292    pub fn is_collateral_long(&self) -> bool {
293        self.get_flag(TradeFlag::IsCollateralLong)
294    }
295
296    /// Return whether the trade is caused by an increase order.
297    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    /// Update with new position state.
354    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    /// Update with transfer out.
362    #[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    /// Update with increase report.
374    #[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    /// Update with decrease report.
387    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}