1use alloy::primitives::{B256, U256};
2use fastnum::{D64, D256, UD64, UD128};
3
4use super::{ContractVersion, FeeSchedule, account, order, perpetual, position};
5use crate::{
6 abi::dex::Exchange::{OrderRequest, OrderRequestV2},
7 types,
8};
9
10#[derive(Clone, derive_more::Debug)]
16pub enum StateEvents {
17 Account(AccountEvent),
19
20 Error(OrderError),
22
23 Exchange(ExchangeEvent),
25
26 Order(OrderEvent),
28
29 Perpetual(PerpetualEvent),
31
32 Position(PositionEvent),
34
35 Trade(types::Trade),
37}
38
39#[derive(Clone, derive_more::Debug)]
41pub struct AccountEvent {
42 pub account_id: types::AccountId,
44
45 pub request_id: Option<types::RequestId>,
47
48 pub r#type: AccountEventType,
50}
51
52#[derive(Clone, Copy, derive_more::Debug)]
54pub enum AccountEventType {
55 Created(types::AccountId),
57
58 Frozen(bool),
60
61 BalanceUpdated(#[debug("{_0}")] UD128),
63
64 LockedBalanceUpdated(#[debug("{_0}")] UD128),
66
67 FeeTierUpdated(types::FeeTier),
71}
72
73#[derive(Clone, derive_more::Debug)]
75pub struct OrderError {
76 pub perpetual_id: types::PerpetualId,
78
79 pub account_id: types::AccountId,
81
82 pub request_id: types::RequestId,
84
85 pub order_id: Option<types::OrderId>,
87
88 pub r#type: OrderErrorType,
90}
91
92#[derive(Clone, Copy, derive_more::Debug)]
94pub enum OrderErrorType {
95 AccountFrozen,
97
98 AmountExceedsAvailableBalance(#[debug("{_0}")] UD128, #[debug("{_1}")] UD128),
100
101 CancelExistingInvalidCloseOrders,
104
105 CantChangeCloseOrder,
107
108 ChangeExpiredOrderNeedsNewExpiry,
110
111 CloseOrderExceedsPosition,
113
114 CloseOrderPositionMismatch,
116
117 ContractNotOperational,
119
120 CrossesBook,
122
123 ExceedsLastExecutionBlock,
125
126 ImmediateOrCancelExecuted,
128
129 InsuficientFundsForRecycleFee,
131
132 InvalidExpiryBlock,
134
135 InvalidOrderId,
137
138 MakerOrderSettlementFailed,
140
141 MaxMatchesReached,
143
144 MaximumAccountOrders,
146
147 OrderDoesNotExist,
149
150 OrderExtensionRejected,
155
156 OrderPostFailed(u16),
158
159 OrderSettlementImpliesInsolvent,
161
162 OrderSizeExceedsAvailableSize,
164
165 PostOrderUnderMinimum,
167
168 PriceOutOfRange,
170
171 SizeOutOfRange,
173
174 ValueExceedsMaximum,
176
177 WrongAccountForOrder,
179}
180
181#[allow(clippy::large_enum_variant)]
185#[derive(Clone, derive_more::Debug)]
186pub enum ExchangeEvent {
187 ContractVersionUpdated(ContractVersion),
190
191 FeeScheduleUpdated(FeeSchedule),
199
200 Halted(bool),
202
203 MinPostUpdated(#[debug("{_0}")] UD128),
205
206 MinSettleUpdated(#[debug("{_0}")] UD128),
208
209 RecycleFeeUpdated(#[debug("{_0}")] UD128),
211}
212
213#[derive(Clone, derive_more::Debug)]
215pub struct OrderEvent {
216 pub perpetual_id: types::PerpetualId,
218
219 pub account_id: types::AccountId,
221
222 pub request_id: Option<types::RequestId>,
224
225 pub client_order_id: Option<types::RequestId>,
227
228 pub order_id: Option<types::OrderId>,
230
231 pub builder: Option<types::BuilderAttribution>,
237
238 pub r#type: OrderEventType,
240}
241
242#[derive(Clone, Copy, derive_more::Debug)]
244pub enum OrderEventType {
245 Filled {
249 #[debug("{fill_price}")]
250 fill_price: UD64,
251 #[debug("{fill_size}")]
252 fill_size: UD64,
253 #[debug("{fee}")]
264 fee: UD64, #[debug("{builder_fee}")]
273 builder_fee: UD64,
274 is_maker: bool,
275 },
276
277 Placed {
279 r#type: types::OrderType,
280 #[debug("{price}")]
281 price: UD64,
282 #[debug("{size}")]
283 size: UD64,
284 expiry_block: u64,
285 #[debug("{leverage}")]
286 leverage: UD64,
287 post_only: bool,
288 fill_or_kill: bool,
289 immediate_or_cancel: bool,
290 },
291
292 Removed,
294
295 Updated {
297 #[debug("{:?}", price.map(|v| format!("{v}")))]
298 price: Option<UD64>,
299 #[debug("{:?}", size.map(|v| format!("{v}")))]
300 size: Option<UD64>,
301 expiry_block: Option<u64>,
302 },
303}
304
305#[derive(Clone, derive_more::Debug)]
307pub struct PerpetualEvent {
308 pub perpetual_id: types::PerpetualId,
310
311 pub r#type: PerpetualEventType,
313}
314
315#[allow(clippy::large_enum_variant)]
318#[derive(Clone, Copy, derive_more::Debug)]
319pub enum PerpetualEventType {
320 Added,
322
323 FundingEvent {
325 #[debug("{rate}")]
326 rate: D64,
327 #[debug("{payment_per_unit}")]
328 payment_per_unit: D256,
329 },
330
331 FeeScheduleUpdated(FeeSchedule),
337
338 FundingSumScalingExpUpdated(u8),
342
343 InitialMarginFractionUpdated(#[debug("{_0}")] UD64),
345
346 LastPriceUpdated(#[debug("{_0}")] UD64),
348
349 MaintenanceMarginFractionUpdated(#[debug("{_0}")] UD64),
351
352 MarkPriceUpdated(#[debug("{_0}")] UD64),
354
355 MakerFeeUpdated(#[debug("{_0}")] UD64),
361
362 OpenInterestUpdated(#[debug("{_0}")] UD128),
364
365 OracleConfigurationUpdated { is_used: bool, feed_id: B256 },
367
368 OraclePriceUpdated(#[debug("{_0}")] UD64),
370
371 Paused(bool),
373
374 TakerFeeUpdated(#[debug("{_0}")] UD64),
378}
379
380#[derive(Clone, derive_more::Debug)]
382pub struct PositionEvent {
383 pub perpetual_id: types::PerpetualId,
385
386 pub account_id: types::AccountId,
388
389 pub request_id: Option<types::RequestId>,
392
393 pub r#type: PositionEventType,
395}
396
397#[derive(Clone, Copy, derive_more::Debug)]
399pub enum PositionEventType {
400 Closed {
402 r#type: position::PositionType,
403 #[debug("{entry_price}")]
404 entry_price: UD64,
405 #[debug("{exit_price}")]
406 exit_price: UD64,
407 #[debug("{size}")]
408 size: UD64,
409 #[debug("{delta_pnl}")]
410 delta_pnl: D256,
411 #[debug("{premium_pnl}")]
412 premium_pnl: D256,
413 },
414
415 CollateralDecreased {
417 #[debug("{prev_entry_price}")]
418 prev_entry_price: UD64,
419 #[debug("{new_entry_price}")]
420 new_entry_price: UD64,
421 #[debug("{deposit}")]
422 deposit: UD128,
423 },
424
425 Decreased {
427 #[debug("{prev_size}")]
428 prev_size: UD64,
429 #[debug("{new_size}")]
430 new_size: UD64,
431 #[debug("{deposit}")]
432 deposit: UD128,
433 #[debug("{delta_pnl}")]
434 delta_pnl: D256,
435 #[debug("{premium_pnl}")]
436 premium_pnl: D256,
437 },
438
439 Deleveraged {
441 force_close: bool,
442 r#type: position::PositionType,
443 #[debug("{entry_price}")]
444 entry_price: UD64,
445 #[debug("{exit_price}")]
446 exit_price: UD64,
447 #[debug("{prev_size}")]
448 prev_size: UD64,
449 #[debug("{new_size}")]
450 new_size: UD64,
451 #[debug("{deposit}")]
452 deposit: UD128,
453 #[debug("{delta_pnl}")]
454 delta_pnl: D256,
455 #[debug("{premium_pnl}")]
456 premium_pnl: D256,
457 },
458
459 DepositUpdated(#[debug("{_0}")] UD128),
461
462 Increased {
464 #[debug("{entry_price}")]
465 entry_price: UD64,
466 #[debug("{prev_size}")]
467 prev_size: UD64,
468 #[debug("{new_size}")]
469 new_size: UD64,
470 #[debug("{deposit}")]
471 deposit: UD128,
472 },
473
474 Inverted {
476 r#type: position::PositionType,
477 #[debug("{entry_price}")]
478 entry_price: UD64,
479 #[debug("{prev_size}")]
480 prev_size: UD64,
481 #[debug("{new_size}")]
482 new_size: UD64,
483 #[debug("{deposit}")]
484 deposit: UD128,
485 #[debug("{delta_pnl}")]
486 delta_pnl: D256,
487 #[debug("{premium_pnl}")]
488 premium_pnl: D256,
489 },
490
491 Liquidated {
493 r#type: position::PositionType,
494 #[debug("{entry_price}")]
495 entry_price: UD64,
496 #[debug("{exit_price}")]
497 exit_price: UD64,
498 #[debug("{prev_size}")]
499 prev_size: UD64,
500 #[debug("{liquidated_size}")]
501 liquidated_size: UD64,
502 #[debug("{new_size}")]
503 new_size: UD64,
504 #[debug("{deposit}")]
505 deposit: UD128,
506 #[debug("{delta_pnl}")]
507 delta_pnl: D256,
508 #[debug("{premium_pnl}")]
509 premium_pnl: D256,
510 },
511
512 MaintenanceMarginUpdated(#[debug("{_0}")] UD128),
515
516 Opened {
518 r#type: position::PositionType,
519 #[debug("{entry_price}")]
520 entry_price: UD64,
521 #[debug("{size}")]
522 size: UD64,
523 #[debug("{deposit}")]
524 deposit: UD128,
525 },
526
527 UnrealizedPnLUpdated {
529 #[debug("{pnl}")]
530 pnl: D256,
531 #[debug("{delta_pnl}")]
532 delta_pnl: D256,
533 #[debug("{premium_pnl}")]
534 premium_pnl: D256,
535 },
536
537 Unwound {
539 r#type: position::PositionType,
540 #[debug("{entry_price}")]
541 entry_price: UD64,
542 #[debug("{exit_price}")]
543 exit_price: UD64,
544 #[debug("{size}")]
545 size: UD64,
546 #[debug("{fair_market_value}")]
547 fair_market_value: D256,
548 #[debug("{payment}")]
549 payment: UD128,
550 },
551}
552
553impl StateEvents {
554 pub fn as_account_event(&self) -> Option<AccountEvent> {
555 if let StateEvents::Account(account_event) = self {
556 Some(account_event.clone())
557 } else {
558 None
559 }
560 }
561
562 pub fn as_error(&self) -> Option<OrderError> {
563 if let StateEvents::Error(error_event) = self { Some(error_event.clone()) } else { None }
564 }
565
566 pub fn as_order_event(&self) -> Option<OrderEvent> {
567 if let StateEvents::Order(order_event) = self { Some(order_event.clone()) } else { None }
568 }
569
570 pub fn as_exchange_event(&self) -> Option<ExchangeEvent> {
571 if let StateEvents::Exchange(exchange_event) = self {
572 Some(exchange_event.clone())
573 } else {
574 None
575 }
576 }
577
578 pub fn as_perpetual_event(&self) -> Option<PerpetualEvent> {
579 if let StateEvents::Perpetual(perpetual_event) = self {
580 Some(perpetual_event.clone())
581 } else {
582 None
583 }
584 }
585
586 pub fn as_position_event(&self) -> Option<PositionEvent> {
587 if let StateEvents::Position(position_event) = self {
588 Some(position_event.clone())
589 } else {
590 None
591 }
592 }
593
594 pub fn as_trade(&self) -> Option<types::Trade> {
595 if let StateEvents::Trade(trade) = self { Some(trade.clone()) } else { None }
596 }
597
598 pub(crate) fn account(
599 acc: &account::Account,
600 ctx: &Option<OrderContext>,
601 r#type: AccountEventType,
602 ) -> Self {
603 Self::Account(AccountEvent {
604 account_id: acc.id(),
605 request_id: ctx.as_ref().map(|c| c.request_id),
606 r#type,
607 })
608 }
609
610 pub(crate) fn order(
611 perp: &perpetual::Perpetual,
612 ord: &order::Order,
613 ctx: &Option<OrderContext>,
614 r#type: OrderEventType,
615 ) -> Self {
616 Self::Order(OrderEvent {
617 perpetual_id: perp.id(),
618 account_id: ord.account_id(),
619 request_id: ctx.as_ref().map(|c| c.request_id),
620 client_order_id: ord.client_order_id(),
621 order_id: Some(ord.order_id()),
622 builder: ord.builder(),
623 r#type,
624 })
625 }
626
627 pub(crate) fn order_error(ctx: &OrderContext, r#type: OrderErrorType) -> StateEvents {
628 Self::Error(OrderError {
629 perpetual_id: ctx.perpetual_id,
630 account_id: ctx.account_id,
631 request_id: ctx.request_id,
632 order_id: ctx.order_id,
633 r#type,
634 })
635 }
636
637 pub(crate) fn affected_order_error(
638 ctx: &OrderContext,
639 ord: &order::Order,
640 r#type: OrderErrorType,
641 ) -> StateEvents {
642 Self::Error(OrderError {
643 perpetual_id: ctx.perpetual_id,
644 account_id: ord.account_id(),
645 request_id: ctx.request_id,
646 order_id: Some(ord.order_id()),
647 r#type,
648 })
649 }
650
651 pub(crate) fn perpetual(
652 perp: &perpetual::Perpetual,
653 r#type: PerpetualEventType,
654 ) -> StateEvents {
655 Self::Perpetual(PerpetualEvent { perpetual_id: perp.id(), r#type })
656 }
657
658 pub(crate) fn position(
659 pos: &position::Position,
660 ctx: &Option<OrderContext>,
661 r#type: PositionEventType,
662 ) -> Self {
663 Self::Position(PositionEvent {
664 perpetual_id: pos.perpetual_id(),
665 account_id: pos.account_id(),
666 request_id: ctx.as_ref().map(|c| c.request_id),
667 r#type,
668 })
669 }
670
671 pub(crate) fn trade(ctx: &OrderContext, taker_fee: UD64, taker_builder_fee: UD64) -> Self {
672 Self::Trade(types::Trade {
673 perpetual_id: ctx.perpetual_id,
674 taker_account_id: ctx.account_id,
675 taker_request_id: ctx.request_id,
676 taker_side: ctx.r#type.try_side().expect("order type with side"),
677 taker_fee,
678 taker_builder: ctx.builder,
679 taker_builder_fee,
680 maker_fills: ctx.maker_fills.clone(),
681 })
682 }
683}
684
685#[derive(Debug)]
687pub(crate) struct OrderContext {
688 pub(crate) perpetual_id: types::PerpetualId,
689 pub(crate) account_id: types::AccountId,
690 pub(crate) request_id: types::RequestId,
691 pub(crate) order_id: Option<types::OrderId>,
692 pub(crate) r#type: types::RequestType,
693 pub(crate) price: U256,
694 pub(crate) expiry_block: u64,
695 pub(crate) leverage: U256,
696 pub(crate) post_only: bool,
697 pub(crate) fill_or_kill: bool,
698 pub(crate) immediate_or_cancel: bool,
699 pub(crate) builder: Option<types::BuilderAttribution>,
700 pub(crate) maker_fills: Vec<types::MakerFill>,
701 pub(crate) clearing_remaining_order: bool,
702 pub(crate) position_closed_at_log_index: Option<u64>,
703}
704
705fn request_order_id(order_id: U256) -> Option<types::OrderId> {
708 if order_id <= U256::from(u16::MAX) {
709 std::num::NonZeroU16::new(order_id.to::<u16>())
710 } else {
711 None
712 }
713}
714
715impl From<&OrderRequest> for OrderContext {
716 fn from(value: &OrderRequest) -> Self {
717 Self {
718 perpetual_id: value.perpId.to(),
719 account_id: value.accountId.to(),
720 request_id: value.orderDescId.to(),
721 order_id: request_order_id(value.orderId),
722 r#type: value.orderType.into(),
723 price: value.pricePNS,
724 expiry_block: value.expiryBlock.to(),
725 leverage: value.leverageHdths,
726 post_only: value.postOnly,
727 fill_or_kill: value.fillOrKill,
728 immediate_or_cancel: value.immediateOrCancel,
729 builder: None,
731 maker_fills: vec![],
732 clearing_remaining_order: false,
733 position_closed_at_log_index: None,
734 }
735 }
736}
737
738impl From<&OrderRequestV2> for OrderContext {
739 fn from(value: &OrderRequestV2) -> Self {
740 Self {
741 perpetual_id: value.perpId.to(),
742 account_id: value.accountId.to(),
743 request_id: value.orderDescId.to(),
744 order_id: request_order_id(value.orderId),
745 r#type: value.orderType.into(),
746 price: value.pricePNS,
747 expiry_block: value.expiryBlock.to(),
748 leverage: value.leverageHdths,
749 post_only: value.postOnly,
750 fill_or_kill: value.fillOrKill,
751 immediate_or_cancel: value.immediateOrCancel,
752 builder: types::BuilderAttribution::decode(&value.extension)
758 .ok()
759 .flatten(),
760 maker_fills: vec![],
761 clearing_remaining_order: false,
762 position_closed_at_log_index: None,
763 }
764 }
765}