use num_traits::{CheckedAdd, CheckedSub, Zero};
use typed_builder::TypedBuilder;
use crate::{fixed::FixedPointOps, num::Unsigned, pool::delta::BalanceChange, price::Price, utils};
#[derive(Debug, Clone, Copy, TypedBuilder)]
pub struct FeeParams<T> {
positive_impact_fee_factor: T,
negative_impact_fee_factor: T,
fee_receiver_factor: T,
#[builder(default = None, setter(strip_option))]
discount_factor: Option<T>,
}
impl<T> FeeParams<T> {
pub fn with_discount_factor(self, factor: T) -> Self {
Self {
discount_factor: Some(factor),
..self
}
}
pub fn receiver_factor(&self) -> &T {
&self.fee_receiver_factor
}
#[inline]
fn factor(&self, balance_change: BalanceChange) -> &T {
match balance_change {
BalanceChange::Improved => &self.positive_impact_fee_factor,
BalanceChange::Unchanged | BalanceChange::Worsened => &self.negative_impact_fee_factor,
}
}
fn discount_factor(&self) -> T
where
T: Zero + Clone,
{
self.discount_factor
.as_ref()
.cloned()
.unwrap_or(Zero::zero())
}
#[inline]
pub fn fee<const DECIMALS: u8>(&self, balance_change: BalanceChange, amount: &T) -> Option<T>
where
T: FixedPointOps<DECIMALS>,
{
let factor = self.factor(balance_change);
let fee = utils::apply_factor(amount, factor)?;
let discount = utils::apply_factor(&fee, &self.discount_factor())?;
fee.checked_sub(&discount)
}
#[inline]
pub fn receiver_fee<const DECIMALS: u8>(&self, fee_amount: &T) -> Option<T>
where
T: FixedPointOps<DECIMALS>,
{
utils::apply_factor(fee_amount, &self.fee_receiver_factor)
}
pub fn apply_fees<const DECIMALS: u8>(
&self,
balance_change: BalanceChange,
amount: &T,
) -> Option<(T, Fees<T>)>
where
T: FixedPointOps<DECIMALS>,
{
let fee_amount = self.fee(balance_change, amount)?;
let fee_receiver_amount = self.receiver_fee(&fee_amount)?;
let fees = Fees {
fee_amount_for_pool: fee_amount.checked_sub(&fee_receiver_amount)?,
fee_amount_for_receiver: fee_receiver_amount,
};
Some((amount.checked_sub(&fee_amount)?, fees))
}
fn order_fees<const DECIMALS: u8>(
&self,
collateral_token_price: &Price<T>,
size_delta_usd: &T,
balance_change: BalanceChange,
) -> crate::Result<OrderFees<T>>
where
T: FixedPointOps<DECIMALS>,
{
if collateral_token_price.has_zero() {
return Err(crate::Error::InvalidPrices);
}
let fee_value = self
.fee(balance_change, size_delta_usd)
.ok_or(crate::Error::Computation("calculating order fee value"))?;
let fee_amount = fee_value
.checked_div(collateral_token_price.pick_price(false))
.ok_or(crate::Error::Computation("calculating order fee amount"))?;
let receiver_fee_amount = self
.receiver_fee(&fee_amount)
.ok_or(crate::Error::Computation("calculating order receiver fee"))?;
Ok(OrderFees {
base: Fees::new(
fee_amount
.checked_sub(&receiver_fee_amount)
.ok_or(crate::Error::Computation("calculating order fee for pool"))?,
receiver_fee_amount,
),
fee_value,
})
}
pub fn base_position_fees<const DECIMALS: u8>(
&self,
collateral_token_price: &Price<T>,
size_delta_usd: &T,
balance_change: BalanceChange,
) -> crate::Result<PositionFees<T>>
where
T: FixedPointOps<DECIMALS>,
{
let order_fees = self.order_fees(collateral_token_price, size_delta_usd, balance_change)?;
Ok(PositionFees {
paid_order_and_borrowing_fee_value: order_fees.fee_value.clone(),
order: order_fees,
borrowing: Default::default(),
funding: Default::default(),
liquidation: Default::default(),
})
}
}
#[derive(Debug, Clone, Copy, TypedBuilder)]
pub struct BorrowingFeeParams<T> {
receiver_factor: T,
exponent_for_long: T,
exponent_for_short: T,
factor_for_long: T,
factor_for_short: T,
#[builder(default = true)]
skip_borrowing_fee_for_smaller_side: bool,
}
impl<T> BorrowingFeeParams<T> {
pub fn exponent(&self, is_long: bool) -> &T {
if is_long {
&self.exponent_for_long
} else {
&self.exponent_for_short
}
}
pub fn factor(&self, is_long: bool) -> &T {
if is_long {
&self.factor_for_long
} else {
&self.factor_for_short
}
}
pub fn skip_borrowing_fee_for_smaller_side(&self) -> bool {
self.skip_borrowing_fee_for_smaller_side
}
pub fn receiver_factor(&self) -> &T {
&self.receiver_factor
}
}
#[derive(Debug, Clone, Copy, TypedBuilder)]
pub struct BorrowingFeeKinkModelParams<T> {
long: BorrowingFeeKinkModelParamsForOneSide<T>,
short: BorrowingFeeKinkModelParamsForOneSide<T>,
}
impl<T> BorrowingFeeKinkModelParams<T> {
fn params_for_one_side(&self, is_long: bool) -> &BorrowingFeeKinkModelParamsForOneSide<T> {
if is_long {
&self.long
} else {
&self.short
}
}
pub fn optimal_usage_factor(&self, is_long: bool) -> &T {
&self.params_for_one_side(is_long).optimal_usage_factor
}
pub fn base_borrowing_factor(&self, is_long: bool) -> &T {
&self.params_for_one_side(is_long).base_borrowing_factor
}
pub fn above_optimal_usage_borrowing_factor(&self, is_long: bool) -> &T {
&self
.params_for_one_side(is_long)
.above_optimal_usage_borrowing_factor
}
pub fn borrowing_factor_per_second<const DECIMALS: u8, M>(
&self,
market: &M,
is_long: bool,
reserved_value: &T,
pool_value: &T,
) -> crate::Result<Option<T>>
where
M: crate::BaseMarket<DECIMALS, Num = T> + ?Sized,
T: FixedPointOps<DECIMALS>,
{
use crate::market::utils::MarketUtils;
let optimal_usage_factor = self.optimal_usage_factor(is_long);
if optimal_usage_factor.is_zero() {
return Ok(None);
}
let usage_factor = market.usage_factor(is_long, reserved_value, pool_value)?;
let base_borrowing_factor = self.base_borrowing_factor(is_long);
let borrowing_factor_per_second = utils::apply_factor(&usage_factor, base_borrowing_factor)
.ok_or(crate::Error::Computation(
"borrowing fee kink model: calculating borrowing factor per second",
))?;
if usage_factor > *optimal_usage_factor && T::UNIT > *optimal_usage_factor {
let diff =
usage_factor
.checked_sub(optimal_usage_factor)
.ok_or(crate::Error::Computation(
"borrowing fee kink model: calculating diff",
))?;
let above_optimal_usage_borrowing_factor =
self.above_optimal_usage_borrowing_factor(is_long);
let additional_borrowing_factor_per_second =
if above_optimal_usage_borrowing_factor > base_borrowing_factor {
above_optimal_usage_borrowing_factor
.checked_sub(base_borrowing_factor)
.ok_or(crate::Error::Computation(
"borrowing fee kink model: calculating additional factor",
))?
} else {
T::zero()
};
let divisor =
T::UNIT
.checked_sub(optimal_usage_factor)
.ok_or(crate::Error::Computation(
"borrowing fee kink model: calculating divisor",
))?;
let borrowing_factor_per_second = additional_borrowing_factor_per_second
.checked_mul_div(&diff, &divisor)
.and_then(|a| borrowing_factor_per_second.checked_add(&a))
.ok_or(crate::Error::Computation(
"borrowing fee kink model: increasing borrowing factor per second",
))?;
Ok(Some(borrowing_factor_per_second))
} else {
Ok(Some(borrowing_factor_per_second))
}
}
}
#[derive(Debug, Clone, Copy, TypedBuilder)]
pub struct BorrowingFeeKinkModelParamsForOneSide<T> {
optimal_usage_factor: T,
base_borrowing_factor: T,
above_optimal_usage_borrowing_factor: T,
}
#[derive(Debug, Clone, Copy, TypedBuilder)]
pub struct FundingFeeParams<T> {
exponent: T,
funding_factor: T,
increase_factor_per_second: T,
decrease_factor_per_second: T,
max_factor_per_second: T,
min_factor_per_second: T,
threshold_for_stable_funding: T,
threshold_for_decrease_funding: T,
}
impl<T> FundingFeeParams<T> {
pub fn exponent(&self) -> &T {
&self.exponent
}
pub fn increase_factor_per_second(&self) -> &T {
&self.increase_factor_per_second
}
pub fn decrease_factor_per_second(&self) -> &T {
&self.decrease_factor_per_second
}
pub fn max_factor_per_second(&self) -> &T {
&self.max_factor_per_second
}
pub fn min_factor_per_second(&self) -> &T {
&self.min_factor_per_second
}
pub fn factor(&self) -> &T {
&self.funding_factor
}
pub fn threshold_for_stable_funding(&self) -> &T {
&self.threshold_for_stable_funding
}
pub fn threshold_for_decrease_funding(&self) -> &T {
&self.threshold_for_decrease_funding
}
pub fn change(
&self,
funding_factor_per_second: &T::Signed,
long_open_interest: &T,
short_open_interest: &T,
diff_factor: &T,
) -> FundingRateChangeType
where
T: Ord + Unsigned,
{
use num_traits::Signed;
let is_skew_the_same_direction_as_funding = (funding_factor_per_second.is_positive()
&& *long_open_interest > *short_open_interest)
|| (funding_factor_per_second.is_negative()
&& *long_open_interest < *short_open_interest);
if is_skew_the_same_direction_as_funding {
if *diff_factor > self.threshold_for_stable_funding {
FundingRateChangeType::Increase
} else if *diff_factor < self.threshold_for_decrease_funding {
FundingRateChangeType::Decrease
} else {
FundingRateChangeType::NoChange
}
} else {
FundingRateChangeType::Increase
}
}
}
#[derive(Default, Debug)]
pub enum FundingRateChangeType {
#[default]
NoChange,
Increase,
Decrease,
}
#[derive(Debug, Clone, Copy, TypedBuilder)]
pub struct LiquidationFeeParams<T> {
factor: T,
receiver_factor: T,
}
impl<T> LiquidationFeeParams<T> {
pub(crate) fn fee<const DECIMALS: u8>(
&self,
size_delta_usd: &T,
collateral_token_price: &Price<T>,
) -> crate::Result<LiquidationFees<T>>
where
T: FixedPointOps<DECIMALS>,
{
if self.factor.is_zero() {
return Ok(Default::default());
}
let fee_value = utils::apply_factor(size_delta_usd, &self.factor).ok_or(
crate::Error::Computation("liquidation fee: calculating fee value"),
)?;
let fee_amount = fee_value
.checked_round_up_div(collateral_token_price.pick_price(false))
.ok_or(crate::Error::Computation(
"liquidation fee: calculating fee amount",
))?;
let fee_amount_for_receiver = utils::apply_factor(&fee_amount, &self.receiver_factor)
.ok_or(crate::Error::Computation(
"liquidation fee: calculating fee amount for receiver",
))?;
Ok(LiquidationFees {
fee_value,
fee_amount,
fee_amount_for_receiver,
})
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "anchor-lang",
derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
)]
pub struct Fees<T> {
fee_amount_for_receiver: T,
fee_amount_for_pool: T,
}
#[cfg(feature = "gmsol-utils")]
impl<T: gmsol_utils::InitSpace> gmsol_utils::InitSpace for Fees<T> {
const INIT_SPACE: usize = 2 * T::INIT_SPACE;
}
impl<T: Zero> Default for Fees<T> {
fn default() -> Self {
Self {
fee_amount_for_receiver: Zero::zero(),
fee_amount_for_pool: Zero::zero(),
}
}
}
impl<T> Fees<T> {
pub fn new(pool: T, receiver: T) -> Self {
Self {
fee_amount_for_pool: pool,
fee_amount_for_receiver: receiver,
}
}
pub fn fee_amount_for_receiver(&self) -> &T {
&self.fee_amount_for_receiver
}
pub fn fee_amount_for_pool(&self) -> &T {
&self.fee_amount_for_pool
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "anchor-lang",
derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
)]
#[derive(Debug, Clone, Copy)]
pub struct OrderFees<T> {
base: Fees<T>,
fee_value: T,
}
#[cfg(feature = "gmsol-utils")]
impl<T: gmsol_utils::InitSpace> gmsol_utils::InitSpace for OrderFees<T> {
const INIT_SPACE: usize = Fees::<T>::INIT_SPACE + T::INIT_SPACE;
}
impl<T> OrderFees<T> {
pub fn fee_amounts(&self) -> &Fees<T> {
&self.base
}
pub fn fee_value(&self) -> &T {
&self.fee_value
}
}
impl<T: Zero> Default for OrderFees<T> {
fn default() -> Self {
Self {
base: Default::default(),
fee_value: Zero::zero(),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "anchor-lang",
derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
)]
#[derive(Debug, Clone, Copy)]
pub struct BorrowingFees<T> {
fee_amount: T,
fee_amount_for_receiver: T,
}
#[cfg(feature = "gmsol-utils")]
impl<T: gmsol_utils::InitSpace> gmsol_utils::InitSpace for BorrowingFees<T> {
const INIT_SPACE: usize = 2 * T::INIT_SPACE;
}
impl<T> BorrowingFees<T> {
pub fn fee_amount(&self) -> &T {
&self.fee_amount
}
pub fn fee_amount_for_receiver(&self) -> &T {
&self.fee_amount_for_receiver
}
pub fn fee_amount_for_pool(&self) -> crate::Result<T>
where
T: CheckedSub,
{
self.fee_amount
.checked_sub(&self.fee_amount_for_receiver)
.ok_or(crate::Error::Computation(
"borrowing fee: calculating fee for pool",
))
}
}
impl<T: Zero> Default for BorrowingFees<T> {
fn default() -> Self {
Self {
fee_amount: Zero::zero(),
fee_amount_for_receiver: Zero::zero(),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "anchor-lang",
derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
)]
#[derive(Debug, Clone, Copy, TypedBuilder)]
pub struct FundingFees<T> {
amount: T,
claimable_long_token_amount: T,
claimable_short_token_amount: T,
}
#[cfg(feature = "gmsol-utils")]
impl<T: gmsol_utils::InitSpace> gmsol_utils::InitSpace for FundingFees<T> {
const INIT_SPACE: usize = 3 * T::INIT_SPACE;
}
impl<T> FundingFees<T> {
pub fn amount(&self) -> &T {
&self.amount
}
pub fn claimable_long_token_amount(&self) -> &T {
&self.claimable_long_token_amount
}
pub fn claimable_short_token_amount(&self) -> &T {
&self.claimable_short_token_amount
}
}
impl<T: Zero> Default for FundingFees<T> {
fn default() -> Self {
Self {
amount: Zero::zero(),
claimable_long_token_amount: Zero::zero(),
claimable_short_token_amount: Zero::zero(),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "anchor-lang",
derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
)]
#[derive(Debug, Clone, Copy)]
pub struct LiquidationFees<T> {
fee_value: T,
fee_amount: T,
fee_amount_for_receiver: T,
}
#[cfg(feature = "gmsol-utils")]
impl<T: gmsol_utils::InitSpace> gmsol_utils::InitSpace for LiquidationFees<T> {
const INIT_SPACE: usize = 3 * T::INIT_SPACE;
}
impl<T: Zero> Default for LiquidationFees<T> {
fn default() -> Self {
Self {
fee_value: Zero::zero(),
fee_amount: Zero::zero(),
fee_amount_for_receiver: Zero::zero(),
}
}
}
impl<T> LiquidationFees<T> {
pub fn fee_amount(&self) -> &T {
&self.fee_amount
}
pub fn fee_amount_for_receiver(&self) -> &T {
&self.fee_amount_for_receiver
}
pub fn fee_amount_for_pool(&self) -> crate::Result<T>
where
T: CheckedSub,
{
self.fee_amount
.checked_sub(&self.fee_amount_for_receiver)
.ok_or(crate::Error::Computation(
"liquidation fee: calculating fee for pool",
))
}
pub fn fee_value(&self) -> &T {
&self.fee_value
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "anchor-lang",
derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
)]
#[derive(Debug, Clone, Copy)]
pub struct PositionFees<T> {
paid_order_and_borrowing_fee_value: T,
order: OrderFees<T>,
borrowing: BorrowingFees<T>,
funding: FundingFees<T>,
liquidation: Option<LiquidationFees<T>>,
}
#[cfg(feature = "gmsol-utils")]
impl<T: gmsol_utils::InitSpace> gmsol_utils::InitSpace for PositionFees<T> {
const INIT_SPACE: usize = T::INIT_SPACE
+ OrderFees::<T>::INIT_SPACE
+ BorrowingFees::<T>::INIT_SPACE
+ FundingFees::<T>::INIT_SPACE
+ 1
+ LiquidationFees::<T>::INIT_SPACE;
}
impl<T> PositionFees<T> {
pub fn for_receiver(&self) -> crate::Result<T>
where
T: CheckedAdd,
{
self.order
.fee_amounts()
.fee_amount_for_receiver()
.checked_add(self.borrowing.fee_amount_for_receiver())
.and_then(|total| {
if let Some(fees) = self.liquidation_fees() {
total.checked_add(fees.fee_amount_for_receiver())
} else {
Some(total)
}
})
.ok_or(crate::Error::Computation("calculating fee for receiver"))
}
pub fn for_pool<const DECIMALS: u8>(&self) -> crate::Result<T>
where
T: FixedPointOps<DECIMALS>,
{
let amount = self
.order
.fee_amounts()
.fee_amount_for_pool()
.checked_add(&self.borrowing.fee_amount_for_pool()?)
.ok_or(crate::Error::Computation("adding borrowing fee for pool"))
.and_then(|total| {
if let Some(fees) = self.liquidation_fees() {
total
.checked_add(&fees.fee_amount_for_pool()?)
.ok_or(crate::Error::Computation("adding liquidation fee for pool"))
} else {
Ok(total)
}
})?;
Ok(amount)
}
pub fn paid_order_and_borrowing_fee_value(&self) -> &T {
&self.paid_order_and_borrowing_fee_value
}
pub fn order_fees(&self) -> &OrderFees<T> {
&self.order
}
pub fn borrowing_fees(&self) -> &BorrowingFees<T> {
&self.borrowing
}
pub fn funding_fees(&self) -> &FundingFees<T> {
&self.funding
}
pub fn liquidation_fees(&self) -> Option<&LiquidationFees<T>> {
self.liquidation.as_ref()
}
pub fn total_cost_amount(&self) -> crate::Result<T>
where
T: CheckedAdd,
{
self.total_cost_excluding_funding()?
.checked_add(&self.funding.amount)
.ok_or(crate::Error::Overflow)
}
pub fn total_cost_excluding_funding(&self) -> crate::Result<T>
where
T: CheckedAdd,
{
self.order
.fee_amounts()
.fee_amount_for_pool()
.checked_add(self.order.fee_amounts().fee_amount_for_receiver())
.and_then(|acc| acc.checked_add(self.borrowing.fee_amount()))
.and_then(|acc| {
if let Some(fees) = self.liquidation_fees() {
acc.checked_add(fees.fee_amount())
} else {
Some(acc)
}
})
.ok_or(crate::Error::Computation(
"overflow while calculating total cost excluding funding",
))
}
pub(crate) fn set_paid_order_and_borrowing_fee_value(
&mut self,
paid_order_and_borrowing_fee_value: T,
) {
self.paid_order_and_borrowing_fee_value = paid_order_and_borrowing_fee_value;
}
pub fn clear_fees_excluding_funding(&mut self)
where
T: Zero,
{
self.order = Default::default();
self.borrowing = BorrowingFees::default();
self.liquidation = None;
}
pub fn set_borrowing_fees<const DECIMALS: u8>(
mut self,
receiver_factor: &T,
price: &Price<T>,
value: T,
) -> crate::Result<Self>
where
T: FixedPointOps<DECIMALS>,
{
let price = price.pick_price(false);
debug_assert!(!price.is_zero(), "must be non-zero");
let amount = value
.checked_div(price)
.ok_or(crate::Error::Computation("calculating borrowing amount"))?;
let paid_fee_value = self
.paid_order_and_borrowing_fee_value
.checked_add(&value)
.ok_or(crate::Error::Computation("adding borrowing fee value"))?;
self.borrowing.fee_amount_for_receiver =
crate::utils::apply_factor(&amount, receiver_factor).ok_or(
crate::Error::Computation("calculating borrowing fee amount for receiver"),
)?;
self.borrowing.fee_amount = amount;
self.paid_order_and_borrowing_fee_value = paid_fee_value;
Ok(self)
}
pub fn set_funding_fees(mut self, fees: FundingFees<T>) -> Self {
self.funding = fees;
self
}
pub fn set_liquidation_fees(mut self, fees: Option<LiquidationFees<T>>) -> Self {
self.liquidation = fees;
self
}
}
impl<T: Zero> Default for PositionFees<T> {
fn default() -> Self {
Self {
paid_order_and_borrowing_fee_value: Zero::zero(),
order: Default::default(),
borrowing: Default::default(),
funding: Default::default(),
liquidation: None,
}
}
}