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}")]
254 fee: UD64, #[debug("{builder_fee}")]
263 builder_fee: UD64,
264 is_maker: bool,
265 },
266
267 Placed {
269 r#type: types::OrderType,
270 #[debug("{price}")]
271 price: UD64,
272 #[debug("{size}")]
273 size: UD64,
274 expiry_block: u64,
275 #[debug("{leverage}")]
276 leverage: UD64,
277 post_only: bool,
278 fill_or_kill: bool,
279 immediate_or_cancel: bool,
280 },
281
282 Removed,
284
285 Updated {
287 #[debug("{:?}", price.map(|v| format!("{v}")))]
288 price: Option<UD64>,
289 #[debug("{:?}", size.map(|v| format!("{v}")))]
290 size: Option<UD64>,
291 expiry_block: Option<u64>,
292 },
293}
294
295#[derive(Clone, derive_more::Debug)]
297pub struct PerpetualEvent {
298 pub perpetual_id: types::PerpetualId,
300
301 pub r#type: PerpetualEventType,
303}
304
305#[allow(clippy::large_enum_variant)]
308#[derive(Clone, Copy, derive_more::Debug)]
309pub enum PerpetualEventType {
310 Added,
312
313 FundingEvent {
315 #[debug("{rate}")]
316 rate: D64,
317 #[debug("{payment_per_unit}")]
318 payment_per_unit: D256,
319 },
320
321 FeeScheduleUpdated(FeeSchedule),
327
328 FundingSumScalingExpUpdated(u8),
332
333 InitialMarginFractionUpdated(#[debug("{_0}")] UD64),
335
336 LastPriceUpdated(#[debug("{_0}")] UD64),
338
339 MaintenanceMarginFractionUpdated(#[debug("{_0}")] UD64),
341
342 MarkPriceUpdated(#[debug("{_0}")] UD64),
344
345 MakerFeeUpdated(#[debug("{_0}")] UD64),
351
352 OpenInterestUpdated(#[debug("{_0}")] UD128),
354
355 OracleConfigurationUpdated { is_used: bool, feed_id: B256 },
357
358 OraclePriceUpdated(#[debug("{_0}")] UD64),
360
361 Paused(bool),
363
364 TakerFeeUpdated(#[debug("{_0}")] UD64),
368}
369
370#[derive(Clone, derive_more::Debug)]
372pub struct PositionEvent {
373 pub perpetual_id: types::PerpetualId,
375
376 pub account_id: types::AccountId,
378
379 pub request_id: Option<types::RequestId>,
382
383 pub r#type: PositionEventType,
385}
386
387#[derive(Clone, Copy, derive_more::Debug)]
389pub enum PositionEventType {
390 Closed {
392 r#type: position::PositionType,
393 #[debug("{entry_price}")]
394 entry_price: UD64,
395 #[debug("{exit_price}")]
396 exit_price: UD64,
397 #[debug("{size}")]
398 size: UD64,
399 #[debug("{delta_pnl}")]
400 delta_pnl: D256,
401 #[debug("{premium_pnl}")]
402 premium_pnl: D256,
403 },
404
405 CollateralDecreased {
407 #[debug("{prev_entry_price}")]
408 prev_entry_price: UD64,
409 #[debug("{new_entry_price}")]
410 new_entry_price: UD64,
411 #[debug("{deposit}")]
412 deposit: UD128,
413 },
414
415 Decreased {
417 #[debug("{prev_size}")]
418 prev_size: UD64,
419 #[debug("{new_size}")]
420 new_size: UD64,
421 #[debug("{deposit}")]
422 deposit: UD128,
423 #[debug("{delta_pnl}")]
424 delta_pnl: D256,
425 #[debug("{premium_pnl}")]
426 premium_pnl: D256,
427 },
428
429 Deleveraged {
431 force_close: bool,
432 r#type: position::PositionType,
433 #[debug("{entry_price}")]
434 entry_price: UD64,
435 #[debug("{exit_price}")]
436 exit_price: UD64,
437 #[debug("{prev_size}")]
438 prev_size: UD64,
439 #[debug("{new_size}")]
440 new_size: UD64,
441 #[debug("{deposit}")]
442 deposit: UD128,
443 #[debug("{delta_pnl}")]
444 delta_pnl: D256,
445 #[debug("{premium_pnl}")]
446 premium_pnl: D256,
447 },
448
449 DepositUpdated(#[debug("{_0}")] UD128),
451
452 Increased {
454 #[debug("{entry_price}")]
455 entry_price: UD64,
456 #[debug("{prev_size}")]
457 prev_size: UD64,
458 #[debug("{new_size}")]
459 new_size: UD64,
460 #[debug("{deposit}")]
461 deposit: UD128,
462 },
463
464 Inverted {
466 r#type: position::PositionType,
467 #[debug("{entry_price}")]
468 entry_price: UD64,
469 #[debug("{prev_size}")]
470 prev_size: UD64,
471 #[debug("{new_size}")]
472 new_size: UD64,
473 #[debug("{deposit}")]
474 deposit: UD128,
475 #[debug("{delta_pnl}")]
476 delta_pnl: D256,
477 #[debug("{premium_pnl}")]
478 premium_pnl: D256,
479 },
480
481 Liquidated {
483 r#type: position::PositionType,
484 #[debug("{entry_price}")]
485 entry_price: UD64,
486 #[debug("{exit_price}")]
487 exit_price: UD64,
488 #[debug("{prev_size}")]
489 prev_size: UD64,
490 #[debug("{liquidated_size}")]
491 liquidated_size: UD64,
492 #[debug("{new_size}")]
493 new_size: UD64,
494 #[debug("{deposit}")]
495 deposit: UD128,
496 #[debug("{delta_pnl}")]
497 delta_pnl: D256,
498 #[debug("{premium_pnl}")]
499 premium_pnl: D256,
500 },
501
502 MaintenanceMarginUpdated(#[debug("{_0}")] UD128),
505
506 Opened {
508 r#type: position::PositionType,
509 #[debug("{entry_price}")]
510 entry_price: UD64,
511 #[debug("{size}")]
512 size: UD64,
513 #[debug("{deposit}")]
514 deposit: UD128,
515 },
516
517 UnrealizedPnLUpdated {
519 #[debug("{pnl}")]
520 pnl: D256,
521 #[debug("{delta_pnl}")]
522 delta_pnl: D256,
523 #[debug("{premium_pnl}")]
524 premium_pnl: D256,
525 },
526
527 Unwound {
529 r#type: position::PositionType,
530 #[debug("{entry_price}")]
531 entry_price: UD64,
532 #[debug("{exit_price}")]
533 exit_price: UD64,
534 #[debug("{size}")]
535 size: UD64,
536 #[debug("{fair_market_value}")]
537 fair_market_value: D256,
538 #[debug("{payment}")]
539 payment: UD128,
540 },
541}
542
543impl StateEvents {
544 pub fn as_account_event(&self) -> Option<AccountEvent> {
545 if let StateEvents::Account(account_event) = self {
546 Some(account_event.clone())
547 } else {
548 None
549 }
550 }
551
552 pub fn as_error(&self) -> Option<OrderError> {
553 if let StateEvents::Error(error_event) = self { Some(error_event.clone()) } else { None }
554 }
555
556 pub fn as_order_event(&self) -> Option<OrderEvent> {
557 if let StateEvents::Order(order_event) = self { Some(order_event.clone()) } else { None }
558 }
559
560 pub fn as_exchange_event(&self) -> Option<ExchangeEvent> {
561 if let StateEvents::Exchange(exchange_event) = self {
562 Some(exchange_event.clone())
563 } else {
564 None
565 }
566 }
567
568 pub fn as_perpetual_event(&self) -> Option<PerpetualEvent> {
569 if let StateEvents::Perpetual(perpetual_event) = self {
570 Some(perpetual_event.clone())
571 } else {
572 None
573 }
574 }
575
576 pub fn as_position_event(&self) -> Option<PositionEvent> {
577 if let StateEvents::Position(position_event) = self {
578 Some(position_event.clone())
579 } else {
580 None
581 }
582 }
583
584 pub fn as_trade(&self) -> Option<types::Trade> {
585 if let StateEvents::Trade(trade) = self { Some(trade.clone()) } else { None }
586 }
587
588 pub(crate) fn account(
589 acc: &account::Account,
590 ctx: &Option<OrderContext>,
591 r#type: AccountEventType,
592 ) -> Self {
593 Self::Account(AccountEvent {
594 account_id: acc.id(),
595 request_id: ctx.as_ref().map(|c| c.request_id),
596 r#type,
597 })
598 }
599
600 pub(crate) fn order(
601 perp: &perpetual::Perpetual,
602 ord: &order::Order,
603 ctx: &Option<OrderContext>,
604 r#type: OrderEventType,
605 ) -> Self {
606 Self::Order(OrderEvent {
607 perpetual_id: perp.id(),
608 account_id: ord.account_id(),
609 request_id: ctx.as_ref().map(|c| c.request_id),
610 client_order_id: ord.client_order_id(),
611 order_id: Some(ord.order_id()),
612 builder: ord.builder(),
613 r#type,
614 })
615 }
616
617 pub(crate) fn order_error(ctx: &OrderContext, r#type: OrderErrorType) -> StateEvents {
618 Self::Error(OrderError {
619 perpetual_id: ctx.perpetual_id,
620 account_id: ctx.account_id,
621 request_id: ctx.request_id,
622 order_id: ctx.order_id,
623 r#type,
624 })
625 }
626
627 pub(crate) fn affected_order_error(
628 ctx: &OrderContext,
629 ord: &order::Order,
630 r#type: OrderErrorType,
631 ) -> StateEvents {
632 Self::Error(OrderError {
633 perpetual_id: ctx.perpetual_id,
634 account_id: ord.account_id(),
635 request_id: ctx.request_id,
636 order_id: Some(ord.order_id()),
637 r#type,
638 })
639 }
640
641 pub(crate) fn perpetual(
642 perp: &perpetual::Perpetual,
643 r#type: PerpetualEventType,
644 ) -> StateEvents {
645 Self::Perpetual(PerpetualEvent { perpetual_id: perp.id(), r#type })
646 }
647
648 pub(crate) fn position(
649 pos: &position::Position,
650 ctx: &Option<OrderContext>,
651 r#type: PositionEventType,
652 ) -> Self {
653 Self::Position(PositionEvent {
654 perpetual_id: pos.perpetual_id(),
655 account_id: pos.account_id(),
656 request_id: ctx.as_ref().map(|c| c.request_id),
657 r#type,
658 })
659 }
660
661 pub(crate) fn trade(ctx: &OrderContext, taker_fee: UD64, taker_builder_fee: UD64) -> Self {
662 Self::Trade(types::Trade {
663 perpetual_id: ctx.perpetual_id,
664 taker_account_id: ctx.account_id,
665 taker_request_id: ctx.request_id,
666 taker_side: ctx.r#type.try_side().expect("order type with side"),
667 taker_fee,
668 taker_builder: ctx.builder,
669 taker_builder_fee,
670 maker_fills: ctx.maker_fills.clone(),
671 })
672 }
673}
674
675#[derive(Debug)]
677pub(crate) struct OrderContext {
678 pub(crate) perpetual_id: types::PerpetualId,
679 pub(crate) account_id: types::AccountId,
680 pub(crate) request_id: types::RequestId,
681 pub(crate) order_id: Option<types::OrderId>,
682 pub(crate) r#type: types::RequestType,
683 pub(crate) price: U256,
684 pub(crate) expiry_block: u64,
685 pub(crate) leverage: U256,
686 pub(crate) post_only: bool,
687 pub(crate) fill_or_kill: bool,
688 pub(crate) immediate_or_cancel: bool,
689 pub(crate) builder: Option<types::BuilderAttribution>,
690 pub(crate) maker_fills: Vec<types::MakerFill>,
691 pub(crate) clearing_remaining_order: bool,
692 pub(crate) position_closed_at_log_index: Option<u64>,
693}
694
695fn request_order_id(order_id: U256) -> Option<types::OrderId> {
698 if order_id <= U256::from(u16::MAX) {
699 std::num::NonZeroU16::new(order_id.to::<u16>())
700 } else {
701 None
702 }
703}
704
705impl From<&OrderRequest> for OrderContext {
706 fn from(value: &OrderRequest) -> Self {
707 Self {
708 perpetual_id: value.perpId.to(),
709 account_id: value.accountId.to(),
710 request_id: value.orderDescId.to(),
711 order_id: request_order_id(value.orderId),
712 r#type: value.orderType.into(),
713 price: value.pricePNS,
714 expiry_block: value.expiryBlock.to(),
715 leverage: value.leverageHdths,
716 post_only: value.postOnly,
717 fill_or_kill: value.fillOrKill,
718 immediate_or_cancel: value.immediateOrCancel,
719 builder: None,
721 maker_fills: vec![],
722 clearing_remaining_order: false,
723 position_closed_at_log_index: None,
724 }
725 }
726}
727
728impl From<&OrderRequestV2> for OrderContext {
729 fn from(value: &OrderRequestV2) -> Self {
730 Self {
731 perpetual_id: value.perpId.to(),
732 account_id: value.accountId.to(),
733 request_id: value.orderDescId.to(),
734 order_id: request_order_id(value.orderId),
735 r#type: value.orderType.into(),
736 price: value.pricePNS,
737 expiry_block: value.expiryBlock.to(),
738 leverage: value.leverageHdths,
739 post_only: value.postOnly,
740 fill_or_kill: value.fillOrKill,
741 immediate_or_cancel: value.immediateOrCancel,
742 builder: types::BuilderAttribution::decode(&value.extension)
748 .ok()
749 .flatten(),
750 maker_fills: vec![],
751 clearing_remaining_order: false,
752 position_closed_at_log_index: None,
753 }
754 }
755}