use anchor_lang::prelude::*;
use borsh::{BorshDeserialize, BorshSerialize};
use bytemuck::Zeroable;
use gmsol_model::{
action::{
decrease_position::DecreasePositionReport, increase_position::IncreasePositionReport,
},
params::fee::PositionFees,
price::Prices,
};
use gmsol_utils::{
order::{TradeFlag, TradeFlagContainer},
InitSpace,
};
use crate::{
states::{order::TransferOut, position::PositionState, Position, Seed},
utils::pubkey::DEFAULT_PUBKEY,
CoreError,
};
#[account(zero_copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
#[derive(BorshSerialize, BorshDeserialize, InitSpace)]
pub struct TradeData {
flags: u8,
#[cfg_attr(feature = "debug", debug(skip))]
padding_0: [u8; 7],
pub trade_id: u64,
#[cfg_attr(
feature = "serde",
serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
)]
pub authority: Pubkey,
#[cfg_attr(
feature = "serde",
serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
)]
pub store: Pubkey,
#[cfg_attr(
feature = "serde",
serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
)]
pub market_token: Pubkey,
#[cfg_attr(
feature = "serde",
serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
)]
pub user: Pubkey,
#[cfg_attr(
feature = "serde",
serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
)]
pub position: Pubkey,
#[cfg_attr(
feature = "serde",
serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
)]
pub order: Pubkey,
#[cfg_attr(
feature = "serde",
serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
)]
pub final_output_token: Pubkey,
pub ts: i64,
pub slot: u64,
pub before: PositionState,
pub after: PositionState,
pub transfer_out: TransferOut,
#[cfg_attr(feature = "debug", debug(skip))]
padding_1: [u8; 8],
pub prices: TradePrices,
pub execution_price: u128,
pub price_impact_value: i128,
pub price_impact_diff: u128,
pub pnl: TradePnl,
pub fees: TradeFees,
#[cfg_attr(feature = "serde", serde(default))]
pub output_amounts: TradeOutputAmounts,
}
impl InitSpace for TradeData {
const INIT_SPACE: usize = std::mem::size_of::<Self>();
}
impl Seed for TradeData {
const SEED: &'static [u8] = b"trade_event_data";
}
#[zero_copy]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(BorshSerialize, BorshDeserialize, InitSpace)]
pub struct TradePrice {
pub min: u128,
pub max: u128,
}
#[zero_copy]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(BorshSerialize, BorshDeserialize, InitSpace)]
pub struct TradePrices {
pub index: TradePrice,
pub long: TradePrice,
pub short: TradePrice,
}
impl TradePrices {
fn set_with_prices(&mut self, prices: &Prices<u128>) {
self.index.min = prices.index_token_price.min;
self.index.max = prices.index_token_price.max;
self.long.min = prices.long_token_price.min;
self.long.max = prices.long_token_price.max;
self.short.min = prices.short_token_price.min;
self.short.max = prices.short_token_price.max;
}
}
#[zero_copy]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(BorshSerialize, BorshDeserialize, InitSpace)]
pub struct TradePnl {
pub pnl: i128,
pub uncapped_pnl: i128,
}
#[zero_copy]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(BorshSerialize, BorshDeserialize, InitSpace)]
pub struct TradeFees {
pub order_fee_for_receiver_amount: u128,
pub order_fee_for_pool_amount: u128,
pub liquidation_fee_amount: u128,
pub liquidation_fee_for_receiver_amount: u128,
pub total_borrowing_fee_amount: u128,
pub borrowing_fee_for_receiver_amount: u128,
pub funding_fee_amount: u128,
pub claimable_funding_fee_long_token_amount: u128,
pub claimable_funding_fee_short_token_amount: u128,
}
impl TradeFees {
fn set_with_position_fees(&mut self, fees: &PositionFees<u128>) {
self.order_fee_for_receiver_amount =
*fees.order_fees().fee_amounts().fee_amount_for_receiver();
self.order_fee_for_pool_amount = *fees.order_fees().fee_amounts().fee_amount_for_pool();
if let Some(fees) = fees.liquidation_fees() {
self.liquidation_fee_amount = *fees.fee_amount();
self.liquidation_fee_for_receiver_amount = *fees.fee_amount_for_receiver();
}
self.total_borrowing_fee_amount = *fees.borrowing_fees().fee_amount();
self.borrowing_fee_for_receiver_amount = *fees.borrowing_fees().fee_amount_for_receiver();
self.funding_fee_amount = *fees.funding_fees().amount();
self.claimable_funding_fee_long_token_amount =
*fees.funding_fees().claimable_long_token_amount();
self.claimable_funding_fee_short_token_amount =
*fees.funding_fees().claimable_short_token_amount();
}
}
#[zero_copy]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(BorshSerialize, BorshDeserialize, Default, InitSpace)]
pub struct TradeOutputAmounts {
pub output_amount: u128,
pub secondary_output_amount: u128,
}
impl TradeData {
pub(crate) fn init(
&mut self,
is_increase: bool,
is_collateral_long: bool,
pubkey: Pubkey,
position: &Position,
order: Pubkey,
) -> Result<&mut Self> {
let clock = Clock::get()?;
self.set_flags(position.try_is_long()?, is_collateral_long, is_increase);
self.trade_id = 0;
require_keys_eq!(self.store, position.store, CoreError::PermissionDenied);
self.market_token = position.market_token;
self.user = position.owner;
self.position = pubkey;
self.order = order;
self.final_output_token = DEFAULT_PUBKEY;
self.ts = clock.unix_timestamp;
self.slot = clock.slot;
self.before = position.state;
self.after = position.state;
self.transfer_out = TransferOut::zeroed();
self.prices = TradePrices::zeroed();
self.execution_price = 0;
self.price_impact_value = 0;
self.price_impact_diff = 0;
self.pnl = TradePnl::zeroed();
self.fees = TradeFees::zeroed();
self.output_amounts = TradeOutputAmounts::zeroed();
Ok(self)
}
fn set_flags(
&mut self,
is_long: bool,
is_collateral_long: bool,
is_increase: bool,
) -> &mut Self {
let mut flags = TradeFlagContainer::default();
flags.set_flag(TradeFlag::IsLong, is_long);
flags.set_flag(TradeFlag::IsCollateralLong, is_collateral_long);
flags.set_flag(TradeFlag::IsIncrease, is_increase);
self.flags = flags.into_value();
self
}
fn get_flag(&self, flag: TradeFlag) -> bool {
let map = TradeFlagContainer::from_value(self.flags);
map.get_flag(flag)
}
pub fn is_long(&self) -> bool {
self.get_flag(TradeFlag::IsLong)
}
pub fn is_collateral_long(&self) -> bool {
self.get_flag(TradeFlag::IsCollateralLong)
}
pub fn is_increase(&self) -> bool {
self.get_flag(TradeFlag::IsIncrease)
}
fn validate(&self) -> Result<()> {
require_gt!(
self.trade_id,
self.before.trade_id,
CoreError::InvalidTradeID
);
if self.is_increase() {
require_gte!(
self.after.size_in_usd,
self.before.size_in_usd,
CoreError::InvalidTradeDeltaSize
);
require_gte!(
self.after.size_in_tokens,
self.before.size_in_tokens,
CoreError::InvalidTradeDeltaTokens
);
} else {
require_gte!(
self.before.size_in_usd,
self.after.size_in_usd,
CoreError::InvalidTradeDeltaSize
);
require_gte!(
self.before.size_in_tokens,
self.after.size_in_tokens,
CoreError::InvalidTradeDeltaTokens
);
}
require_gte!(
self.after.borrowing_factor,
self.before.borrowing_factor,
CoreError::InvalidBorrowingFactor
);
require_gte!(
self.after.funding_fee_amount_per_size,
self.before.funding_fee_amount_per_size,
CoreError::InvalidFundingFactors
);
require_gte!(
self.after.long_token_claimable_funding_amount_per_size,
self.before.long_token_claimable_funding_amount_per_size,
CoreError::InvalidFundingFactors
);
require_gte!(
self.after.short_token_claimable_funding_amount_per_size,
self.before.short_token_claimable_funding_amount_per_size,
CoreError::InvalidFundingFactors
);
Ok(())
}
pub(crate) fn update_with_state(&mut self, new_state: &PositionState) -> Result<()> {
self.trade_id = new_state.trade_id;
self.after = *new_state;
self.validate()?;
Ok(())
}
#[inline(never)]
pub(crate) fn update_with_transfer_out(&mut self, transfer_out: &TransferOut) -> Result<()> {
self.transfer_out = *transfer_out;
self.transfer_out.set_executed(true);
Ok(())
}
pub(crate) fn set_final_output_token(&mut self, token: &Pubkey) {
self.final_output_token = *token;
}
#[inline(never)]
pub(crate) fn update_with_increase_report(
&mut self,
report: &IncreasePositionReport<u128, i128>,
) -> Result<()> {
self.prices.set_with_prices(report.params().prices());
self.execution_price = *report.execution().execution_price();
self.price_impact_value = *report.execution().price_impact_value();
self.fees.set_with_position_fees(report.fees());
Ok(())
}
pub(crate) fn update_with_decrease_report(
&mut self,
report: &DecreasePositionReport<u128, i128>,
prices: &Prices<u128>,
) -> Result<()> {
self.prices.set_with_prices(prices);
self.execution_price = *report.execution_price();
self.price_impact_value = *report.price_impact_value();
self.price_impact_diff = *report.price_impact_diff();
self.pnl.pnl = *report.pnl().pnl();
self.pnl.uncapped_pnl = *report.pnl().uncapped_pnl();
self.fees.set_with_position_fees(report.fees());
self.output_amounts.output_amount = *report.output_amounts().output_amount();
self.output_amounts.secondary_output_amount =
*report.output_amounts().secondary_output_amount();
Ok(())
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(feature = "utils")]
fn test_trade_event() {
use crate::events::{
EventPositionState, EventTradeFees, EventTradeOutputAmounts, EventTradePnl,
EventTradePrice, EventTradePrices, EventTransferOut, TradeEvent, TradeEventRef,
};
use super::*;
let state = EventPositionState {
trade_id: u64::MAX,
increased_at: i64::MAX,
updated_at_slot: u64::MAX,
decreased_at: i64::MAX,
size_in_tokens: u128::MAX,
collateral_amount: u128::MAX,
size_in_usd: u128::MAX,
borrowing_factor: u128::MAX,
funding_fee_amount_per_size: u128::MAX,
long_token_claimable_funding_amount_per_size: u128::MAX,
short_token_claimable_funding_amount_per_size: u128::MAX,
reserved: [0; 128],
};
let transfer_out = EventTransferOut {
executed: u8::MAX,
padding_0: Default::default(),
final_output_token: u64::MAX,
secondary_output_token: u64::MAX,
long_token: u64::MAX,
short_token: u64::MAX,
long_token_for_claimable_account_of_user: u64::MAX,
short_token_for_claimable_account_of_user: u64::MAX,
long_token_for_claimable_account_of_holding: u64::MAX,
short_token_for_claimable_account_of_holding: u64::MAX,
};
let price = EventTradePrice {
min: u128::MAX,
max: u128::MAX,
};
let event = TradeEvent {
flags: u8::MAX,
padding_0: Default::default(),
trade_id: u64::MAX,
authority: Pubkey::new_unique(),
store: Pubkey::new_unique(),
market_token: Pubkey::new_unique(),
user: Pubkey::new_unique(),
position: Pubkey::new_unique(),
order: Pubkey::new_unique(),
final_output_token: Pubkey::new_unique(),
ts: i64::MAX,
slot: u64::MAX,
before: state.clone(),
after: state,
transfer_out,
padding_1: Default::default(),
prices: EventTradePrices {
index: price.clone(),
long: price.clone(),
short: price.clone(),
},
execution_price: u128::MAX,
price_impact_value: i128::MAX,
price_impact_diff: u128::MAX,
pnl: EventTradePnl {
pnl: i128::MAX,
uncapped_pnl: i128::MAX,
},
fees: EventTradeFees {
order_fee_for_receiver_amount: u128::MAX,
order_fee_for_pool_amount: u128::MAX,
liquidation_fee_amount: u128::MAX,
liquidation_fee_for_receiver_amount: u128::MAX,
total_borrowing_fee_amount: u128::MAX,
borrowing_fee_for_receiver_amount: u128::MAX,
funding_fee_amount: u128::MAX,
claimable_funding_fee_long_token_amount: u128::MAX,
claimable_funding_fee_short_token_amount: u128::MAX,
},
output_amounts: EventTradeOutputAmounts {
output_amount: u128::MAX,
secondary_output_amount: u128::MAX,
},
};
let TradeEvent {
flags,
padding_0,
trade_id,
authority,
store,
market_token,
user,
position,
order,
final_output_token,
ts,
slot,
before,
after,
transfer_out,
padding_1,
prices: _,
execution_price,
price_impact_value,
price_impact_diff,
pnl,
fees,
output_amounts,
} = event.clone();
let price = TradePrice {
min: price.min,
max: price.max,
};
let data = TradeData {
flags,
padding_0,
trade_id,
authority,
store,
market_token,
user,
position,
order,
final_output_token,
ts,
slot,
before: before.into(),
after: after.into(),
transfer_out: transfer_out.into(),
padding_1,
prices: TradePrices {
index: price,
long: price,
short: price,
},
execution_price,
price_impact_value,
price_impact_diff,
pnl: TradePnl {
pnl: pnl.pnl,
uncapped_pnl: pnl.uncapped_pnl,
},
fees: TradeFees {
order_fee_for_receiver_amount: fees.order_fee_for_receiver_amount,
order_fee_for_pool_amount: fees.order_fee_for_pool_amount,
liquidation_fee_amount: fees.liquidation_fee_amount,
liquidation_fee_for_receiver_amount: fees.liquidation_fee_for_receiver_amount,
total_borrowing_fee_amount: fees.total_borrowing_fee_amount,
borrowing_fee_for_receiver_amount: fees.borrowing_fee_for_receiver_amount,
funding_fee_amount: fees.funding_fee_amount,
claimable_funding_fee_long_token_amount: fees
.claimable_funding_fee_long_token_amount,
claimable_funding_fee_short_token_amount: fees
.claimable_funding_fee_short_token_amount,
},
output_amounts: TradeOutputAmounts {
output_amount: output_amounts.output_amount,
secondary_output_amount: output_amounts.secondary_output_amount,
},
};
let mut serialized_event = Vec::with_capacity(TradeEvent::INIT_SPACE);
event
.serialize(&mut serialized_event)
.expect("serializing `TradeEvent`");
let mut serialized_data = Vec::with_capacity(<TradeData as anchor_lang::Space>::INIT_SPACE);
TradeEventRef::from(&data)
.serialize(&mut serialized_data)
.expect("serializing `TradeEventRef`");
assert_eq!(serialized_event, serialized_data);
}
}