use alloy::primitives::{B256, U256};
use fastnum::{D64, D256, UD64, UD128};
use super::{ContractVersion, FeeSchedule, account, order, perpetual, position};
use crate::{
abi::dex::Exchange::{OrderRequest, OrderRequestV2},
types,
};
#[derive(Clone, derive_more::Debug)]
pub enum StateEvents {
Account(AccountEvent),
Error(OrderError),
Exchange(ExchangeEvent),
Order(OrderEvent),
Perpetual(PerpetualEvent),
Position(PositionEvent),
Trade(types::Trade),
}
#[derive(Clone, derive_more::Debug)]
pub struct AccountEvent {
pub account_id: types::AccountId,
pub request_id: Option<types::RequestId>,
pub r#type: AccountEventType,
}
#[derive(Clone, Copy, derive_more::Debug)]
pub enum AccountEventType {
Created(types::AccountId),
Frozen(bool),
BalanceUpdated(#[debug("{_0}")] UD128),
LockedBalanceUpdated(#[debug("{_0}")] UD128),
FeeTierUpdated(types::FeeTier),
}
#[derive(Clone, derive_more::Debug)]
pub struct OrderError {
pub perpetual_id: types::PerpetualId,
pub account_id: types::AccountId,
pub request_id: types::RequestId,
pub order_id: Option<types::OrderId>,
pub r#type: OrderErrorType,
}
#[derive(Clone, Copy, derive_more::Debug)]
pub enum OrderErrorType {
AccountFrozen,
AmountExceedsAvailableBalance(#[debug("{_0}")] UD128, #[debug("{_1}")] UD128),
CancelExistingInvalidCloseOrders,
CantChangeCloseOrder,
ChangeExpiredOrderNeedsNewExpiry,
CloseOrderExceedsPosition,
CloseOrderPositionMismatch,
ContractNotOperational,
CrossesBook,
ExceedsLastExecutionBlock,
ImmediateOrCancelExecuted,
InsuficientFundsForRecycleFee,
InvalidExpiryBlock,
InvalidOrderId,
MakerOrderSettlementFailed,
MaxMatchesReached,
MaximumAccountOrders,
OrderDoesNotExist,
OrderExtensionRejected,
OrderPostFailed(u16),
OrderSettlementImpliesInsolvent,
OrderSizeExceedsAvailableSize,
PostOrderUnderMinimum,
PriceOutOfRange,
SizeOutOfRange,
ValueExceedsMaximum,
WrongAccountForOrder,
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, derive_more::Debug)]
pub enum ExchangeEvent {
ContractVersionUpdated(ContractVersion),
FeeScheduleUpdated(FeeSchedule),
Halted(bool),
MinPostUpdated(#[debug("{_0}")] UD128),
MinSettleUpdated(#[debug("{_0}")] UD128),
RecycleFeeUpdated(#[debug("{_0}")] UD128),
}
#[derive(Clone, derive_more::Debug)]
pub struct OrderEvent {
pub perpetual_id: types::PerpetualId,
pub account_id: types::AccountId,
pub request_id: Option<types::RequestId>,
pub client_order_id: Option<types::RequestId>,
pub order_id: Option<types::OrderId>,
pub builder: Option<types::BuilderAttribution>,
pub r#type: OrderEventType,
}
#[derive(Clone, Copy, derive_more::Debug)]
pub enum OrderEventType {
Filled {
#[debug("{fill_price}")]
fill_price: UD64,
#[debug("{fill_size}")]
fill_size: UD64,
#[debug("{fee}")]
fee: UD64, #[debug("{builder_fee}")]
builder_fee: UD64,
is_maker: bool,
},
Placed {
r#type: types::OrderType,
#[debug("{price}")]
price: UD64,
#[debug("{size}")]
size: UD64,
expiry_block: u64,
#[debug("{leverage}")]
leverage: UD64,
post_only: bool,
fill_or_kill: bool,
immediate_or_cancel: bool,
},
Removed,
Updated {
#[debug("{:?}", price.map(|v| format!("{v}")))]
price: Option<UD64>,
#[debug("{:?}", size.map(|v| format!("{v}")))]
size: Option<UD64>,
expiry_block: Option<u64>,
},
}
#[derive(Clone, derive_more::Debug)]
pub struct PerpetualEvent {
pub perpetual_id: types::PerpetualId,
pub r#type: PerpetualEventType,
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Copy, derive_more::Debug)]
pub enum PerpetualEventType {
Added,
FundingEvent {
#[debug("{rate}")]
rate: D64,
#[debug("{payment_per_unit}")]
payment_per_unit: D256,
},
FeeScheduleUpdated(FeeSchedule),
FundingSumScalingExpUpdated(u8),
InitialMarginFractionUpdated(#[debug("{_0}")] UD64),
LastPriceUpdated(#[debug("{_0}")] UD64),
MaintenanceMarginFractionUpdated(#[debug("{_0}")] UD64),
MarkPriceUpdated(#[debug("{_0}")] UD64),
MakerFeeUpdated(#[debug("{_0}")] UD64),
OpenInterestUpdated(#[debug("{_0}")] UD128),
OracleConfigurationUpdated { is_used: bool, feed_id: B256 },
OraclePriceUpdated(#[debug("{_0}")] UD64),
Paused(bool),
TakerFeeUpdated(#[debug("{_0}")] UD64),
}
#[derive(Clone, derive_more::Debug)]
pub struct PositionEvent {
pub perpetual_id: types::PerpetualId,
pub account_id: types::AccountId,
pub request_id: Option<types::RequestId>,
pub r#type: PositionEventType,
}
#[derive(Clone, Copy, derive_more::Debug)]
pub enum PositionEventType {
Closed {
r#type: position::PositionType,
#[debug("{entry_price}")]
entry_price: UD64,
#[debug("{exit_price}")]
exit_price: UD64,
#[debug("{size}")]
size: UD64,
#[debug("{delta_pnl}")]
delta_pnl: D256,
#[debug("{premium_pnl}")]
premium_pnl: D256,
},
CollateralDecreased {
#[debug("{prev_entry_price}")]
prev_entry_price: UD64,
#[debug("{new_entry_price}")]
new_entry_price: UD64,
#[debug("{deposit}")]
deposit: UD128,
},
Decreased {
#[debug("{prev_size}")]
prev_size: UD64,
#[debug("{new_size}")]
new_size: UD64,
#[debug("{deposit}")]
deposit: UD128,
#[debug("{delta_pnl}")]
delta_pnl: D256,
#[debug("{premium_pnl}")]
premium_pnl: D256,
},
Deleveraged {
force_close: bool,
r#type: position::PositionType,
#[debug("{entry_price}")]
entry_price: UD64,
#[debug("{exit_price}")]
exit_price: UD64,
#[debug("{prev_size}")]
prev_size: UD64,
#[debug("{new_size}")]
new_size: UD64,
#[debug("{deposit}")]
deposit: UD128,
#[debug("{delta_pnl}")]
delta_pnl: D256,
#[debug("{premium_pnl}")]
premium_pnl: D256,
},
DepositUpdated(#[debug("{_0}")] UD128),
Increased {
#[debug("{entry_price}")]
entry_price: UD64,
#[debug("{prev_size}")]
prev_size: UD64,
#[debug("{new_size}")]
new_size: UD64,
#[debug("{deposit}")]
deposit: UD128,
},
Inverted {
r#type: position::PositionType,
#[debug("{entry_price}")]
entry_price: UD64,
#[debug("{prev_size}")]
prev_size: UD64,
#[debug("{new_size}")]
new_size: UD64,
#[debug("{deposit}")]
deposit: UD128,
#[debug("{delta_pnl}")]
delta_pnl: D256,
#[debug("{premium_pnl}")]
premium_pnl: D256,
},
Liquidated {
r#type: position::PositionType,
#[debug("{entry_price}")]
entry_price: UD64,
#[debug("{exit_price}")]
exit_price: UD64,
#[debug("{prev_size}")]
prev_size: UD64,
#[debug("{liquidated_size}")]
liquidated_size: UD64,
#[debug("{new_size}")]
new_size: UD64,
#[debug("{deposit}")]
deposit: UD128,
#[debug("{delta_pnl}")]
delta_pnl: D256,
#[debug("{premium_pnl}")]
premium_pnl: D256,
},
MaintenanceMarginUpdated(#[debug("{_0}")] UD128),
Opened {
r#type: position::PositionType,
#[debug("{entry_price}")]
entry_price: UD64,
#[debug("{size}")]
size: UD64,
#[debug("{deposit}")]
deposit: UD128,
},
UnrealizedPnLUpdated {
#[debug("{pnl}")]
pnl: D256,
#[debug("{delta_pnl}")]
delta_pnl: D256,
#[debug("{premium_pnl}")]
premium_pnl: D256,
},
Unwound {
r#type: position::PositionType,
#[debug("{entry_price}")]
entry_price: UD64,
#[debug("{exit_price}")]
exit_price: UD64,
#[debug("{size}")]
size: UD64,
#[debug("{fair_market_value}")]
fair_market_value: D256,
#[debug("{payment}")]
payment: UD128,
},
}
impl StateEvents {
pub fn as_account_event(&self) -> Option<AccountEvent> {
if let StateEvents::Account(account_event) = self {
Some(account_event.clone())
} else {
None
}
}
pub fn as_error(&self) -> Option<OrderError> {
if let StateEvents::Error(error_event) = self { Some(error_event.clone()) } else { None }
}
pub fn as_order_event(&self) -> Option<OrderEvent> {
if let StateEvents::Order(order_event) = self { Some(order_event.clone()) } else { None }
}
pub fn as_exchange_event(&self) -> Option<ExchangeEvent> {
if let StateEvents::Exchange(exchange_event) = self {
Some(exchange_event.clone())
} else {
None
}
}
pub fn as_perpetual_event(&self) -> Option<PerpetualEvent> {
if let StateEvents::Perpetual(perpetual_event) = self {
Some(perpetual_event.clone())
} else {
None
}
}
pub fn as_position_event(&self) -> Option<PositionEvent> {
if let StateEvents::Position(position_event) = self {
Some(position_event.clone())
} else {
None
}
}
pub fn as_trade(&self) -> Option<types::Trade> {
if let StateEvents::Trade(trade) = self { Some(trade.clone()) } else { None }
}
pub(crate) fn account(
acc: &account::Account,
ctx: &Option<OrderContext>,
r#type: AccountEventType,
) -> Self {
Self::Account(AccountEvent {
account_id: acc.id(),
request_id: ctx.as_ref().map(|c| c.request_id),
r#type,
})
}
pub(crate) fn order(
perp: &perpetual::Perpetual,
ord: &order::Order,
ctx: &Option<OrderContext>,
r#type: OrderEventType,
) -> Self {
Self::Order(OrderEvent {
perpetual_id: perp.id(),
account_id: ord.account_id(),
request_id: ctx.as_ref().map(|c| c.request_id),
client_order_id: ord.client_order_id(),
order_id: Some(ord.order_id()),
builder: ord.builder(),
r#type,
})
}
pub(crate) fn order_error(ctx: &OrderContext, r#type: OrderErrorType) -> StateEvents {
Self::Error(OrderError {
perpetual_id: ctx.perpetual_id,
account_id: ctx.account_id,
request_id: ctx.request_id,
order_id: ctx.order_id,
r#type,
})
}
pub(crate) fn affected_order_error(
ctx: &OrderContext,
ord: &order::Order,
r#type: OrderErrorType,
) -> StateEvents {
Self::Error(OrderError {
perpetual_id: ctx.perpetual_id,
account_id: ord.account_id(),
request_id: ctx.request_id,
order_id: Some(ord.order_id()),
r#type,
})
}
pub(crate) fn perpetual(
perp: &perpetual::Perpetual,
r#type: PerpetualEventType,
) -> StateEvents {
Self::Perpetual(PerpetualEvent { perpetual_id: perp.id(), r#type })
}
pub(crate) fn position(
pos: &position::Position,
ctx: &Option<OrderContext>,
r#type: PositionEventType,
) -> Self {
Self::Position(PositionEvent {
perpetual_id: pos.perpetual_id(),
account_id: pos.account_id(),
request_id: ctx.as_ref().map(|c| c.request_id),
r#type,
})
}
pub(crate) fn trade(ctx: &OrderContext, taker_fee: UD64, taker_builder_fee: UD64) -> Self {
Self::Trade(types::Trade {
perpetual_id: ctx.perpetual_id,
taker_account_id: ctx.account_id,
taker_request_id: ctx.request_id,
taker_side: ctx.r#type.try_side().expect("order type with side"),
taker_fee,
taker_builder: ctx.builder,
taker_builder_fee,
maker_fills: ctx.maker_fills.clone(),
})
}
}
#[derive(Debug)]
pub(crate) struct OrderContext {
pub(crate) perpetual_id: types::PerpetualId,
pub(crate) account_id: types::AccountId,
pub(crate) request_id: types::RequestId,
pub(crate) order_id: Option<types::OrderId>,
pub(crate) r#type: types::RequestType,
pub(crate) price: U256,
pub(crate) expiry_block: u64,
pub(crate) leverage: U256,
pub(crate) post_only: bool,
pub(crate) fill_or_kill: bool,
pub(crate) immediate_or_cancel: bool,
pub(crate) builder: Option<types::BuilderAttribution>,
pub(crate) maker_fills: Vec<types::MakerFill>,
pub(crate) clearing_remaining_order: bool,
pub(crate) position_closed_at_log_index: Option<u64>,
}
fn request_order_id(order_id: U256) -> Option<types::OrderId> {
if order_id <= U256::from(u16::MAX) {
std::num::NonZeroU16::new(order_id.to::<u16>())
} else {
None
}
}
impl From<&OrderRequest> for OrderContext {
fn from(value: &OrderRequest) -> Self {
Self {
perpetual_id: value.perpId.to(),
account_id: value.accountId.to(),
request_id: value.orderDescId.to(),
order_id: request_order_id(value.orderId),
r#type: value.orderType.into(),
price: value.pricePNS,
expiry_block: value.expiryBlock.to(),
leverage: value.leverageHdths,
post_only: value.postOnly,
fill_or_kill: value.fillOrKill,
immediate_or_cancel: value.immediateOrCancel,
builder: None,
maker_fills: vec![],
clearing_remaining_order: false,
position_closed_at_log_index: None,
}
}
}
impl From<&OrderRequestV2> for OrderContext {
fn from(value: &OrderRequestV2) -> Self {
Self {
perpetual_id: value.perpId.to(),
account_id: value.accountId.to(),
request_id: value.orderDescId.to(),
order_id: request_order_id(value.orderId),
r#type: value.orderType.into(),
price: value.pricePNS,
expiry_block: value.expiryBlock.to(),
leverage: value.leverageHdths,
post_only: value.postOnly,
fill_or_kill: value.fillOrKill,
immediate_or_cancel: value.immediateOrCancel,
builder: types::BuilderAttribution::decode(&value.extension)
.ok()
.flatten(),
maker_fills: vec![],
clearing_remaining_order: false,
position_closed_at_log_index: None,
}
}
}