gmsol_store/states/market/
mod.rs

1//! ## Market
2//! A [`Market`](crate::states::Market) in GMX-Solana is defined by three tokens:
3//!
4//! - **Index Token**: The underlying asset that serves as the trading instrument. The price movements
5//!   of this token determine the profit or loss of positions. It does not need to be a real token.
6//! - **Long Token**: The token used to:
7//!   - Collateralize long and short positions
8//!   - Pay profits to long position holders
9//! - **Short Token**: The token used to:
10//!   - Collateralize long and short positions
11//!   - Pay profits to short position holders
12//!
13//! Long token and short token can be the same token, in which case the market
14//! is called a *single-token market*.
15//!
16//! Liquidity Providers (LPs) can provide liquidity to the market by depositing pool tokens (long token
17//! and short token) in exchange for market tokens. These market tokens represent the LP's proportional
18//! share of the market's liquidity pool. The deposited tokens are held in shared token accounts called
19//! *Market Vaults*, with deposited amounts for this market tracked in the market state. LPs can later
20//! redeem their market tokens back for the underlying collateral tokens through withdrawal instructions.
21//!
22//! Traders can open long or short positions using either token as collateral. When opening a position,
23//! the trader deposits collateral tokens and specifies the desired leverage. The position's profit or
24//! loss is determined by price movements of the index token. The loss is incurred in the collateral
25//! token used to open the position while the profit is realized in the long token and short token
26//! respectively.
27
28use std::str::FromStr;
29
30use anchor_lang::{prelude::*, Bump};
31use anchor_spl::token::Mint;
32use borsh::{BorshDeserialize, BorshSerialize};
33use config::MarketConfigFlag;
34use gmsol_model::{price::Prices, ClockKind, PoolKind};
35use gmsol_utils::market::MarketError;
36use revertible::RevertibleBuffer;
37
38use crate::{
39    utils::fixed_str::{bytes_to_fixed_str, fixed_str_to_bytes},
40    CoreError,
41};
42
43use super::{Factor, InitSpace, Oracle, Seed};
44
45use self::{
46    config::{MarketConfig, MarketConfigBuffer, MarketConfigKey},
47    pool::{Pool, Pools},
48};
49
50pub use gmsol_utils::market::{HasMarketMeta, MarketMeta};
51pub use model::AsLiquidityMarket;
52
53/// Market Utils.
54pub mod utils;
55
56/// Clock ops.
57pub mod clock;
58
59/// Market Config.
60pub mod config;
61
62/// Revertible Market Operations.
63pub mod revertible;
64
65/// Pool.
66pub mod pool;
67
68/// Market Status.
69pub mod status;
70
71mod model;
72
73/// Max number of flags.
74pub const MAX_FLAGS: usize = 8;
75
76const MAX_NAME_LEN: usize = 64;
77
78/// Market.
79#[account(zero_copy)]
80#[cfg_attr(feature = "debug", derive(Debug))]
81#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
82pub struct Market {
83    version: u8,
84    /// Bump Seed.
85    pub(crate) bump: u8,
86    flags: MarketFlagContainer,
87    padding: [u8; 13],
88    #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
89    name: [u8; MAX_NAME_LEN],
90    pub(crate) meta: MarketMeta,
91    /// Store.
92    pub store: Pubkey,
93    config: MarketConfig,
94    indexer: Indexer,
95    state: State,
96    buffer: RevertibleBuffer,
97    #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
98    reserved: [u8; 256],
99}
100
101#[zero_copy]
102#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
103#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
104struct State {
105    pools: Pools,
106    clocks: Clocks,
107    other: OtherState,
108    #[cfg_attr(feature = "debug", debug(skip))]
109    #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
110    reserved: [u8; 1024],
111}
112
113impl Bump for Market {
114    fn seed(&self) -> u8 {
115        self.bump
116    }
117}
118
119impl Seed for Market {
120    const SEED: &'static [u8] = b"market";
121}
122
123impl InitSpace for Market {
124    const INIT_SPACE: usize = std::mem::size_of::<Self>();
125}
126
127impl Default for Market {
128    fn default() -> Self {
129        use bytemuck::Zeroable;
130        Self::zeroed()
131    }
132}
133
134impl AsRef<Market> for Market {
135    fn as_ref(&self) -> &Market {
136        self
137    }
138}
139
140impl Market {
141    /// Find PDA for [`Market`] account.
142    pub fn find_market_address(
143        store: &Pubkey,
144        token: &Pubkey,
145        store_program_id: &Pubkey,
146    ) -> (Pubkey, u8) {
147        Pubkey::find_program_address(
148            &[Self::SEED, store.as_ref(), token.as_ref()],
149            store_program_id,
150        )
151    }
152
153    /// Initialize the market.
154    #[allow(clippy::too_many_arguments)]
155    pub fn init(
156        &mut self,
157        bump: u8,
158        store: Pubkey,
159        name: &str,
160        market_token_mint: Pubkey,
161        index_token_mint: Pubkey,
162        long_token_mint: Pubkey,
163        short_token_mint: Pubkey,
164        is_enabled: bool,
165    ) -> Result<()> {
166        self.bump = bump;
167        self.store = store;
168        self.name = fixed_str_to_bytes(name)?;
169        self.set_enabled(is_enabled);
170        self.meta.market_token_mint = market_token_mint;
171        self.meta.index_token_mint = index_token_mint;
172        self.meta.long_token_mint = long_token_mint;
173        self.meta.short_token_mint = short_token_mint;
174        let is_pure = self.meta.long_token_mint == self.meta.short_token_mint;
175        self.set_flag(MarketFlag::Pure, is_pure);
176        self.state.pools.init(is_pure);
177        self.state.clocks.init_to_current()?;
178        self.config.init();
179
180        // Initialize buffer.
181        self.buffer.init();
182        Ok(())
183    }
184
185    /// Get meta.
186    pub fn meta(&self) -> &MarketMeta {
187        &self.meta
188    }
189
190    /// Get validated meta.
191    pub fn validated_meta(&self, store: &Pubkey) -> Result<&MarketMeta> {
192        self.validate(store)?;
193        Ok(self.meta())
194    }
195
196    /// Get name.
197    pub fn name(&self) -> Result<&str> {
198        bytes_to_fixed_str(&self.name)
199    }
200
201    /// Description.
202    pub fn description(&self) -> Result<String> {
203        let name = self.name()?;
204        Ok(format!(
205            "Market {{ name = {name}, token = {}}}",
206            self.meta.market_token_mint
207        ))
208    }
209
210    /// Get flag.
211    pub fn flag(&self, flag: MarketFlag) -> bool {
212        self.flags.get_flag(flag)
213    }
214
215    /// Set flag.
216    ///
217    /// Return the previous value.
218    pub fn set_flag(&mut self, flag: MarketFlag, value: bool) -> bool {
219        self.flags.set_flag(flag, value)
220    }
221
222    /// Is this market a pure market, i.e., a single token market.
223    pub fn is_pure(&self) -> bool {
224        self.flag(MarketFlag::Pure)
225    }
226
227    /// Is this market enabled.
228    pub fn is_enabled(&self) -> bool {
229        self.flag(MarketFlag::Enabled)
230    }
231
232    /// Set enabled.
233    ///
234    /// Return previous value.
235    pub fn set_enabled(&mut self, enabled: bool) -> bool {
236        self.set_flag(MarketFlag::Enabled, enabled)
237    }
238
239    /// Is ADL enabled.
240    pub fn is_adl_enabled(&self, is_long: bool) -> bool {
241        if is_long {
242            self.flag(MarketFlag::AutoDeleveragingEnabledForLong)
243        } else {
244            self.flag(MarketFlag::AutoDeleveragingEnabledForShort)
245        }
246    }
247
248    /// Set ADL enabled.
249    ///
250    /// Return previous value.
251    pub fn set_adl_enabled(&mut self, is_long: bool, enabled: bool) -> bool {
252        if is_long {
253            self.set_flag(MarketFlag::AutoDeleveragingEnabledForLong, enabled)
254        } else {
255            self.set_flag(MarketFlag::AutoDeleveragingEnabledForShort, enabled)
256        }
257    }
258
259    /// Is GT Minting enabled.
260    pub fn is_gt_minting_enabled(&self) -> bool {
261        self.flag(MarketFlag::GTEnabled)
262    }
263
264    /// Set whether the GT minting is enabled.
265    ///
266    /// Return the previous value.
267    pub fn set_is_gt_minting_enbaled(&mut self, enabled: bool) -> bool {
268        self.set_flag(MarketFlag::GTEnabled, enabled)
269    }
270
271    /// Get pool of the given kind.
272    #[inline]
273    pub fn pool(&self, kind: PoolKind) -> Option<Pool> {
274        self.state.pools.get(kind).map(|s| s.pool()).copied()
275    }
276
277    /// Try to get pool of the given kind.
278    pub fn try_pool(&self, kind: PoolKind) -> gmsol_model::Result<&Pool> {
279        Ok(self
280            .state
281            .pools
282            .get(kind)
283            .ok_or(gmsol_model::Error::MissingPoolKind(kind))?
284            .pool())
285    }
286
287    /// Get clock of the given kind.
288    pub fn clock(&self, kind: ClockKind) -> Option<i64> {
289        self.state.clocks.get(kind).copied()
290    }
291
292    fn clocks(&self) -> &Clocks {
293        &self.state.clocks
294    }
295
296    /// Validate the market.
297    pub fn validate(&self, store: &Pubkey) -> Result<()> {
298        require_keys_eq!(*store, self.store, CoreError::StoreMismatched);
299        require!(self.is_enabled(), CoreError::DisabledMarket);
300        Ok(())
301    }
302
303    /// Get config.
304    pub fn get_config(&self, key: &str) -> Result<&Factor> {
305        let key = MarketConfigKey::from_str(key)
306            .map_err(|_| error!(CoreError::InvalidMarketConfigKey))?;
307        self.get_config_by_key(key)
308            .ok_or_else(|| error!(CoreError::Unimplemented))
309    }
310
311    /// Get config by key.
312    #[inline]
313    pub fn get_config_by_key(&self, key: MarketConfigKey) -> Option<&Factor> {
314        self.config.get(key)
315    }
316
317    /// Get config mutably.
318    pub fn get_config_mut(&mut self, key: &str) -> Result<&mut Factor> {
319        let key = MarketConfigKey::from_str(key)
320            .map_err(|_| error!(CoreError::InvalidMarketConfigKey))?;
321        self.config
322            .get_mut(key)
323            .ok_or_else(|| error!(CoreError::Unimplemented))
324    }
325
326    /// Get config flag.
327    pub fn get_config_flag(&self, key: &str) -> Result<bool> {
328        let key = MarketConfigFlag::from_str(key)
329            .map_err(|_| error!(CoreError::InvalidMarketConfigKey))?;
330        Ok(self.get_config_flag_by_key(key))
331    }
332
333    /// Get config flag by key.
334    #[inline]
335    pub fn get_config_flag_by_key(&self, key: MarketConfigFlag) -> bool {
336        self.config.flag(key)
337    }
338
339    /// Set config flag.
340    ///
341    /// Returns previous value.
342    pub fn set_config_flag(&mut self, key: &str, value: bool) -> Result<bool> {
343        let key = MarketConfigFlag::from_str(key)
344            .map_err(|_| error!(CoreError::InvalidMarketConfigKey))?;
345        Ok(self.config.set_flag(key, value))
346    }
347
348    /// Get other market state.
349    pub fn state(&self) -> &OtherState {
350        &self.state.other
351    }
352
353    /// Get market indexer.
354    pub fn indexer(&self) -> &Indexer {
355        &self.indexer
356    }
357
358    /// Get market indexer mutably.
359    pub fn indexer_mut(&mut self) -> &mut Indexer {
360        &mut self.indexer
361    }
362
363    /// Update config with buffer.
364    pub fn update_config_with_buffer(&mut self, buffer: &MarketConfigBuffer) -> Result<()> {
365        for entry in buffer.iter() {
366            let key = entry.key()?;
367            let current_value = self
368                .config
369                .get_mut(key)
370                .ok_or_else(|| error!(CoreError::Unimplemented))?;
371            let new_value = entry.value();
372            *current_value = new_value;
373        }
374        Ok(())
375    }
376
377    /// Get prices from oracle.
378    pub fn prices(&self, oracle: &Oracle) -> Result<Prices<u128>> {
379        oracle.market_prices(self)
380    }
381
382    /// Get max pool value for deposit.
383    pub fn max_pool_value_for_deposit(&self, is_long_token: bool) -> gmsol_model::Result<Factor> {
384        if is_long_token {
385            Ok(self.config.max_pool_value_for_deposit_for_long_token)
386        } else {
387            Ok(self.config.max_pool_value_for_deposit_for_short_token)
388        }
389    }
390
391    /// As a liquidity market.
392    pub fn as_liquidity_market<'a>(
393        &'a self,
394        market_token: &'a Mint,
395    ) -> AsLiquidityMarket<'a, Self> {
396        AsLiquidityMarket::new(self, market_token)
397    }
398
399    /// Validate that this market is shiftable to the target market.
400    pub fn validate_shiftable(&self, target: &Self) -> Result<()> {
401        // Currently we only support the shift between markets with
402        // with the same long tokens and short tokens.
403        //
404        // It should be possible to allow shift between markets with the compatible tokens in the future,
405        // for example, allowing shifting from BTC[WSOL-USDC] to SOL[USDC-WSOL].
406
407        require_keys_eq!(
408            self.meta().long_token_mint,
409            target.meta().long_token_mint,
410            CoreError::TokenMintMismatched,
411        );
412
413        require_keys_eq!(
414            self.meta().short_token_mint,
415            target.meta().short_token_mint,
416            CoreError::TokenMintMismatched,
417        );
418
419        Ok(())
420    }
421}
422
423/// Market Flags.
424#[derive(num_enum::IntoPrimitive)]
425#[repr(u8)]
426pub enum MarketFlag {
427    /// Is enabled.
428    Enabled,
429    /// Is Pure.
430    Pure,
431    /// Is auto-deleveraging enabled for long.
432    AutoDeleveragingEnabledForLong,
433    /// Is auto-deleveraging enabled for short.
434    AutoDeleveragingEnabledForShort,
435    /// Is GT minting enabled.
436    GTEnabled,
437    // CHECK: cannot have more than `MAX_FLAGS` flags.
438}
439
440gmsol_utils::flags!(MarketFlag, MAX_FLAGS, u8);
441
442/// Market State.
443#[zero_copy]
444#[derive(BorshSerialize, BorshDeserialize, InitSpace)]
445#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
446#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
447pub struct OtherState {
448    #[cfg_attr(feature = "debug", debug(skip))]
449    padding: [u8; 16],
450    rev: u64,
451    trade_count: u64,
452    long_token_balance: u64,
453    short_token_balance: u64,
454    funding_factor_per_second: i128,
455    #[cfg_attr(feature = "debug", debug(skip))]
456    #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
457    reserved: [u8; 256],
458}
459
460impl OtherState {
461    /// Get long token balance.
462    pub fn long_token_balance_raw(&self) -> u64 {
463        self.long_token_balance
464    }
465
466    /// Get short token balance.
467    pub fn short_token_balance_raw(&self) -> u64 {
468        self.short_token_balance
469    }
470
471    /// Get funding factor per second.
472    pub fn funding_factor_per_second(&self) -> i128 {
473        self.funding_factor_per_second
474    }
475
476    /// Get current trade count.
477    pub fn trade_count(&self) -> u64 {
478        self.trade_count
479    }
480
481    /// Next trade id.
482    pub fn next_trade_id(&mut self) -> Result<u64> {
483        let next_id = self
484            .trade_count
485            .checked_add(1)
486            .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
487        self.trade_count = next_id;
488        Ok(next_id)
489    }
490}
491
492impl HasMarketMeta for Market {
493    fn is_pure(&self) -> bool {
494        self.is_pure()
495    }
496
497    fn market_meta(&self) -> &MarketMeta {
498        &self.meta
499    }
500}
501
502/// Market clocks.
503#[zero_copy]
504#[derive(BorshSerialize, BorshDeserialize, InitSpace)]
505#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
506#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
507pub struct Clocks {
508    #[cfg_attr(feature = "debug", debug(skip))]
509    padding: [u8; 8],
510    rev: u64,
511    /// Price impact distribution clock.
512    price_impact_distribution: i64,
513    /// Borrowing clock.
514    borrowing: i64,
515    /// Funding clock.
516    funding: i64,
517    /// ADL updated clock for long.
518    adl_for_long: i64,
519    /// ADL updated clock for short.
520    adl_for_short: i64,
521    #[cfg_attr(feature = "debug", debug(skip))]
522    reserved: [i64; 3],
523}
524
525impl Clocks {
526    fn init_to_current(&mut self) -> Result<()> {
527        let current = Clock::get()?.unix_timestamp;
528        self.price_impact_distribution = current;
529        self.borrowing = current;
530        self.funding = current;
531        Ok(())
532    }
533
534    fn get(&self, kind: ClockKind) -> Option<&i64> {
535        let clock = match kind {
536            ClockKind::PriceImpactDistribution => &self.price_impact_distribution,
537            ClockKind::Borrowing => &self.borrowing,
538            ClockKind::Funding => &self.funding,
539            ClockKind::AdlForLong => &self.adl_for_long,
540            ClockKind::AdlForShort => &self.adl_for_short,
541            _ => return None,
542        };
543        Some(clock)
544    }
545
546    fn get_mut(&mut self, kind: ClockKind) -> Option<&mut i64> {
547        let clock = match kind {
548            ClockKind::PriceImpactDistribution => &mut self.price_impact_distribution,
549            ClockKind::Borrowing => &mut self.borrowing,
550            ClockKind::Funding => &mut self.funding,
551            ClockKind::AdlForLong => &mut self.adl_for_long,
552            ClockKind::AdlForShort => &mut self.adl_for_short,
553            _ => return None,
554        };
555        Some(clock)
556    }
557}
558
559/// Market indexer.
560#[zero_copy]
561#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
562#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
563pub struct Indexer {
564    trade_count: u64,
565    deposit_count: u64,
566    withdrawal_count: u64,
567    order_count: u64,
568    shift_count: u64,
569    glv_deposit_count: u64,
570    glv_withdrawal_count: u64,
571    #[cfg_attr(feature = "debug", debug(skip))]
572    padding_0: [u8; 8],
573    #[cfg_attr(feature = "debug", debug(skip))]
574    #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
575    reserved: [u8; 128],
576}
577
578impl Indexer {
579    /// Get current deposit count.
580    pub fn deposit_count(&self) -> u64 {
581        self.deposit_count
582    }
583
584    /// Get current withdrawal count.
585    pub fn withdrawal_count(&self) -> u64 {
586        self.withdrawal_count
587    }
588
589    /// Get current order count.
590    pub fn order_count(&self) -> u64 {
591        self.order_count
592    }
593
594    /// Get current shift count.
595    pub fn shift_count(&self) -> u64 {
596        self.shift_count
597    }
598
599    /// Get current GLV deposit count.
600    pub fn glv_deposit_count(&self) -> u64 {
601        self.glv_deposit_count
602    }
603
604    /// Get current GLV withdrawal count.
605    pub fn glv_withdrawal_count(&self) -> u64 {
606        self.glv_withdrawal_count
607    }
608
609    /// Next deposit id.
610    pub fn next_deposit_id(&mut self) -> Result<u64> {
611        let next_id = self
612            .deposit_count
613            .checked_add(1)
614            .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
615        self.deposit_count = next_id;
616        Ok(next_id)
617    }
618
619    /// Next withdrawal id.
620    pub fn next_withdrawal_id(&mut self) -> Result<u64> {
621        let next_id = self
622            .withdrawal_count
623            .checked_add(1)
624            .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
625        self.withdrawal_count = next_id;
626        Ok(next_id)
627    }
628
629    /// Next order id.
630    pub fn next_order_id(&mut self) -> Result<u64> {
631        let next_id = self
632            .order_count
633            .checked_add(1)
634            .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
635        self.order_count = next_id;
636        Ok(next_id)
637    }
638
639    /// Next shift id.
640    pub fn next_shift_id(&mut self) -> Result<u64> {
641        let next_id = self
642            .shift_count
643            .checked_add(1)
644            .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
645        self.shift_count = next_id;
646        Ok(next_id)
647    }
648
649    /// Next GLV deposit id.
650    pub fn next_glv_deposit_id(&mut self) -> Result<u64> {
651        let next_id = self
652            .glv_deposit_count
653            .checked_add(1)
654            .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
655        self.glv_deposit_count = next_id;
656        Ok(next_id)
657    }
658
659    /// Next GLV withdrawal id.
660    pub fn next_glv_withdrawal_id(&mut self) -> Result<u64> {
661        let next_id = self
662            .glv_withdrawal_count
663            .checked_add(1)
664            .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
665        self.glv_withdrawal_count = next_id;
666        Ok(next_id)
667    }
668}
669
670impl From<MarketError> for CoreError {
671    fn from(err: MarketError) -> Self {
672        msg!("Market Error: {}", err);
673        match err {
674            MarketError::NotACollateralToken => Self::InvalidArgument,
675        }
676    }
677}
678
679#[cfg(test)]
680mod tests {
681    use super::*;
682    use crate::events::{EventClocks, EventOtherState};
683
684    #[test]
685    fn test_event_clocks() {
686        let clocks = Clocks {
687            padding: Default::default(),
688            rev: u64::MAX,
689            price_impact_distribution: i64::MAX,
690            borrowing: i64::MAX,
691            funding: i64::MAX,
692            adl_for_long: i64::MAX,
693            adl_for_short: i64::MAX,
694            reserved: Default::default(),
695        };
696
697        let event_clocks = EventClocks {
698            padding: clocks.padding,
699            rev: clocks.rev,
700            price_impact_distribution: clocks.price_impact_distribution,
701            borrowing: clocks.borrowing,
702            funding: clocks.funding,
703            adl_for_long: clocks.adl_for_long,
704            adl_for_short: clocks.adl_for_short,
705            reserved: clocks.reserved,
706        };
707
708        let mut data = Vec::with_capacity(Pool::INIT_SPACE);
709        clocks
710            .serialize(&mut data)
711            .expect("failed to serialize `Clocks`");
712
713        let mut event_data = Vec::with_capacity(Pool::INIT_SPACE);
714        event_clocks
715            .serialize(&mut event_data)
716            .expect("failed to serialize `EventClocks`");
717
718        assert_eq!(data, event_data);
719    }
720
721    #[test]
722    fn test_event_other_state() {
723        let clocks = OtherState {
724            padding: Default::default(),
725            rev: u64::MAX,
726            trade_count: u64::MAX,
727            long_token_balance: u64::MAX,
728            short_token_balance: u64::MAX,
729            funding_factor_per_second: i128::MAX,
730            reserved: [0; 256],
731        };
732
733        let event_clocks = EventOtherState {
734            padding: clocks.padding,
735            rev: clocks.rev,
736            trade_count: clocks.trade_count,
737            long_token_balance: clocks.long_token_balance,
738            short_token_balance: clocks.short_token_balance,
739            funding_factor_per_second: clocks.funding_factor_per_second,
740            reserved: clocks.reserved,
741        };
742
743        let mut data = Vec::with_capacity(Pool::INIT_SPACE);
744        clocks
745            .serialize(&mut data)
746            .expect("failed to serialize `OtherState`");
747
748        let mut event_data = Vec::with_capacity(Pool::INIT_SPACE);
749        event_clocks
750            .serialize(&mut event_data)
751            .expect("failed to serialize `EventOtherState`");
752
753        assert_eq!(data, event_data);
754    }
755}