use crate::prelude::*;
use super::config::Config;
impl Config {
pub fn calculate_trade_fee(
&self,
old_notional_size_in_collateral: Signed<Collateral>,
new_notional_size_in_collateral: Signed<Collateral>,
old_counter_collateral: Collateral,
new_counter_collateral: Collateral,
) -> Result<Collateral> {
debug_assert!(
old_notional_size_in_collateral.is_zero()
|| (old_notional_size_in_collateral.is_negative()
== new_notional_size_in_collateral.is_negative())
);
let old_notional_size_in_collateral = old_notional_size_in_collateral.abs_unsigned();
let new_notional_size_in_collateral = new_notional_size_in_collateral.abs_unsigned();
let notional_size_fee = match new_notional_size_in_collateral
.checked_sub(old_notional_size_in_collateral)
.ok()
{
Some(delta) => {
debug_assert!(old_notional_size_in_collateral <= new_notional_size_in_collateral);
delta.checked_mul_dec(self.trading_fee_notional_size)?
}
None => {
debug_assert!(old_notional_size_in_collateral > new_notional_size_in_collateral);
Collateral::zero()
}
};
let counter_collateral_fee = match new_counter_collateral
.checked_sub(old_counter_collateral)
.ok()
{
Some(delta) => {
debug_assert!(old_counter_collateral <= new_counter_collateral);
delta.checked_mul_dec(self.trading_fee_counter_collateral)?
}
None => {
debug_assert!(old_counter_collateral > new_counter_collateral);
Collateral::zero()
}
};
notional_size_fee
.checked_add(counter_collateral_fee)
.context("Overflow when calculating trading fee")
}
pub fn calculate_trade_fee_open(
&self,
notional_size_in_collateral: Signed<Collateral>,
counter_collateral: Collateral,
) -> Result<Collateral> {
self.calculate_trade_fee(
Signed::zero(),
notional_size_in_collateral,
Collateral::zero(),
counter_collateral,
)
}
}
pub mod events {
use super::*;
use crate::contracts::market::order::OrderId;
use crate::contracts::market::position::PositionId;
use crate::{constants::event_key, contracts::market::deferred_execution::DeferredExecId};
use cosmwasm_std::{Decimal256, Event};
#[derive(Debug, Clone)]
pub enum TradeId {
Position(PositionId),
LimitOrder(OrderId),
Deferred(DeferredExecId),
}
#[derive(Debug, Clone, Copy)]
pub enum FeeSource {
Trading,
Borrow,
DeltaNeutrality,
}
impl FeeSource {
fn as_str(self) -> &'static str {
match self {
FeeSource::Trading => "trading",
FeeSource::Borrow => "borrow",
FeeSource::DeltaNeutrality => "delta-neutrality",
}
}
}
impl FromStr for FeeSource {
type Err = anyhow::Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"trading" => Ok(FeeSource::Trading),
"borrow" => Ok(FeeSource::Borrow),
"delta-neutrality" => Ok(FeeSource::DeltaNeutrality),
_ => Err(anyhow::anyhow!("Unknown FeeSource {s}")),
}
}
}
#[derive(Debug, Clone)]
pub struct FeeEvent {
pub trade_id: TradeId,
pub fee_source: FeeSource,
pub lp_amount: Collateral,
pub lp_amount_usd: Usd,
pub xlp_amount: Collateral,
pub xlp_amount_usd: Usd,
pub protocol_amount: Collateral,
pub protocol_amount_usd: Usd,
}
impl From<FeeEvent> for Event {
fn from(
FeeEvent {
trade_id,
fee_source,
lp_amount,
lp_amount_usd,
xlp_amount,
xlp_amount_usd,
protocol_amount,
protocol_amount_usd,
}: FeeEvent,
) -> Self {
let (trade_id_key, trade_id_val) = match trade_id {
TradeId::Position(pos_id) => ("pos-id", pos_id.to_string()),
TradeId::LimitOrder(order_id) => ("order-id", order_id.to_string()),
TradeId::Deferred(deferred_id) => ("deferred-id", deferred_id.to_string()),
};
Event::new("fee")
.add_attribute(trade_id_key, trade_id_val)
.add_attribute("source", fee_source.as_str())
.add_attribute("lp-amount", lp_amount.to_string())
.add_attribute("lp-amount-usd", lp_amount_usd.to_string())
.add_attribute("xlp-amount", xlp_amount.to_string())
.add_attribute("xlp-amount-usd", xlp_amount_usd.to_string())
.add_attribute("protocol-amount", protocol_amount.to_string())
.add_attribute("protocol-amount-usd", protocol_amount_usd.to_string())
}
}
impl TryFrom<Event> for FeeEvent {
type Error = anyhow::Error;
fn try_from(evt: Event) -> anyhow::Result<Self> {
let trade_id = match evt.try_u64_attr("pos-id")? {
Some(pos_id) => TradeId::Position(PositionId::new(pos_id)),
None => match evt.try_u64_attr("order-id")? {
Some(order_id) => TradeId::LimitOrder(OrderId::new(order_id)),
None => {
let deferred_id = evt.u64_attr("deferred-id")?;
TradeId::Deferred(DeferredExecId::from_u64(deferred_id))
}
},
};
Ok(FeeEvent {
trade_id,
fee_source: evt.string_attr("source")?.parse()?,
lp_amount: evt.decimal_attr("lp-amount")?,
lp_amount_usd: evt.decimal_attr("lp-amount-usd")?,
xlp_amount: evt.decimal_attr("xlp-amount")?,
xlp_amount_usd: evt.decimal_attr("xlp-amount-usd")?,
protocol_amount: evt.decimal_attr("protocol-amount")?,
protocol_amount_usd: evt.decimal_attr("protocol-amount-usd")?,
})
}
}
pub struct FundingPaymentEvent {
pub pos_id: PositionId,
pub amount: Signed<Collateral>,
pub amount_usd: Signed<Usd>,
pub direction: DirectionToBase,
}
impl From<&FundingPaymentEvent> for Event {
fn from(src: &FundingPaymentEvent) -> Self {
Event::new("funding-payment")
.add_attribute("pos-id", src.pos_id.to_string())
.add_attribute("amount", src.amount.to_string())
.add_attribute("amount-usd", src.amount_usd.to_string())
.add_attribute(event_key::DIRECTION, src.direction.as_str())
}
}
impl From<FundingPaymentEvent> for Event {
fn from(src: FundingPaymentEvent) -> Self {
(&src).into()
}
}
impl TryFrom<Event> for FundingPaymentEvent {
type Error = anyhow::Error;
fn try_from(evt: Event) -> anyhow::Result<Self> {
Ok(FundingPaymentEvent {
pos_id: PositionId::new(evt.u64_attr("pos-id")?),
amount: evt.number_attr("amount")?,
amount_usd: evt.number_attr("amount-usd")?,
direction: evt.direction_attr(event_key::DIRECTION)?,
})
}
}
pub struct FundingRateChangeEvent {
pub time: Timestamp,
pub long_rate_base: Number,
pub short_rate_base: Number,
}
impl From<FundingRateChangeEvent> for Event {
fn from(
FundingRateChangeEvent {
time,
long_rate_base,
short_rate_base,
}: FundingRateChangeEvent,
) -> Self {
Event::new("funding-rate-change")
.add_attribute("time", time.to_string())
.add_attribute("long-rate", long_rate_base.to_string())
.add_attribute("short-rate", short_rate_base.to_string())
}
}
impl TryFrom<Event> for FundingRateChangeEvent {
type Error = anyhow::Error;
fn try_from(evt: Event) -> anyhow::Result<Self> {
Ok(FundingRateChangeEvent {
time: evt.timestamp_attr("time")?,
long_rate_base: evt.number_attr("long-rate-base")?,
short_rate_base: evt.number_attr("short-rate-base")?,
})
}
}
pub struct BorrowFeeChangeEvent {
pub time: Timestamp,
pub total_rate: Decimal256,
pub lp_rate: Decimal256,
pub xlp_rate: Decimal256,
}
impl From<BorrowFeeChangeEvent> for Event {
fn from(
BorrowFeeChangeEvent {
time,
total_rate,
lp_rate,
xlp_rate,
}: BorrowFeeChangeEvent,
) -> Self {
Event::new("borrow-fee-change")
.add_attribute("time", time.to_string())
.add_attribute("total", total_rate.to_string())
.add_attribute("lp", lp_rate.to_string())
.add_attribute("xlp", xlp_rate.to_string())
}
}
impl TryFrom<Event> for BorrowFeeChangeEvent {
type Error = anyhow::Error;
fn try_from(evt: Event) -> Result<Self, Self::Error> {
Ok(BorrowFeeChangeEvent {
time: evt.timestamp_attr("time")?,
total_rate: evt.decimal_attr("total")?,
lp_rate: evt.decimal_attr("lp")?,
xlp_rate: evt.decimal_attr("xlp")?,
})
}
}
pub struct CrankFeeEvent {
pub trade_id: TradeId,
pub amount: Collateral,
pub amount_usd: Usd,
pub old_balance: Collateral,
pub new_balance: Collateral,
}
impl From<CrankFeeEvent> for Event {
fn from(
CrankFeeEvent {
trade_id,
amount,
amount_usd,
old_balance,
new_balance,
}: CrankFeeEvent,
) -> Self {
let (trade_id_key, trade_id_val) = match trade_id {
TradeId::Position(pos_id) => ("pos-id", pos_id.to_string()),
TradeId::LimitOrder(order_id) => ("order-id", order_id.to_string()),
TradeId::Deferred(deferred_id) => ("deferred-id", deferred_id.to_string()),
};
Event::new("crank-fee")
.add_attribute(trade_id_key, trade_id_val)
.add_attribute("amount", amount.to_string())
.add_attribute("amount-usd", amount_usd.to_string())
.add_attribute("old-balance", old_balance.to_string())
.add_attribute("new-balance", new_balance.to_string())
}
}
impl TryFrom<Event> for CrankFeeEvent {
type Error = anyhow::Error;
fn try_from(evt: Event) -> anyhow::Result<Self> {
let trade_id = match evt.try_u64_attr("pos-id")? {
Some(pos_id) => TradeId::Position(PositionId::new(pos_id)),
None => match evt.try_u64_attr("order-id")? {
Some(order_id) => TradeId::LimitOrder(OrderId::new(order_id)),
None => {
let deferred_id = evt.u64_attr("deferred-id")?;
TradeId::Deferred(DeferredExecId::from_u64(deferred_id))
}
},
};
Ok(CrankFeeEvent {
trade_id,
amount: evt.decimal_attr("amount")?,
amount_usd: evt.decimal_attr("amount-usd")?,
old_balance: evt.decimal_attr("old-balance")?,
new_balance: evt.decimal_attr("new-balance")?,
})
}
}
pub struct CrankFeeEarnedEvent {
pub recipient: Addr,
pub amount: NonZero<Collateral>,
pub amount_usd: NonZero<Usd>,
}
impl From<CrankFeeEarnedEvent> for Event {
fn from(
CrankFeeEarnedEvent {
recipient,
amount,
amount_usd,
}: CrankFeeEarnedEvent,
) -> Self {
Event::new("crank-fee-claimed")
.add_attribute("recipient", recipient.to_string())
.add_attribute("amount", amount.to_string())
.add_attribute("amount-usd", amount_usd.to_string())
}
}
impl TryFrom<Event> for CrankFeeEarnedEvent {
type Error = anyhow::Error;
fn try_from(evt: Event) -> anyhow::Result<Self> {
Ok(CrankFeeEarnedEvent {
recipient: evt.unchecked_addr_attr("recipient")?,
amount: evt.non_zero_attr("amount")?,
amount_usd: evt.non_zero_attr("amount-usd")?,
})
}
}
pub struct InsufficientMarginEvent {
pub pos: PositionId,
pub fee_type: FeeType,
pub available: Signed<Collateral>,
pub requested: Signed<Collateral>,
pub desc: Option<String>,
}
impl From<&InsufficientMarginEvent> for Event {
fn from(
InsufficientMarginEvent {
pos,
fee_type,
available,
requested,
desc,
}: &InsufficientMarginEvent,
) -> Self {
let evt = Event::new(event_key::INSUFFICIENT_MARGIN)
.add_attribute(event_key::POS_ID, pos.to_string())
.add_attribute(event_key::FEE_TYPE, fee_type.as_str())
.add_attribute(event_key::AVAILABLE, available.to_string())
.add_attribute(event_key::REQUESTED, requested.to_string());
match desc {
Some(desc) => evt.add_attribute(event_key::DESC, desc),
None => evt,
}
}
}
impl From<InsufficientMarginEvent> for Event {
fn from(event: InsufficientMarginEvent) -> Self {
(&event).into()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FeeType {
Overall,
Borrow,
DeltaNeutrality,
Funding,
Crank,
FundingTotal,
}
impl FeeType {
pub fn as_str(self) -> &'static str {
match self {
FeeType::Overall => "overall",
FeeType::Borrow => "borrow",
FeeType::DeltaNeutrality => "delta-neutrality",
FeeType::Funding => "funding",
FeeType::FundingTotal => "funding-total",
FeeType::Crank => "crank",
}
}
}
}
#[cfg(test)]
mod tests {
use crate::contracts::market::spot_price::SpotPriceConfig;
use super::*;
#[test]
fn trade_fee_open() {
let config = Config {
trading_fee_notional_size: "0.01".parse().unwrap(),
trading_fee_counter_collateral: "0.02".parse().unwrap(),
..Config::new(SpotPriceConfig::Manual {
admin: Addr::unchecked("foo"),
})
};
assert_eq!(
config
.calculate_trade_fee_open("-500".parse().unwrap(), "200".parse().unwrap())
.unwrap(),
"9".parse().unwrap()
)
}
#[test]
fn trade_fee_update() {
let config = Config {
trading_fee_notional_size: "0.01".parse().unwrap(),
trading_fee_counter_collateral: "0.02".parse().unwrap(),
..Config::new(SpotPriceConfig::Manual {
admin: Addr::unchecked("foo"),
})
};
assert_eq!(
config
.calculate_trade_fee(
"-100".parse().unwrap(),
"-500".parse().unwrap(),
"100".parse().unwrap(),
"200".parse().unwrap()
)
.unwrap(),
"6".parse().unwrap()
);
assert_eq!(
config
.calculate_trade_fee(
"-100".parse().unwrap(),
"-500".parse().unwrap(),
"300".parse().unwrap(),
"200".parse().unwrap()
)
.unwrap(),
"4".parse().unwrap()
);
assert_eq!(
config
.calculate_trade_fee(
"-600".parse().unwrap(),
"-500".parse().unwrap(),
"300".parse().unwrap(),
"200".parse().unwrap()
)
.unwrap(),
"0".parse().unwrap()
);
assert_eq!(
config
.calculate_trade_fee(
"-600".parse().unwrap(),
"-500".parse().unwrap(),
"100".parse().unwrap(),
"200".parse().unwrap()
)
.unwrap(),
"2".parse().unwrap()
);
}
}