From 870f4aaab90311e62a5ad0a7788e7647f3f85bdd Mon Sep 17 00:00:00 2001 From: 0xaslan <161349919+0xaslan@users.noreply.github.com> Date: Thu, 30 Jan 2025 14:53:37 -0500 Subject: [PATCH] Pay trading fees with input token (#319) * emit volumes event on epoch change * emit volumes event on epoch change * pay trading fee with input token * gitignore * remove pnpm lock * undo gitignore --- packages/deepbook/sources/book/book.move | 14 +++- packages/deepbook/sources/book/fill.move | 20 ++++- packages/deepbook/sources/book/order.move | 56 +++++++------ .../deepbook/sources/book/order_info.move | 82 ++++++++++--------- .../deepbook/sources/helper/constants.move | 5 ++ packages/deepbook/sources/pool.move | 31 ++++--- packages/deepbook/sources/state/balances.move | 18 ++++ packages/deepbook/sources/state/history.move | 2 + packages/deepbook/sources/state/state.move | 26 +++--- .../deepbook/sources/vault/deep_price.move | 32 +++++++- 10 files changed, 184 insertions(+), 102 deletions(-) diff --git a/packages/deepbook/sources/book/book.move b/packages/deepbook/sources/book/book.move index 22c5b110..a73bb1a4 100644 --- a/packages/deepbook/sources/book/book.move +++ b/packages/deepbook/sources/book/book.move @@ -171,15 +171,21 @@ public(package) fun get_quantity_out( else book_side.prev_slice(ref, offset); }; - let quantity_in_deep = if (is_bid) { - deep_price.deep_quantity( + let fee_quantity = if (is_bid) { + deep_price.fee_quantity( quantity_out, quote_quantity - quantity_in_left, + is_bid, ) } else { - deep_price.deep_quantity(base_quantity - quantity_in_left, quantity_out) + deep_price.fee_quantity( + base_quantity - quantity_in_left, + quantity_out, + is_bid, + ) }; - let deep_fee = math::mul(taker_fee, quantity_in_deep); + + let deep_fee = math::mul(taker_fee, fee_quantity.deep()); if (is_bid) { (quantity_out, quantity_in_left, deep_fee) diff --git a/packages/deepbook/sources/book/fill.move b/packages/deepbook/sources/book/fill.move index 0bfc73e0..a131202a 100644 --- a/packages/deepbook/sources/book/fill.move +++ b/packages/deepbook/sources/book/fill.move @@ -166,10 +166,22 @@ public(package) fun get_settled_maker_quantities(self: &Fill): Balances { balances::new(base, quote, 0) } -public(package) fun set_fill_maker_fee(self: &mut Fill, fee: u64) { - self.maker_fee = fee; +public(package) fun set_fill_maker_fee(self: &mut Fill, fee: &Balances) { + if (fee.deep() > 0) { + self.maker_fee_is_deep = true; + } else { + self.maker_fee_is_deep = false; + }; + + self.maker_fee = fee.non_zero_value(); } -public(package) fun set_fill_taker_fee(self: &mut Fill, fee: u64) { - self.taker_fee = fee; +public(package) fun set_fill_taker_fee(self: &mut Fill, fee: &Balances) { + if (fee.deep() > 0) { + self.taker_fee_is_deep = true; + } else { + self.taker_fee_is_deep = false; + }; + + self.taker_fee = fee.non_zero_value(); } diff --git a/packages/deepbook/sources/book/order.move b/packages/deepbook/sources/book/order.move index 063ef8eb..0c69b963 100644 --- a/packages/deepbook/sources/book/order.move +++ b/packages/deepbook/sources/book/order.move @@ -213,15 +213,14 @@ public(package) fun calculate_cancel_refund( let cancel_quantity = cancel_quantity.get_with_default( self.quantity - self.filled_quantity, ); - let deep_out = math::mul( - maker_fee, - self - .order_deep_price() - .deep_quantity( - cancel_quantity, - math::mul(cancel_quantity, self.price()), - ), - ); + let mut fee_quantity = self + .order_deep_price + .fee_quantity( + cancel_quantity, + math::mul(cancel_quantity, self.price()), + self.is_bid(), + ); + fee_quantity.mul(maker_fee); let mut base_out = 0; let mut quote_out = 0; @@ -231,35 +230,40 @@ public(package) fun calculate_cancel_refund( base_out = cancel_quantity; }; - balances::new(base_out, quote_out, deep_out) + let mut refund = balances::new(base_out, quote_out, 0); + refund.add_balances(fee_quantity); + + refund } -public(package) fun locked_balance( - self: &Order, - maker_fee: u64, -): (u64, u64, u64) { +public(package) fun locked_balance(self: &Order, maker_fee: u64): Balances { let (is_bid, order_price, _) = utils::decode_order_id(self.order_id()); let mut base_quantity = 0; let mut quote_quantity = 0; let remaining_base_quantity = self.quantity() - self.filled_quantity(); - let remaining_quote_quantity = math::mul(remaining_base_quantity, order_price); + let remaining_quote_quantity = math::mul( + remaining_base_quantity, + order_price, + ); if (is_bid) { quote_quantity = quote_quantity + remaining_quote_quantity; } else { base_quantity = base_quantity + remaining_base_quantity; }; - let deep_quantity = math::mul( - maker_fee, - self - .order_deep_price() - .deep_quantity( - remaining_base_quantity, - remaining_quote_quantity, - ), - ); - - (base_quantity, quote_quantity, deep_quantity) + let mut fee_quantity = self + .order_deep_price() + .fee_quantity( + remaining_base_quantity, + remaining_quote_quantity, + is_bid, + ); + fee_quantity.mul(maker_fee); + + let mut locked_balance = balances::new(base_quantity, quote_quantity, 0); + locked_balance.add_balances(fee_quantity); + + locked_balance } public(package) fun emit_order_canceled( diff --git a/packages/deepbook/sources/book/order_info.move b/packages/deepbook/sources/book/order_info.move index 00bbaa45..4bfe7c88 100644 --- a/packages/deepbook/sources/book/order_info.move +++ b/packages/deepbook/sources/book/order_info.move @@ -283,6 +283,16 @@ public(package) fun fills_ref(self: &mut OrderInfo): &mut vector { &mut self.fills } +public(package) fun paid_fees_balances(self: &OrderInfo): Balances { + if (self.fee_is_deep) { + balances::new(0, 0, self.paid_fees) + } else if (self.is_bid) { + balances::new(0, self.paid_fees, 0) + } else { + balances::new(self.paid_fees, 0, 0) + } +} + /// Given a partially filled `OrderInfo`, the taker fee and maker fee, for the user /// placing the order, calculate all of the balances that need to be settled and /// the balances that are owed. The executed quantity is multiplied by the taker_fee @@ -292,36 +302,32 @@ public(package) fun calculate_partial_fill_balances( taker_fee: u64, maker_fee: u64, ): (Balances, Balances) { - let taker_deep_in = math::mul( - taker_fee, - self - .order_deep_price - .deep_quantity( - self.executed_quantity, - self.cumulative_quote_quantity, - ), - ); - self.paid_fees = taker_deep_in; - let fills = &mut self.fills; + let mut taker_fee_quantity = self + .order_deep_price + .fee_quantity( + self.executed_quantity, + self.cumulative_quote_quantity, + self.is_bid, + ); + taker_fee_quantity.mul(taker_fee); + self.paid_fees = taker_fee_quantity.non_zero_value(); + let fills = &mut self.fills; let mut i = 0; while (i < fills.length()) { let fill = &mut fills[i]; if (!fill.expired()) { let base_quantity = fill.base_quantity(); let quote_quantity = fill.quote_quantity(); - let fill_taker_fee = math::mul( - taker_fee, - self - .order_deep_price - .deep_quantity( - base_quantity, - quote_quantity, - ), - ); - if (fill_taker_fee > 0) { - fill.set_fill_taker_fee(fill_taker_fee); - }; + let mut fill_taker_fee_quantity = self + .order_deep_price + .fee_quantity( + base_quantity, + quote_quantity, + self.is_bid, + ); + fill_taker_fee_quantity.mul(taker_fee); + fill.set_fill_taker_fee(&fill_taker_fee_quantity); }; i = i + 1; @@ -329,7 +335,7 @@ public(package) fun calculate_partial_fill_balances( let mut settled_balances = balances::new(0, 0, 0); let mut owed_balances = balances::new(0, 0, 0); - owed_balances.add_deep(taker_deep_in); + owed_balances.add_balances(taker_fee_quantity); if (self.is_bid) { settled_balances.add_base(self.executed_quantity); @@ -341,17 +347,16 @@ public(package) fun calculate_partial_fill_balances( let remaining_quantity = self.remaining_quantity(); if (self.order_inserted()) { - let maker_deep_in = math::mul( - maker_fee, - self - .order_deep_price - .deep_quantity( - remaining_quantity, - math::mul(remaining_quantity, self.price()), - ), - ); - self.maker_fees = maker_deep_in; - owed_balances.add_deep(maker_deep_in); + let mut maker_fee_quantity = self + .order_deep_price + .fee_quantity( + remaining_quantity, + math::mul(remaining_quantity, self.price()), + self.is_bid, + ); + maker_fee_quantity.mul(maker_fee); + self.maker_fees = maker_fee_quantity.non_zero_value(); + owed_balances.add_balances(maker_fee_quantity); if (self.is_bid) { owed_balances.add_quote( math::mul(remaining_quantity, self.price()), @@ -512,7 +517,8 @@ public(package) fun emit_orders_filled(self: &OrderInfo, timestamp: u64) { if (!fill.expired()) { event::emit(self.order_filled_from_fill(fill, timestamp)); } else { - let cancel_maker = self.balance_manager_id() == fill.balance_manager_id(); + let cancel_maker = + self.balance_manager_id() == fill.balance_manager_id(); if (cancel_maker) { self.emit_order_canceled_maker_from_fill(fill, timestamp); } else { @@ -591,7 +597,7 @@ fun order_expired_from_fill( is_bid: !self.is_bid(), original_quantity: fill.original_maker_quantity(), base_asset_quantity_canceled: fill.base_quantity(), - timestamp + timestamp, } } @@ -610,6 +616,6 @@ fun emit_order_canceled_maker_from_fill( !self.is_bid(), fill.original_maker_quantity(), fill.base_quantity(), - timestamp + timestamp, ) } diff --git a/packages/deepbook/sources/helper/constants.move b/packages/deepbook/sources/helper/constants.move index bcdf35c0..d4a775fc 100644 --- a/packages/deepbook/sources/helper/constants.move +++ b/packages/deepbook/sources/helper/constants.move @@ -14,6 +14,7 @@ const MAX_PRICE: u64 = ((1u128 << 63) - 1) as u64; const DEFAULT_STAKE_REQUIRED: u64 = 100_000_000; // 100 DEEP const HALF: u64 = 500_000_000; const DEEP_UNIT: u64 = 1_000_000; +const FEE_PENALTY_MULTIPLIER: u64 = 250_000_000; // 25% // Restrictions on limit orders. // No restriction on the order. @@ -214,6 +215,10 @@ public fun max_fan_out(): u64 { MAX_FAN_OUT } +public fun fee_penalty_multiplier(): u64 { + FEE_PENALTY_MULTIPLIER +} + #[test_only] public fun maker_fee(): u64 { MAKER_FEE diff --git a/packages/deepbook/sources/pool.move b/packages/deepbook/sources/pool.move index 476795ba..993e52e5 100644 --- a/packages/deepbook/sources/pool.move +++ b/packages/deepbook/sources/pool.move @@ -37,7 +37,6 @@ const EInvalidLotSize: u64 = 4; const EInvalidMinSize: u64 = 5; const EInvalidQuantityIn: u64 = 6; const EIneligibleReferencePool: u64 = 7; -const EFeeTypeNotSupported: u64 = 8; const EInvalidOrderBalanceManager: u64 = 9; const EIneligibleTargetPool: u64 = 10; const EPackageVersionDisabled: u64 = 11; @@ -972,10 +971,13 @@ public fun get_order_deep_required( let self = self.load_inner(); let maker_fee = self.state.governance().trade_params().maker_fee(); let taker_fee = self.state.governance().trade_params().taker_fee(); - let deep_quantity = order_deep_price.deep_quantity( - base_quantity, - math::mul(base_quantity, price), - ); + let deep_quantity = order_deep_price + .fee_quantity( + base_quantity, + math::mul(base_quantity, price), + true, + ) + .deep(); (math::mul(taker_fee, deep_quantity), math::mul(maker_fee, deep_quantity)) } @@ -998,10 +1000,10 @@ public fun locked_balance( account_orders.do_ref!(|order| { let maker_fee = self.state.history().historic_maker_fee(order.epoch()); - let (base, quote, deep) = order.locked_balance(maker_fee); - base_quantity = base_quantity + base; - quote_quantity = quote_quantity + quote; - deep_quantity = deep_quantity + deep; + let locked_balance = order.locked_balance(maker_fee); + base_quantity = base_quantity + locked_balance.base(); + quote_quantity = quote_quantity + locked_balance.quote(); + deep_quantity = deep_quantity + locked_balance.deep(); }); let settled_balances = self @@ -1195,9 +1197,14 @@ fun place_order_int( ctx: &TxContext, ): OrderInfo { let whitelist = self.whitelisted(); - assert!(pay_with_deep || whitelist, EFeeTypeNotSupported); - let self = self.load_inner_mut(); + + let order_deep_price = if (pay_with_deep) { + self.deep_price.get_order_deep_price(whitelist) + } else { + self.deep_price.empty_deep_price() + }; + let mut order_info = order_info::new( self.pool_id, balance_manager.id(), @@ -1211,7 +1218,7 @@ fun place_order_int( pay_with_deep, ctx.epoch(), expire_timestamp, - self.deep_price.get_order_deep_price(whitelist), + order_deep_price, market_order, clock.timestamp_ms(), ); diff --git a/packages/deepbook/sources/state/balances.move b/packages/deepbook/sources/state/balances.move index d79154ea..5e36e35e 100644 --- a/packages/deepbook/sources/state/balances.move +++ b/packages/deepbook/sources/state/balances.move @@ -5,6 +5,8 @@ /// Whenever funds are moved, they are moved in the form of `Balances`. module deepbook::balances; +use deepbook::math; + // === Structs === public struct Balances has store, copy, drop { base: u64, @@ -59,3 +61,19 @@ public(package) fun quote(balances: &Balances): u64 { public(package) fun deep(balances: &Balances): u64 { balances.deep } + +public(package) fun mul(balances: &mut Balances, factor: u64) { + balances.base = math::mul(balances.base, factor); + balances.quote = math::mul(balances.quote, factor); + balances.deep = math::mul(balances.deep, factor); +} + +public(package) fun non_zero_value(balances: &Balances): u64 { + if (balances.base > 0) { + balances.base + } else if (balances.quote > 0) { + balances.quote + } else { + balances.deep + } +} diff --git a/packages/deepbook/sources/state/history.move b/packages/deepbook/sources/state/history.move index e42ac9f0..002919cf 100644 --- a/packages/deepbook/sources/state/history.move +++ b/packages/deepbook/sources/state/history.move @@ -10,6 +10,7 @@ use deepbook::balances::{Self, Balances}; use deepbook::constants; use deepbook::math; use deepbook::trade_params::TradeParams; +use sui::event; use sui::table::{Self, Table}; // === Errors === @@ -89,6 +90,7 @@ public(package) fun reset_volumes( self: &mut History, trade_params: TradeParams, ) { + event::emit(self.volumes); self.volumes = Volumes { total_volume: 0, diff --git a/packages/deepbook/sources/state/state.move b/packages/deepbook/sources/state/state.move index 7dd97f24..b575628b 100644 --- a/packages/deepbook/sources/state/state.move +++ b/packages/deepbook/sources/state/state.move @@ -146,9 +146,7 @@ public(package) fun process_create( maker_fee, ); let (old_settled, old_owed) = account.settle(); - self - .history - .add_total_fees_collected(balances::new(0, 0, order_info.paid_fees())); + self.history.add_total_fees_collected(order_info.paid_fees_balances()); settled.add_balances(old_settled); owed.add_balances(old_owed); @@ -436,25 +434,23 @@ fun process_fills(self: &mut State, fills: &mut vector, ctx: &TxContext) { let historic_maker_fee = self .history .historic_maker_fee(fill.maker_epoch()); - let fee_volume = fill + let maker_is_bid = !fill.taker_is_bid(); + let mut fee_quantity = fill .maker_deep_price() - .deep_quantity(base_volume, quote_volume); - let order_maker_fee = if (whitelisted) { - 0 + .fee_quantity(base_volume, quote_volume, maker_is_bid); + + if (whitelisted) { + fee_quantity.mul(0) } else { - math::mul(fee_volume, historic_maker_fee) + fee_quantity.mul(historic_maker_fee) }; if (!fill.expired()) { - if (order_maker_fee > 0) { - fill.set_fill_maker_fee(order_maker_fee); - }; + fill.set_fill_maker_fee(&fee_quantity); self.history.add_volume(base_volume, account.active_stake()); - self - .history - .add_total_fees_collected(balances::new(0, 0, order_maker_fee)); + self.history.add_total_fees_collected(fee_quantity); } else { - account.add_settled_balances(balances::new(0, 0, order_maker_fee)); + account.add_settled_balances(fee_quantity); }; i = i + 1; diff --git a/packages/deepbook/sources/vault/deep_price.move b/packages/deepbook/sources/vault/deep_price.move index c70520bc..95e72977 100644 --- a/packages/deepbook/sources/vault/deep_price.move +++ b/packages/deepbook/sources/vault/deep_price.move @@ -5,6 +5,8 @@ /// between DEEP and the base and quote assets. module deepbook::deep_price; +use deepbook::balances::{Self, Balances}; +use deepbook::constants; use deepbook::math; use sui::event; @@ -89,15 +91,39 @@ public(package) fun get_order_deep_price( new_order_deep_price(asset_is_base, deep_per_asset) } -public(package) fun deep_quantity( +public(package) fun empty_deep_price(_self: &DeepPrice): OrderDeepPrice { + new_order_deep_price(false, 0) +} + +public(package) fun fee_quantity( self: &OrderDeepPrice, base_quantity: u64, quote_quantity: u64, -): u64 { - if (self.asset_is_base) { + is_bid: bool, +): Balances { + let deep_quantity = if (self.asset_is_base) { math::mul(base_quantity, self.deep_per_asset) } else { math::mul(quote_quantity, self.deep_per_asset) + }; + + if (deep_quantity > 0) { + balances::new(0, 0, deep_quantity) + } else if (is_bid) { + balances::new( + 0, + math::mul( + quote_quantity, + constants::fee_penalty_multiplier(), + ), + 0, + ) + } else { + balances::new( + math::mul(base_quantity, constants::fee_penalty_multiplier()), + 0, + 0, + ) } }