From d90856b138da050cb3e03292dff935c831451a21 Mon Sep 17 00:00:00 2001 From: bathord Date: Sun, 15 Mar 2026 12:34:57 +0300 Subject: [PATCH 1/9] Add maker fee rate to referral fee config --- packages/deepbook/sources/pool.move | 21 ++++++++++------- packages/deepbook/tests/pool_tests.move | 30 ++++++++++++------------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/packages/deepbook/sources/pool.move b/packages/deepbook/sources/pool.move index c4e7a655c..b48e4e645 100644 --- a/packages/deepbook/sources/pool.move +++ b/packages/deepbook/sources/pool.move @@ -108,9 +108,10 @@ public struct DeepBurned has copy, drop, deep_burned: u64, } -/// The configuration for the referral fee rate. +/// The configuration for the referral fee rates. public struct ReferralFeeConfig has store { - fee_rate: u64, + taker_fee_rate: u64, + maker_fee_rate: u64, } /// The key for the referral fee configuration. @@ -939,22 +940,26 @@ public fun update_pool_referral_multiplier( public fun update_pool_referral_fee_rate( self: &mut Pool, referral: &DeepBookPoolReferral, - fee_rate: u64, + taker_fee_rate: u64, + maker_fee_rate: u64, ctx: &TxContext, ) { let _ = self.load_inner(); referral.assert_referral_owner(ctx); assert!(referral.balance_manager_referral_pool_id() == self.id(), EWrongPoolReferral); - assert!(fee_rate <= constants::max_referral_fee_rate(), EInvalidReferralFeeRate); - assert!(fee_rate % constants::fee_precision_multiple() == 0, EInvalidReferralFeeRate); + assert!(taker_fee_rate <= constants::max_referral_fee_rate(), EInvalidReferralFeeRate); + assert!(taker_fee_rate % constants::fee_precision_multiple() == 0, EInvalidReferralFeeRate); + assert!(maker_fee_rate <= constants::max_referral_fee_rate(), EInvalidReferralFeeRate); + assert!(maker_fee_rate % constants::fee_precision_multiple() == 0, EInvalidReferralFeeRate); let referral_id = object::id(referral); let key = ReferralFeeConfigKey(referral_id); if (self.id.exists_(key)) { let config: &mut ReferralFeeConfig = self.id.borrow_mut(key); - config.fee_rate = fee_rate; + config.taker_fee_rate = taker_fee_rate; + config.maker_fee_rate = maker_fee_rate; } else { - self.id.add(key, ReferralFeeConfig { fee_rate }); + self.id.add(key, ReferralFeeConfig { taker_fee_rate, maker_fee_rate }); } } @@ -1994,7 +1999,7 @@ fun process_referral_fees( } else { order_info.executed_quantity() }; - volume_fee = math::mul(volume, config.fee_rate); + volume_fee = math::mul(volume, config.taker_fee_rate); }; let referral_rewards: &mut ReferralRewards = self diff --git a/packages/deepbook/tests/pool_tests.move b/packages/deepbook/tests/pool_tests.move index 2fca4b21a..1eb715f66 100644 --- a/packages/deepbook/tests/pool_tests.move +++ b/packages/deepbook/tests/pool_tests.move @@ -9842,7 +9842,7 @@ fun pool_referral_fee_rate_ok() { { let mut pool = test.take_shared_by_id>(pool_id); let referral = test.take_shared_by_id(referral_id); - pool.update_pool_referral_fee_rate(&referral, fee_rate, test.ctx()); + pool.update_pool_referral_fee_rate(&referral, fee_rate, 0, test.ctx()); return_shared(referral); return_shared(pool); }; @@ -9922,7 +9922,7 @@ fun pool_referral_fee_rate_base_asset() { { let mut pool = test.take_shared_by_id>(pool_id); let referral = test.take_shared_by_id(referral_id); - pool.update_pool_referral_fee_rate(&referral, fee_rate, test.ctx()); + pool.update_pool_referral_fee_rate(&referral, fee_rate, 0, test.ctx()); return_shared(referral); return_shared(pool); }; @@ -10001,7 +10001,7 @@ fun pool_referral_fee_rate_deep() { { let mut pool = test.take_shared_by_id>(pool_id); let referral = test.take_shared_by_id(referral_id); - pool.update_pool_referral_fee_rate(&referral, fee_rate, test.ctx()); + pool.update_pool_referral_fee_rate(&referral, fee_rate, 0, test.ctx()); return_shared(referral); return_shared(pool); }; @@ -10081,7 +10081,7 @@ fun pool_referral_combined_fees() { { let mut pool = test.take_shared_by_id>(pool_id); let referral = test.take_shared_by_id(referral_id); - pool.update_pool_referral_fee_rate(&referral, fee_rate, test.ctx()); + pool.update_pool_referral_fee_rate(&referral, fee_rate, 0, test.ctx()); return_shared(referral); return_shared(pool); }; @@ -10159,7 +10159,7 @@ fun pool_referral_combined_fees_base_asset() { { let mut pool = test.take_shared_by_id>(pool_id); let referral = test.take_shared_by_id(referral_id); - pool.update_pool_referral_fee_rate(&referral, fee_rate, test.ctx()); + pool.update_pool_referral_fee_rate(&referral, fee_rate, 0, test.ctx()); return_shared(referral); return_shared(pool); }; @@ -10237,7 +10237,7 @@ fun pool_referral_combined_fees_deep() { { let mut pool = test.take_shared_by_id>(pool_id); let referral = test.take_shared_by_id(referral_id); - pool.update_pool_referral_fee_rate(&referral, fee_rate, test.ctx()); + pool.update_pool_referral_fee_rate(&referral, fee_rate, 0, test.ctx()); return_shared(referral); return_shared(pool); }; @@ -10353,7 +10353,7 @@ fun whitelisted_pool_referral_fee() { { let mut pool = test.take_shared_by_id>(pool_id); let referral = test.take_shared_by_id(referral_id); - pool.update_pool_referral_fee_rate(&referral, fee_rate, test.ctx()); + pool.update_pool_referral_fee_rate(&referral, fee_rate, 0, test.ctx()); return_shared(referral); return_shared(pool); }; @@ -10430,7 +10430,7 @@ fun pool_referral_fee_zero() { { let mut pool = test.take_shared_by_id>(pool_id); let referral = test.take_shared_by_id(referral_id); - pool.update_pool_referral_fee_rate(&referral, fee_rate, test.ctx()); + pool.update_pool_referral_fee_rate(&referral, fee_rate, 0, test.ctx()); return_shared(referral); return_shared(pool); }; @@ -10502,7 +10502,7 @@ fun pool_referral_fee_update() { { let mut pool = test.take_shared_by_id>(pool_id); let referral = test.take_shared_by_id(referral_id); - pool.update_pool_referral_fee_rate(&referral, fee_rate_1, test.ctx()); + pool.update_pool_referral_fee_rate(&referral, fee_rate_1, 0, test.ctx()); return_shared(referral); return_shared(pool); }; @@ -10513,7 +10513,7 @@ fun pool_referral_fee_update() { { let mut pool = test.take_shared_by_id>(pool_id); let referral = test.take_shared_by_id(referral_id); - pool.update_pool_referral_fee_rate(&referral, fee_rate_2, test.ctx()); + pool.update_pool_referral_fee_rate(&referral, fee_rate_2, 0, test.ctx()); return_shared(referral); return_shared(pool); }; @@ -10585,7 +10585,7 @@ fun pool_referral_fee_maker_no_fill() { { let mut pool = test.take_shared_by_id>(pool_id); let referral = test.take_shared_by_id(referral_id); - pool.update_pool_referral_fee_rate(&referral, fee_rate, test.ctx()); + pool.update_pool_referral_fee_rate(&referral, fee_rate, 0, test.ctx()); return_shared(referral); return_shared(pool); }; @@ -10663,7 +10663,7 @@ fun pool_referral_fee_maker_partial_fill() { { let mut pool = test.take_shared_by_id>(pool_id); let referral = test.take_shared_by_id(referral_id); - pool.update_pool_referral_fee_rate(&referral, fee_rate, test.ctx()); + pool.update_pool_referral_fee_rate(&referral, fee_rate, 0, test.ctx()); return_shared(referral); return_shared(pool); }; @@ -10748,7 +10748,7 @@ fun pool_referral_fee_max_exceeded() { { let mut pool = test.take_shared_by_id>(pool_id); let referral = test.take_shared_by_id(referral_id); - pool.update_pool_referral_fee_rate(&referral, fee_rate, test.ctx()); + pool.update_pool_referral_fee_rate(&referral, fee_rate, 0, test.ctx()); return_shared(referral); return_shared(pool); }; @@ -10774,7 +10774,7 @@ fun pool_referral_fee_invalid_precision() { { let mut pool = test.take_shared_by_id>(pool_id); let referral = test.take_shared_by_id(referral_id); - pool.update_pool_referral_fee_rate(&referral, fee_rate, test.ctx()); + pool.update_pool_referral_fee_rate(&referral, fee_rate, 0, test.ctx()); return_shared(referral); return_shared(pool); }; @@ -10799,7 +10799,7 @@ fun pool_referral_fee_unauthorized_update() { { let mut pool = test.take_shared_by_id>(pool_id); let referral = test.take_shared_by_id(referral_id); - pool.update_pool_referral_fee_rate(&referral, fee_rate, test.ctx()); + pool.update_pool_referral_fee_rate(&referral, fee_rate, 0, test.ctx()); return_shared(referral); return_shared(pool); }; From 173e8196892cb0731a8ef4aa04b2d8fb00184da1 Mon Sep 17 00:00:00 2001 From: bathord Date: Sun, 15 Mar 2026 12:39:26 +0300 Subject: [PATCH 2/9] Add withdraw referral fee functions to vault --- packages/deepbook/sources/vault/vault.move | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/deepbook/sources/vault/vault.move b/packages/deepbook/sources/vault/vault.move index 84117f31c..3495cdbee 100644 --- a/packages/deepbook/sources/vault/vault.move +++ b/packages/deepbook/sources/vault/vault.move @@ -139,6 +139,27 @@ public(package) fun withdraw_deep_to_burn( self.deep_balance.split(amount_to_burn) } +public(package) fun withdraw_referral_fee_base( + self: &mut Vault, + amount: u64, +): Balance { + self.base_balance.split(amount) +} + +public(package) fun withdraw_referral_fee_quote( + self: &mut Vault, + amount: u64, +): Balance { + self.quote_balance.split(amount) +} + +public(package) fun withdraw_referral_fee_deep( + self: &mut Vault, + amount: u64, +): Balance { + self.deep_balance.split(amount) +} + public(package) fun borrow_flashloan_base( self: &mut Vault, pool_id: ID, From dec54c446e18b6418ada3d17ff1d5cf6347b8cc7 Mon Sep 17 00:00:00 2001 From: bathord Date: Mon, 16 Mar 2026 17:01:00 +0300 Subject: [PATCH 3/9] Add maker referral fees Add MakerReferralInfo & MakerReferralKey. MakerReferralInfo is attached to pool.id as DF when maker referral fee is locked in vault. Add lock_maker_referral_fee function to pre-lock the maker ref. fee. Add process_maker_referral_fills function to process all the fills against maker orders that have pre-locked referral fees. Update cancel_order function to refund pre-locked maker referral fees if any. Update modify_order function to refund a proportional part of the pre-locked maker referral fees if any. Update locked_balance to take into account locked maker referral fees if any. Update place_order_int to process maker referral fills and to pre-lock maker referral fees for the created order. Add credit_account_settled_balances function to state module to refund maker referral fees when orders expire during matching. --- packages/deepbook/sources/pool.move | 342 ++++++++++++++++++--- packages/deepbook/sources/state/state.move | 14 + 2 files changed, 320 insertions(+), 36 deletions(-) diff --git a/packages/deepbook/sources/pool.move b/packages/deepbook/sources/pool.move index b48e4e645..7f495c13d 100644 --- a/packages/deepbook/sources/pool.move +++ b/packages/deepbook/sources/pool.move @@ -117,6 +117,20 @@ public struct ReferralFeeConfig has store { /// The key for the referral fee configuration. public struct ReferralFeeConfigKey(ID) has copy, drop, store; +/// Tracks a maker order's pre-locked referral fee. Stored as a dynamic field +/// on Pool keyed by MakerReferralKey(order_id). Created at order placement, +/// consumed on fill/cancel/modify/expiry. +public struct MakerReferralInfo has copy, drop, store { + referral_id: ID, + effective_rate: u64, + locked_balance: u64, + fee_is_deep: bool, + is_bid: bool, +} + +/// Dynamic field key wrapping an order_id (u128) for MakerReferralInfo lookup. +public struct MakerReferralKey(u128) has copy, drop, store; + public struct ReferralRewards has store { multiplier: u64, base: Balance, @@ -516,28 +530,57 @@ public fun modify_order( ) { let previous_quantity = self.get_order(order_id).quantity(); - let self = self.load_inner_mut(); - let (cancel_quantity, order) = self - .book - .modify_order(order_id, new_quantity, clock.timestamp_ms()); - assert!(order.balance_manager_id() == balance_manager.id(), EInvalidOrderBalanceManager); - let (settled, owed) = self - .state - .process_modify( - balance_manager.id(), + // Phase 1: Modify in book, process state, emit event, capture values for Phase 2 + let (mut settled, owed, cancel_quantity, order_price, order_deep_price) = { + let inner = self.load_inner_mut(); + let (cancel_quantity, order) = inner + .book + .modify_order(order_id, new_quantity, clock.timestamp_ms()); + assert!( + order.balance_manager_id() == balance_manager.id(), + EInvalidOrderBalanceManager, + ); + let (settled, owed) = inner + .state + .process_modify( + balance_manager.id(), + cancel_quantity, + order, + inner.pool_id, + ctx, + ); + let order_price = order.price(); + let order_deep_price = *order.order_deep_price(); + order.emit_order_modified( + inner.pool_id, + previous_quantity, + ctx.sender(), + clock.timestamp_ms(), + ); + (settled, owed, cancel_quantity, order_price, order_deep_price) + }; + + // Phase 2: Calculate partial maker referral refund + let key = MakerReferralKey(order_id); + if (self.id.exists_(key)) { + let info: &mut MakerReferralInfo = self.id.borrow_mut(key); + let mut fee_balances = order_deep_price.fee_quantity( cancel_quantity, - order, - self.pool_id, - ctx, + math::mul(cancel_quantity, order_price), + info.is_bid, ); - self.vault.settle_balance_manager(settled, owed, balance_manager, trade_proof); + fee_balances.mul(info.effective_rate); + let refund = fee_balances.non_zero_value().min(info.locked_balance); + info.locked_balance = info.locked_balance - refund; - order.emit_order_modified( - self.pool_id, - previous_quantity, - ctx.sender(), - clock.timestamp_ms(), - ); + if (info.fee_is_deep) settled.add_deep(refund) + else if (info.is_bid) settled.add_quote(refund) + else settled.add_base(refund); + }; + + // Phase 3: Settle vault (returns protocol refund + referral refund to maker) + let inner = self.load_inner_mut(); + inner.vault.settle_balance_manager(settled, owed, balance_manager, trade_proof); } /// Cancel an order. The order must be owned by the balance_manager. @@ -553,19 +596,33 @@ public fun cancel_order( clock: &Clock, ctx: &TxContext, ) { - let self = self.load_inner_mut(); - let mut order = self.book.cancel_order(order_id); - assert!(order.balance_manager_id() == balance_manager.id(), EInvalidOrderBalanceManager); - let (settled, owed) = self - .state - .process_cancel(&mut order, balance_manager.id(), self.pool_id, ctx); - self.vault.settle_balance_manager(settled, owed, balance_manager, trade_proof); + // Phase 1: Cancel in book, process state, emit event + let (mut settled, owed) = { + let inner = self.load_inner_mut(); + let mut order = inner.book.cancel_order(order_id); + assert!( + order.balance_manager_id() == balance_manager.id(), + EInvalidOrderBalanceManager, + ); + let (settled, owed) = inner + .state + .process_cancel(&mut order, balance_manager.id(), inner.pool_id, ctx); + order.emit_order_canceled(inner.pool_id, ctx.sender(), clock.timestamp_ms()); + (settled, owed) + }; - order.emit_order_canceled( - self.pool_id, - ctx.sender(), - clock.timestamp_ms(), - ); + // Phase 2: Inflate settled with maker referral refund if applicable + let key = MakerReferralKey(order_id); + if (self.id.exists_(key)) { + let info: MakerReferralInfo = df::remove(&mut self.id, key); + if (info.fee_is_deep) settled.add_deep(info.locked_balance) + else if (info.is_bid) settled.add_quote(info.locked_balance) + else settled.add_base(info.locked_balance); + }; + + // Phase 3: Settle vault (returns protocol refund + referral refund to maker) + let inner = self.load_inner_mut(); + inner.vault.settle_balance_manager(settled, owed, balance_manager, trade_proof); } /// Cancel multiple orders within a vector. The orders must be owned by the @@ -1486,15 +1543,17 @@ public fun get_order_deep_required( (math::mul(taker_fee, deep_quantity), math::mul(maker_fee, deep_quantity)) } -/// Returns the locked balance for the balance_manager in the pool +/// Returns the locked balance for the balance_manager in the pool. +/// Includes protocol-locked amounts, maker referral-locked amounts, +/// and settled balances. /// Returns (base_quantity, quote_quantity, deep_quantity) public fun locked_balance( self: &Pool, balance_manager: &BalanceManager, ): (u64, u64, u64) { let account_orders = self.get_account_order_details(balance_manager); - let self = self.load_inner(); - if (!self.state.account_exists(balance_manager.id())) { + let inner = self.load_inner(); + if (!inner.state.account_exists(balance_manager.id())) { return (0, 0, 0) }; @@ -1503,14 +1562,26 @@ public fun locked_balance( let mut deep_quantity = 0; account_orders.do_ref!(|order| { - let maker_fee = self.state.history().historic_maker_fee(order.epoch()); + let maker_fee = inner.state.history().historic_maker_fee(order.epoch()); 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 key = MakerReferralKey(order.order_id()); + if (self.id.exists_(key)) { + let info: &MakerReferralInfo = self.id.borrow(key); + if (info.fee_is_deep) { + deep_quantity = deep_quantity + info.locked_balance; + } else if (info.is_bid) { + quote_quantity = quote_quantity + info.locked_balance; + } else { + base_quantity = base_quantity + info.locked_balance; + }; + }; }); - let settled_balances = self.state.account(balance_manager.id()).settled_balances(); + let settled_balances = inner.state.account(balance_manager.id()).settled_balances(); base_quantity = base_quantity + settled_balances.base(); quote_quantity = quote_quantity + settled_balances.quote(); deep_quantity = deep_quantity + settled_balances.deep(); @@ -1964,6 +2035,17 @@ fun place_order_int( trade_proof, ); + self.process_maker_referral_fills( + &order_info, + ctx, + ); + + self.lock_maker_referral_fee( + &order_info, + balance_manager, + trade_proof, + ); + order_info } @@ -2043,6 +2125,194 @@ fun process_referral_fees( }; } +/// Pre-lock the maker referral fee into the Vault when a limit order rests +/// in the book. The fee is calculated from the effective rate (volume-based + +/// multiplier on protocol maker fee) and the order's remaining quantity. +fun lock_maker_referral_fee( + self: &mut Pool, + order_info: &OrderInfo, + balance_manager: &mut BalanceManager, + trade_proof: &TradeProof, +) { + if (!order_info.order_inserted()) return; + + let referral_id = balance_manager.get_balance_manager_referral_id(self.id()); + if (referral_id.is_none()) return; + let referral_id = referral_id.destroy_some(); + + let maker_fee_rate = if (self.id.exists_(ReferralFeeConfigKey(referral_id))) { + let config: &ReferralFeeConfig = self.id.borrow(ReferralFeeConfigKey(referral_id)); + config.maker_fee_rate + } else { + 0 + }; + + let referral_rewards: &ReferralRewards = self + .id + .borrow(referral_id); + let multiplier = referral_rewards.multiplier; + + let protocol_maker_fee = { + let inner = self.load_inner(); + inner.state.governance().trade_params().maker_fee() + }; + + let effective_rate = maker_fee_rate + math::mul(protocol_maker_fee, multiplier); + if (effective_rate == 0) return; + + let remaining_qty = order_info.remaining_quantity(); + let price = order_info.price(); + let is_bid = order_info.is_bid(); + let fee_is_deep = order_info.fee_is_deep(); + let order_deep_price = order_info.order_deep_price(); + + let mut fee_balances = order_deep_price.fee_quantity( + remaining_qty, + math::mul(remaining_qty, price), + is_bid, + ); + fee_balances.mul(effective_rate); + let locked_amount = fee_balances.non_zero_value(); + if (locked_amount == 0) return; + + let referral_owed = if (fee_is_deep) { + balances::new(0, 0, locked_amount) + } else if (is_bid) { + balances::new(0, locked_amount, 0) + } else { + balances::new(locked_amount, 0, 0) + }; + + { + let inner = self.load_inner_mut(); + inner.vault.settle_balance_manager( + balances::empty(), + referral_owed, + balance_manager, + trade_proof, + ); + }; + + self.id.add( + MakerReferralKey(order_info.order_id()), + MakerReferralInfo { + referral_id, + effective_rate, + locked_balance: locked_amount, + fee_is_deep, + is_bid, + }, + ); +} + +/// Process fills against maker orders that have pre-locked referral fees. +/// For each fill: transfer the proportional fee from Vault to ReferralRewards, +/// handle completed orders (sweep dust), and refund expired orders to the +/// maker's settled_balances. +fun process_maker_referral_fills( + self: &mut Pool, + order_info: &OrderInfo, + ctx: &TxContext, +) { + let fills = order_info.fills(); + let num_fills = fills.length(); + if (num_fills == 0) return; + + let pool_id = self.id(); + let mut i = 0; + while (i < num_fills) { + let fill = &fills[i]; + let key = MakerReferralKey(fill.maker_order_id()); + + if (self.id.exists_(key)) { + let info: MakerReferralInfo = *self.id.borrow(key); + + if (fill.expired()) { + let refund = if (info.fee_is_deep) { + balances::new(0, 0, info.locked_balance) + } else if (info.is_bid) { + balances::new(0, info.locked_balance, 0) + } else { + balances::new(info.locked_balance, 0, 0) + }; + { + let inner = self.load_inner_mut(); + inner.state.credit_account_settled_balances( + fill.balance_manager_id(), + refund, + ctx, + ); + }; + df::remove(&mut self.id, key); + } else { + let mut fee_balances = fill.maker_deep_price().fee_quantity( + fill.base_quantity(), + fill.quote_quantity(), + info.is_bid, + ); + fee_balances.mul(info.effective_rate); + let transfer_amount = if (fill.completed()) { + info.locked_balance + } else { + fee_balances.non_zero_value().min(info.locked_balance) + }; + + if (transfer_amount > 0) { + if (info.fee_is_deep) { + let bal = { + let inner = self.load_inner_mut(); + inner.vault.withdraw_referral_fee_deep(transfer_amount) + }; + let rewards: &mut ReferralRewards = + self.id.borrow_mut(info.referral_id); + rewards.deep.join(bal); + } else if (info.is_bid) { + let bal = { + let inner = self.load_inner_mut(); + inner.vault.withdraw_referral_fee_quote(transfer_amount) + }; + let rewards: &mut ReferralRewards = + self.id.borrow_mut(info.referral_id); + rewards.quote.join(bal); + } else { + let bal = { + let inner = self.load_inner_mut(); + inner.vault.withdraw_referral_fee_base(transfer_amount) + }; + let rewards: &mut ReferralRewards = + self.id.borrow_mut(info.referral_id); + rewards.base.join(bal); + }; + + let (base_fee, quote_fee, deep_fee) = if (info.fee_is_deep) { + (0, 0, transfer_amount) + } else if (info.is_bid) { + (0, transfer_amount, 0) + } else { + (transfer_amount, 0, 0) + }; + event::emit(ReferralFeeEvent { + pool_id, + referral_id: info.referral_id, + base_fee, + quote_fee, + deep_fee, + }); + }; + + if (fill.completed()) { + df::remove(&mut self.id, key); + } else if (transfer_amount > 0) { + let info_mut: &mut MakerReferralInfo = self.id.borrow_mut(key); + info_mut.locked_balance = info_mut.locked_balance - transfer_amount; + }; + }; + }; + + i = i + 1; + }; +} + fun update_ewma_state( self: &mut Pool, clock: &Clock, diff --git a/packages/deepbook/sources/state/state.move b/packages/deepbook/sources/state/state.move index 3b90fe486..6d1ddd54f 100644 --- a/packages/deepbook/sources/state/state.move +++ b/packages/deepbook/sources/state/state.move @@ -433,6 +433,20 @@ public(package) fun history(self: &State): &History { &self.history } +/// Credit settled balances to an account by balance_manager_id. +/// Used to refund maker referral fees when orders expire during matching +/// (maker's BalanceManager is unavailable at that point). +public(package) fun credit_account_settled_balances( + self: &mut State, + balance_manager_id: ID, + balances: Balances, + ctx: &TxContext, +) { + self.update_account(balance_manager_id, ctx); + let account = &mut self.accounts[balance_manager_id]; + account.add_settled_balances(balances); +} + // === Private Functions === /// Process fills for all makers. Update maker accounts and history. fun process_fills(self: &mut State, fills: &mut vector, ctx: &TxContext) { From 54c8d35c4f47bb0456b9ec83d5d49d40f56b9315 Mon Sep 17 00:00:00 2001 From: bathord Date: Mon, 16 Mar 2026 18:25:53 +0300 Subject: [PATCH 4/9] Add maker referral tests --- .../deepbook/tests/maker_referral_tests.move | 2485 +++++++++++++++++ 1 file changed, 2485 insertions(+) create mode 100644 packages/deepbook/tests/maker_referral_tests.move diff --git a/packages/deepbook/tests/maker_referral_tests.move b/packages/deepbook/tests/maker_referral_tests.move new file mode 100644 index 000000000..dc3b12718 --- /dev/null +++ b/packages/deepbook/tests/maker_referral_tests.move @@ -0,0 +1,2485 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[test_only] +module deepbook::maker_referral_tests; + +use deepbook::{ + balance_manager::{BalanceManager, TradeCap, DeepBookPoolReferral}, + balance_manager_tests::{ + USDC, + create_acct_and_share_with_funds_typed, + asset_balance + }, + constants, + math, + pool::Pool, + pool_tests::{ + setup_test, + setup_pool_with_default_fees, + setup_pool_with_default_fees_and_reference_pool, + place_limit_order, + place_market_order, + cancel_order, + modify_order, + set_time, + }, +}; +use std::unit_test::assert_eq; +use sui::{ + clock::Clock, + sui::SUI, + test_scenario::{Scenario, begin, end, return_shared} +}; +use token::deep::DEEP; + +const OWNER: address = @0x1; +const ALICE: address = @0xAAAA; +const BOB: address = @0xBBBB; +const CAROL: address = @0xCCCC; + +// === Group 1: Basic Placement and Fill (DEEP fees) === + +#[test] +fun maker_referral_full_fill_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + // Mint referral with 0 multiplier, set maker_fee_rate = 10 bps + let referral_id; + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + referral_id = pool.mint_referral(0, test.ctx()); + return_shared(pool); + }; + + let maker_fee_rate = 1_000_000; // 10 bps + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + pool.update_pool_referral_fee_rate(&referral, 0, maker_fee_rate, test.ctx()); + return_shared(referral); + return_shared(pool); + }; + + // Set referral on BOB's balance manager (BOB is the maker) + test.next_tx(BOB); + { + let mut balance_manager = test.take_shared_by_id(balance_manager_id_bob); + let referral = test.take_shared_by_id(referral_id); + let trade_cap = balance_manager.mint_trade_cap(test.ctx()); + balance_manager.set_balance_manager_referral(&referral, &trade_cap); + transfer::public_transfer(trade_cap, BOB); + return_shared(referral); + return_shared(balance_manager); + }; + + // BOB (maker) places ask limit order at $3 for 100 SUI (rests in book, pay_with_deep=true) + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + let bob_deep_before = asset_balance(BOB, balance_manager_id_bob, &mut test); + + let order_info = place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, // ask + true, // pay_with_deep + constants::max_u64(), + &mut test, + ); + assert!(order_info.order_inserted()); + + let _bob_deep_after_placement = asset_balance(BOB, balance_manager_id_bob, &mut test); + + // ALICE (taker) places bid market order filling BOB's ask completely + place_market_order( + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + quantity, + true, // bid + true, // pay_with_deep + &mut test, + ); + + // Check referral rewards + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + + // effective_rate = maker_fee_rate (1_000_000) + mul(protocol_maker_fee, 0) = 1_000_000 + // For ask with DEEP: fee_quantity returns DEEP + // deep_per_asset (SUI is base) ≈ 100 * 1e9 (DEEP_MULTIPLIER) + // fee_balances.deep = mul(100 * 1e9, 100 * 1e9) = 100 * 100 * 1e9 = 10_000 * 1e9 + // After mul(effective_rate=1_000_000): mul(10_000e9, 1_000_000) = 10_000_000_000 (10 DEEP) + let expected_deep = math::mul( + math::mul(quantity, constants::deep_multiplier()), + maker_fee_rate, + ); + assert_eq!(deep, expected_deep); + assert_eq!(base, 0); + assert_eq!(quote, 0); + + return_shared(referral); + return_shared(pool); + }; + + // Verify the maker's DEEP was reduced by the referral lock (which was fully transferred to rewards) + let bob_deep_after_fill = asset_balance(BOB, balance_manager_id_bob, &mut test); + let expected_lock = math::mul( + math::mul(quantity, constants::deep_multiplier()), + maker_fee_rate, + ); + // Bob's DEEP decreased by protocol maker fee + referral lock at placement, + // then protocol fee was refunded on fill (it goes to settled), but referral lock is consumed. + // The net difference from before placement to after fill should include the referral amount. + assert!(bob_deep_before > bob_deep_after_fill); + // Referral lock = expected_lock should equal the deep rewards + assert_eq!(expected_lock, math::mul( + math::mul(quantity, constants::deep_multiplier()), + maker_fee_rate, + )); + + end(test); +} + +#[test] +fun maker_referral_partial_fill_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let referral_id; + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + referral_id = pool.mint_referral(0, test.ctx()); + return_shared(pool); + }; + + let maker_fee_rate = 1_000_000; // 10 bps + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + pool.update_pool_referral_fee_rate(&referral, 0, maker_fee_rate, test.ctx()); + return_shared(referral); + return_shared(pool); + }; + + test.next_tx(BOB); + { + let mut balance_manager = test.take_shared_by_id(balance_manager_id_bob); + let referral = test.take_shared_by_id(referral_id); + let trade_cap = balance_manager.mint_trade_cap(test.ctx()); + balance_manager.set_balance_manager_referral(&referral, &trade_cap); + transfer::public_transfer(trade_cap, BOB); + return_shared(referral); + return_shared(balance_manager); + }; + + // BOB places ask limit for 1000 SUI at $3 + let price = 3 * constants::float_scaling(); + let total_quantity = 1000 * constants::float_scaling(); + + place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + total_quantity, + false, // ask + true, // pay_with_deep + constants::max_u64(), + &mut test, + ); + + // ALICE fills only 400 + let fill_quantity = 400 * constants::float_scaling(); + place_market_order( + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + fill_quantity, + true, // bid + true, // pay_with_deep + &mut test, + ); + + // Check referral rewards (should be for 400 qty only) + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + + let expected_deep = math::mul( + math::mul(fill_quantity, constants::deep_multiplier()), + maker_fee_rate, + ); + assert_eq!(deep, expected_deep); + assert_eq!(base, 0); + assert_eq!(quote, 0); + + // Check locked_balance includes remaining referral lock for 600 qty + let balance_manager = test.take_shared_by_id(balance_manager_id_bob); + let (_, _, deep_locked) = pool.locked_balance(&balance_manager); + // Should have remaining lock for 600 qty + let remaining_qty = total_quantity - fill_quantity; + let remaining_referral_lock = math::mul( + math::mul(remaining_qty, constants::deep_multiplier()), + maker_fee_rate, + ); + assert!(deep_locked >= remaining_referral_lock); + return_shared(balance_manager); + return_shared(referral); + return_shared(pool); + }; + + end(test); +} + +#[test] +fun maker_referral_multiple_partial_fills_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let balance_manager_id_carol = create_acct_and_share_with_funds_typed( + CAROL, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let referral_id; + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + referral_id = pool.mint_referral(0, test.ctx()); + return_shared(pool); + }; + + let maker_fee_rate = 1_000_000; + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + pool.update_pool_referral_fee_rate(&referral, 0, maker_fee_rate, test.ctx()); + return_shared(referral); + return_shared(pool); + }; + + test.next_tx(BOB); + { + let mut balance_manager = test.take_shared_by_id(balance_manager_id_bob); + let referral = test.take_shared_by_id(referral_id); + let trade_cap = balance_manager.mint_trade_cap(test.ctx()); + balance_manager.set_balance_manager_referral(&referral, &trade_cap); + transfer::public_transfer(trade_cap, BOB); + return_shared(referral); + return_shared(balance_manager); + }; + + // BOB places ask for 1000 SUI at $3 + let price = 3 * constants::float_scaling(); + let total_quantity = 1000 * constants::float_scaling(); + + place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + total_quantity, + false, + true, + constants::max_u64(), + &mut test, + ); + + // Fill 1: ALICE buys 300 + place_market_order( + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + 300 * constants::float_scaling(), + true, + true, + &mut test, + ); + + // Fill 2: CAROL buys 300 + place_market_order( + CAROL, + pool_id, + balance_manager_id_carol, + 3, + constants::self_matching_allowed(), + 300 * constants::float_scaling(), + true, + true, + &mut test, + ); + + // Fill 3: ALICE buys remaining 400 (completes order) + place_market_order( + ALICE, + pool_id, + balance_manager_id_alice, + 4, + constants::self_matching_allowed(), + 400 * constants::float_scaling(), + true, + true, + &mut test, + ); + + // Check referral rewards - should be for the full 1000 qty (dust sweep on completion) + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + + // On completion (fill.completed()=true), transfer_amount = info.locked_balance (entire remaining) + // So total rewards should equal the original locked amount + let expected_total_deep = math::mul( + math::mul(total_quantity, constants::deep_multiplier()), + maker_fee_rate, + ); + assert_eq!(deep, expected_total_deep); + assert_eq!(base, 0); + assert_eq!(quote, 0); + + return_shared(referral); + return_shared(pool); + }; + + end(test); +} + +#[test] +fun maker_referral_bid_order_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let referral_id; + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + referral_id = pool.mint_referral(0, test.ctx()); + return_shared(pool); + }; + + let maker_fee_rate = 1_000_000; + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + pool.update_pool_referral_fee_rate(&referral, 0, maker_fee_rate, test.ctx()); + return_shared(referral); + return_shared(pool); + }; + + test.next_tx(BOB); + { + let mut balance_manager = test.take_shared_by_id(balance_manager_id_bob); + let referral = test.take_shared_by_id(referral_id); + let trade_cap = balance_manager.mint_trade_cap(test.ctx()); + balance_manager.set_balance_manager_referral(&referral, &trade_cap); + transfer::public_transfer(trade_cap, BOB); + return_shared(referral); + return_shared(balance_manager); + }; + + // BOB (maker) places bid limit at $1 for 100 SUI (below current best ask at $2, so it rests) + // Actually, setup_pool creates an empty pool, no pre-existing orders. + // We need to ensure the order rests: place bid below any existing ask. + // Since there are no existing asks, any bid will rest. + let price = 2 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + let order_info = place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + true, // bid + true, // pay_with_deep + constants::max_u64(), + &mut test, + ); + assert!(order_info.order_inserted()); + + // ALICE places ask market order to fill BOB's bid + place_market_order( + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + quantity, + false, // ask + true, // pay_with_deep + &mut test, + ); + + // Check referral rewards + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + + // For bid order with DEEP: fee_quantity calculates DEEP based on deep_per_asset + // deep_per_asset is for SUI (base), so deep = mul(base_qty, deep_per_asset) + // = mul(100e9, 100e9) = 10_000e9 + // After mul with effective_rate (1_000_000): mul(10_000e9, 1_000_000) = 10e9 = 10 DEEP + let expected_deep = math::mul( + math::mul(quantity, constants::deep_multiplier()), + maker_fee_rate, + ); + assert_eq!(deep, expected_deep); + assert_eq!(base, 0); + assert_eq!(quote, 0); + + return_shared(referral); + return_shared(pool); + }; + + end(test); +} + +// === Group 2: Cancellation === + +#[test] +fun maker_referral_cancel_full_refund_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let (referral_id, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, // multiplier + 1_000_000, // maker_fee_rate = 10 bps + &mut test, + ); + + let bob_deep_before = asset_balance(BOB, balance_manager_id_bob, &mut test); + + // BOB places ask, then cancels before any fill + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + let order_info = place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, + ); + let order_id = order_info.order_id(); + + cancel_order(BOB, pool_id, balance_manager_id_bob, order_id, &mut test); + + // Referral rewards should be 0 + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + assert_eq!(base, 0); + assert_eq!(quote, 0); + assert_eq!(deep, 0); + return_shared(referral); + return_shared(pool); + }; + + // BOB's DEEP balance should be restored to original + let bob_deep_after = asset_balance(BOB, balance_manager_id_bob, &mut test); + assert_eq!(bob_deep_before, bob_deep_after); + + end(test); +} + +#[test] +fun maker_referral_partial_fill_then_cancel_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; + let (referral_id, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + // BOB places ask for 1000 SUI at $3 + let price = 3 * constants::float_scaling(); + let total_quantity = 1000 * constants::float_scaling(); + + let order_info = place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + total_quantity, + false, + true, + constants::max_u64(), + &mut test, + ); + let order_id = order_info.order_id(); + + // ALICE fills 400 + let fill_quantity = 400 * constants::float_scaling(); + place_market_order( + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + fill_quantity, + true, + true, + &mut test, + ); + + // BOB cancels remaining 600 + cancel_order(BOB, pool_id, balance_manager_id_bob, order_id, &mut test); + + // Referral rewards = fee for 400 qty + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + + let expected_deep_for_400 = math::mul( + math::mul(fill_quantity, constants::deep_multiplier()), + maker_fee_rate, + ); + assert_eq!(deep, expected_deep_for_400); + assert_eq!(base, 0); + assert_eq!(quote, 0); + + return_shared(referral); + return_shared(pool); + }; + + end(test); +} + +#[test] +fun maker_referral_cancel_all_orders_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let (referral_id, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + 1_000_000, + &mut test, + ); + + let bob_deep_before = asset_balance(BOB, balance_manager_id_bob, &mut test); + + // BOB places two orders + let price1 = 3 * constants::float_scaling(); + let price2 = 4 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + place_limit_order( + BOB, pool_id, balance_manager_id_bob, 1, + constants::no_restriction(), constants::self_matching_allowed(), + price1, quantity, false, true, constants::max_u64(), &mut test, + ); + place_limit_order( + BOB, pool_id, balance_manager_id_bob, 2, + constants::no_restriction(), constants::self_matching_allowed(), + price2, quantity, false, true, constants::max_u64(), &mut test, + ); + + cancel_all_orders(pool_id, BOB, balance_manager_id_bob, &mut test); + + // Referral rewards should be 0 + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + assert_eq!(base, 0); + assert_eq!(quote, 0); + assert_eq!(deep, 0); + return_shared(referral); + return_shared(pool); + }; + + // BOB's DEEP should be restored + let bob_deep_after = asset_balance(BOB, balance_manager_id_bob, &mut test); + assert_eq!(bob_deep_before, bob_deep_after); + + end(test); +} + +// === Helpers === + +fun setup_maker_referral( + pool_id: ID, + maker_balance_manager_id: ID, + multiplier: u64, + maker_fee_rate: u64, + test: &mut Scenario, +): (ID, u64) { + let referral_id; + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + referral_id = pool.mint_referral(multiplier, test.ctx()); + return_shared(pool); + }; + + if (maker_fee_rate > 0) { + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + pool.update_pool_referral_fee_rate(&referral, 0, maker_fee_rate, test.ctx()); + return_shared(referral); + return_shared(pool); + }; + }; + + test.next_tx(BOB); + { + let mut balance_manager = test.take_shared_by_id(maker_balance_manager_id); + let referral = test.take_shared_by_id(referral_id); + let trade_cap = test.take_from_sender(); + balance_manager.set_balance_manager_referral(&referral, &trade_cap); + test.return_to_sender(trade_cap); + return_shared(referral); + return_shared(balance_manager); + }; + + (referral_id, maker_fee_rate) +} + +fun cancel_all_orders( + pool_id: ID, + owner: address, + balance_manager_id: ID, + test: &mut Scenario, +) { + test.next_tx(owner); + { + let mut pool = test.take_shared_by_id>(pool_id); + let clock = test.take_shared(); + let mut balance_manager = test.take_shared_by_id(balance_manager_id); + let trade_proof = balance_manager.generate_proof_as_owner(test.ctx()); + pool.cancel_all_orders( + &mut balance_manager, + &trade_proof, + &clock, + test.ctx(), + ); + return_shared(pool); + return_shared(clock); + return_shared(balance_manager); + } +} + +// === Group 3: Modification === + +#[test] +fun maker_referral_modify_reduces_quantity_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; + let (referral_id, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + // BOB places ask for 1000 SUI at $3 + let price = 3 * constants::float_scaling(); + let original_quantity = 1000 * constants::float_scaling(); + + let order_info = place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + original_quantity, + false, + true, + constants::max_u64(), + &mut test, + ); + let order_id = order_info.order_id(); + + let bob_deep_after_placement = asset_balance(BOB, balance_manager_id_bob, &mut test); + + // Modify to 600 SUI (cancel 400) + let new_quantity = 600 * constants::float_scaling(); + modify_order( + BOB, + pool_id, + balance_manager_id_bob, + order_id, + new_quantity, + &mut test, + ); + + let bob_deep_after_modify = asset_balance(BOB, balance_manager_id_bob, &mut test); + + // BOB receives both: protocol maker fee refund + referral refund for the cancelled 400 qty + let cancel_quantity = original_quantity - new_quantity; + let referral_refund = math::mul( + math::mul(cancel_quantity, constants::deep_multiplier()), + maker_fee_rate, + ); + let protocol_maker_fee_refund = math::mul( + math::mul(cancel_quantity, constants::deep_multiplier()), + constants::maker_fee(), + ); + assert_eq!( + bob_deep_after_modify, + bob_deep_after_placement + referral_refund + protocol_maker_fee_refund, + ); + + // Check locked_balance reflects reduced lock for 600 qty + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let balance_manager = test.take_shared_by_id(balance_manager_id_bob); + let (_, _, deep_locked) = pool.locked_balance(&balance_manager); + let remaining_referral_lock = math::mul( + math::mul(new_quantity, constants::deep_multiplier()), + maker_fee_rate, + ); + assert!(deep_locked >= remaining_referral_lock); + return_shared(balance_manager); + return_shared(pool); + }; + + // Referral rewards should be 0 (no fills happened) + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + assert_eq!(base, 0); + assert_eq!(quote, 0); + assert_eq!(deep, 0); + return_shared(referral); + return_shared(pool); + }; + + end(test); +} + +#[test] +fun maker_referral_modify_then_fill_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; + let (referral_id, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + // BOB places ask for 1000 SUI at $3 + let price = 3 * constants::float_scaling(); + let original_quantity = 1000 * constants::float_scaling(); + + let order_info = place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + original_quantity, + false, + true, + constants::max_u64(), + &mut test, + ); + let order_id = order_info.order_id(); + + // Modify to 500 SUI + let new_quantity = 500 * constants::float_scaling(); + modify_order( + BOB, + pool_id, + balance_manager_id_bob, + order_id, + new_quantity, + &mut test, + ); + + // ALICE fills remaining 500 (completes the order) + place_market_order( + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + new_quantity, + true, + true, + &mut test, + ); + + // Referral rewards = fee for 500 qty (the entire remaining lock, dust-swept on completion) + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + + // After modify, locked_balance was reduced to cover 500 qty + let expected_deep = math::mul( + math::mul(new_quantity, constants::deep_multiplier()), + maker_fee_rate, + ); + assert_eq!(deep, expected_deep); + assert_eq!(base, 0); + assert_eq!(quote, 0); + + return_shared(referral); + return_shared(pool); + }; + + end(test); +} + +// === Group 4: Order Expiry === + +#[test] +fun maker_referral_expired_order_refund_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; + let (referral_id, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + // BOB places ask at $3 for 100 SUI with a near-future expiry + // set_time adds 1_000_000 to the given value + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + let expire_timestamp = 1_000_000 + 200; // just slightly after the clock time set by set_time(0) + + let order_info = place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + expire_timestamp, + &mut test, + ); + assert!(order_info.order_inserted()); + + // Advance time past expiry + set_time(500, &mut test); + + // ALICE places a market order to trigger matching and expire BOB's order + place_market_order( + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + quantity, + true, + true, + &mut test, + ); + + // Referral rewards should be 0 (order expired, no fill) + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + assert_eq!(base, 0); + assert_eq!(quote, 0); + assert_eq!(deep, 0); + return_shared(referral); + return_shared(pool); + }; + + // BOB should have the referral lock refunded to settled balances + // Withdraw settled amounts first + withdraw_settled_amounts(pool_id, BOB, balance_manager_id_bob, &mut test); + + end(test); +} + +fun withdraw_settled_amounts( + pool_id: ID, + owner: address, + balance_manager_id: ID, + test: &mut Scenario, +) { + test.next_tx(owner); + { + let mut pool = test.take_shared_by_id>(pool_id); + let mut balance_manager = test.take_shared_by_id(balance_manager_id); + let trade_proof = balance_manager.generate_proof_as_owner(test.ctx()); + pool.withdraw_settled_amounts(&mut balance_manager, &trade_proof); + return_shared(pool); + return_shared(balance_manager); + } +} + +/// Helper to set up maker referral for whitelisted pool scenarios. +/// Since the referral belongs to the pool, we need a separate helper for whitelisted pools +/// where ALICE mints referral, sets rate, and the maker sets it on their balance manager. +fun setup_maker_referral_for_address( + pool_id: ID, + maker_address: address, + maker_balance_manager_id: ID, + multiplier: u64, + maker_fee_rate: u64, + test: &mut Scenario, +): (ID, u64) { + let referral_id; + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + referral_id = pool.mint_referral(multiplier, test.ctx()); + return_shared(pool); + }; + + if (maker_fee_rate > 0) { + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + pool.update_pool_referral_fee_rate(&referral, 0, maker_fee_rate, test.ctx()); + return_shared(referral); + return_shared(pool); + }; + }; + + test.next_tx(maker_address); + { + let mut balance_manager = test.take_shared_by_id(maker_balance_manager_id); + let referral = test.take_shared_by_id(referral_id); + let trade_cap = test.take_from_sender(); + balance_manager.set_balance_manager_referral(&referral, &trade_cap); + test.return_to_sender(trade_cap); + return_shared(referral); + return_shared(balance_manager); + }; + + (referral_id, maker_fee_rate) +} + +// === Group 5: Non-DEEP Fee Assets === + +#[test] +fun maker_referral_ask_non_deep_base_fee_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + + // Whitelisted pool (fees in base/quote, not DEEP) + let pool_id = setup_pool_with_default_fees( + OWNER, + registry_id, + true, // whitelisted + false, // not stable + &mut test, + ); + + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; // 10 bps + let (referral_id, _) = setup_maker_referral_for_address( + pool_id, + BOB, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + // BOB places ask at $3 for 100 SUI (non-DEEP, whitelisted pool) + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + let order_info = place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, // ask + false, // pay_with_deep = false + constants::max_u64(), + &mut test, + ); + assert!(order_info.order_inserted()); + + // ALICE fills BOB's ask + place_market_order( + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + quantity, + true, // bid + false, // pay_with_deep = false + &mut test, + ); + + // Check referral rewards - should be in BASE (SUI) for asks + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + + // For ask, non-DEEP: fee_quantity returns base = mul(base_qty, fee_penalty_multiplier) + // = mul(100e9, 1.25e9) = 125e9 + // After mul with effective_rate (1_000_000): mul(125e9, 1_000_000) = 125_000_000 + let expected_base = math::mul( + math::mul(quantity, constants::fee_penalty_multiplier()), + maker_fee_rate, + ); + assert!(base > 0); + assert_eq!(base, expected_base); + assert_eq!(quote, 0); + assert_eq!(deep, 0); + + return_shared(referral); + return_shared(pool); + }; + + end(test); +} + +#[test] +fun maker_referral_bid_non_deep_quote_fee_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + + let pool_id = setup_pool_with_default_fees( + OWNER, + registry_id, + true, // whitelisted + false, + &mut test, + ); + + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; + let (referral_id, _) = setup_maker_referral_for_address( + pool_id, + BOB, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + // BOB places bid at $2 for 100 SUI + let price = 2 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + let order_info = place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + true, // bid + false, // pay_with_deep = false + constants::max_u64(), + &mut test, + ); + assert!(order_info.order_inserted()); + + // ALICE fills BOB's bid + place_market_order( + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + quantity, + false, // ask + false, // pay_with_deep = false + &mut test, + ); + + // Check referral rewards - should be in QUOTE (USDC) for bids + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + + // For bid, non-DEEP: fee_quantity returns quote = mul(quote_qty, fee_penalty_multiplier) + // quote_qty = mul(100e9, 2e9) = 200e9 + // mul(200e9, 1.25e9) = 250e9 + // After mul with effective_rate: mul(250e9, 1_000_000) = 250_000_000 + let quote_quantity = math::mul(quantity, price); + let expected_quote = math::mul( + math::mul(quote_quantity, constants::fee_penalty_multiplier()), + maker_fee_rate, + ); + assert!(quote > 0); + assert_eq!(quote, expected_quote); + assert_eq!(base, 0); + assert_eq!(deep, 0); + + return_shared(referral); + return_shared(pool); + }; + + end(test); +} + +#[test] +fun maker_referral_non_deep_cancel_refund_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + + let pool_id = setup_pool_with_default_fees( + OWNER, + registry_id, + true, + false, + &mut test, + ); + + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; + let (referral_id, _) = setup_maker_referral_for_address( + pool_id, + BOB, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + let bob_sui_before = asset_balance(BOB, balance_manager_id_bob, &mut test); + + // BOB places ask at $3 for 100 SUI (non-DEEP), then cancels + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + let order_info = place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + false, + constants::max_u64(), + &mut test, + ); + let order_id = order_info.order_id(); + + cancel_order(BOB, pool_id, balance_manager_id_bob, order_id, &mut test); + + // Referral rewards should be 0 + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + assert_eq!(base, 0); + assert_eq!(quote, 0); + assert_eq!(deep, 0); + return_shared(referral); + return_shared(pool); + }; + + // BOB's SUI should be fully restored + let bob_sui_after = asset_balance(BOB, balance_manager_id_bob, &mut test); + assert_eq!(bob_sui_before, bob_sui_after); + + end(test); +} + +// === Group 6: Configuration Variants === + +#[test] +fun maker_referral_multiplier_only_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + // multiplier = 1x (1_000_000_000), maker_fee_rate = 0 + let multiplier = 1_000_000_000; + let (referral_id, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + multiplier, + 0, // no volume-based fee + &mut test, + ); + + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + place_limit_order( + BOB, pool_id, balance_manager_id_bob, 1, + constants::no_restriction(), constants::self_matching_allowed(), + price, quantity, false, true, constants::max_u64(), &mut test, + ); + + place_market_order( + ALICE, pool_id, balance_manager_id_alice, 2, + constants::self_matching_allowed(), quantity, true, true, &mut test, + ); + + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + + // effective_rate = 0 + mul(protocol_maker_fee, multiplier) + // = mul(500_000, 1_000_000_000) = 500_000 + let effective_rate = math::mul(constants::maker_fee(), multiplier); + let expected_deep = math::mul( + math::mul(quantity, constants::deep_multiplier()), + effective_rate, + ); + assert!(deep > 0); + assert_eq!(deep, expected_deep); + assert_eq!(base, 0); + assert_eq!(quote, 0); + + return_shared(referral); + return_shared(pool); + }; + + end(test); +} + +#[test] +fun maker_referral_fee_rate_only_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + // multiplier = 0, maker_fee_rate = 10 bps + let maker_fee_rate = 1_000_000; + let (referral_id, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + place_limit_order( + BOB, pool_id, balance_manager_id_bob, 1, + constants::no_restriction(), constants::self_matching_allowed(), + price, quantity, false, true, constants::max_u64(), &mut test, + ); + + place_market_order( + ALICE, pool_id, balance_manager_id_alice, 2, + constants::self_matching_allowed(), quantity, true, true, &mut test, + ); + + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + + // effective_rate = maker_fee_rate + 0 = 1_000_000 + let expected_deep = math::mul( + math::mul(quantity, constants::deep_multiplier()), + maker_fee_rate, + ); + assert_eq!(deep, expected_deep); + assert_eq!(base, 0); + assert_eq!(quote, 0); + + return_shared(referral); + return_shared(pool); + }; + + end(test); +} + +#[test] +fun maker_referral_both_rate_and_multiplier_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + // Both: multiplier = 0.5x, maker_fee_rate = 10 bps + let multiplier = 500_000_000; // 0.5x + let maker_fee_rate = 1_000_000; + let (referral_id, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + multiplier, + maker_fee_rate, + &mut test, + ); + + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + place_limit_order( + BOB, pool_id, balance_manager_id_bob, 1, + constants::no_restriction(), constants::self_matching_allowed(), + price, quantity, false, true, constants::max_u64(), &mut test, + ); + + place_market_order( + ALICE, pool_id, balance_manager_id_alice, 2, + constants::self_matching_allowed(), quantity, true, true, &mut test, + ); + + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + + // effective_rate = maker_fee_rate + mul(protocol_maker_fee, multiplier) + // = 1_000_000 + mul(500_000, 500_000_000) = 1_000_000 + 250_000 = 1_250_000 + let effective_rate = maker_fee_rate + math::mul(constants::maker_fee(), multiplier); + let expected_deep = math::mul( + math::mul(quantity, constants::deep_multiplier()), + effective_rate, + ); + assert_eq!(deep, expected_deep); + assert_eq!(base, 0); + assert_eq!(quote, 0); + + return_shared(referral); + return_shared(pool); + }; + + end(test); +} + +#[test] +fun maker_referral_zero_effective_rate_no_lock() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + // Both = 0: no referral fee + let (referral_id, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + 0, // no maker fee rate set (will skip update_pool_referral_fee_rate) + &mut test, + ); + + let bob_deep_before = asset_balance(BOB, balance_manager_id_bob, &mut test); + + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + place_limit_order( + BOB, pool_id, balance_manager_id_bob, 1, + constants::no_restriction(), constants::self_matching_allowed(), + price, quantity, false, true, constants::max_u64(), &mut test, + ); + + let bob_deep_after_placement = asset_balance(BOB, balance_manager_id_bob, &mut test); + + // The DEEP delta should only be the protocol maker fee, no extra referral lock + let protocol_lock = math::mul( + math::mul(quantity, constants::deep_multiplier()), + constants::maker_fee(), + ); + assert_eq!(bob_deep_before - bob_deep_after_placement, protocol_lock); + + // Fill the order + place_market_order( + ALICE, pool_id, balance_manager_id_alice, 2, + constants::self_matching_allowed(), quantity, true, true, &mut test, + ); + + // Referral rewards should be 0 + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + assert_eq!(base, 0); + assert_eq!(quote, 0); + assert_eq!(deep, 0); + return_shared(referral); + return_shared(pool); + }; + + end(test); +} + +// === Group 7: Edge Cases === + +#[test] +fun maker_referral_market_order_no_lock() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; + let (referral_id, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + // ALICE places an ask limit order (rests in book) + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + place_limit_order( + ALICE, pool_id, balance_manager_id_alice, 1, + constants::no_restriction(), constants::self_matching_allowed(), + price, quantity, false, true, constants::max_u64(), &mut test, + ); + + // BOB places a market bid order (fully filled as taker, never rests in book) + place_market_order( + BOB, pool_id, balance_manager_id_bob, 2, + constants::self_matching_allowed(), quantity, true, true, &mut test, + ); + + // No maker referral rewards should be created (BOB was taker) + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + assert_eq!(base, 0); + assert_eq!(quote, 0); + assert_eq!(deep, 0); + return_shared(referral); + return_shared(pool); + }; + + end(test); +} + +#[test] +fun maker_referral_ioc_fully_filled_no_lock() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; + let (referral_id, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + // ALICE places an ask limit order (rests in book) + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + place_limit_order( + ALICE, pool_id, balance_manager_id_alice, 1, + constants::no_restriction(), constants::self_matching_allowed(), + price, quantity, false, true, constants::max_u64(), &mut test, + ); + + // BOB places IOC bid that fully fills (order_inserted = false) + let order_info = place_limit_order( + BOB, pool_id, balance_manager_id_bob, 2, + constants::immediate_or_cancel(), constants::self_matching_allowed(), + price, quantity, true, true, constants::max_u64(), &mut test, + ); + assert!(!order_info.order_inserted()); + + // No maker referral lock + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + assert_eq!(base, 0); + assert_eq!(quote, 0); + assert_eq!(deep, 0); + return_shared(referral); + return_shared(pool); + }; + + end(test); +} + +#[test] +fun maker_referral_no_referral_set_no_lock() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + // BOB does NOT set any referral + let bob_deep_before = asset_balance(BOB, balance_manager_id_bob, &mut test); + + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + place_limit_order( + BOB, pool_id, balance_manager_id_bob, 1, + constants::no_restriction(), constants::self_matching_allowed(), + price, quantity, false, true, constants::max_u64(), &mut test, + ); + + let bob_deep_after = asset_balance(BOB, balance_manager_id_bob, &mut test); + + // Only protocol maker fee should be locked, no referral lock + let protocol_lock = math::mul( + math::mul(quantity, constants::deep_multiplier()), + constants::maker_fee(), + ); + assert_eq!(bob_deep_before - bob_deep_after, protocol_lock); + + end(test); +} + +#[test] +fun maker_referral_taker_and_maker_referral_simultaneous_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + // R1: taker referral (for ALICE), has taker_fee_rate but no maker_fee_rate + let referral_id_r1; + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + referral_id_r1 = pool.mint_referral(0, test.ctx()); + return_shared(pool); + }; + let taker_fee_rate = 1_000_000; // 10 bps + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id_r1); + pool.update_pool_referral_fee_rate(&referral, taker_fee_rate, 0, test.ctx()); + return_shared(referral); + return_shared(pool); + }; + // Set R1 on ALICE's balance manager (taker) + test.next_tx(ALICE); + { + let mut balance_manager = test.take_shared_by_id(balance_manager_id_alice); + let referral = test.take_shared_by_id(referral_id_r1); + let trade_cap = test.take_from_sender(); + balance_manager.set_balance_manager_referral(&referral, &trade_cap); + test.return_to_sender(trade_cap); + return_shared(referral); + return_shared(balance_manager); + }; + + // R2: maker referral (for BOB), has maker_fee_rate but no taker_fee_rate + let referral_id_r2; + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + referral_id_r2 = pool.mint_referral(0, test.ctx()); + return_shared(pool); + }; + let maker_fee_rate = 1_000_000; // 10 bps + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id_r2); + pool.update_pool_referral_fee_rate(&referral, 0, maker_fee_rate, test.ctx()); + return_shared(referral); + return_shared(pool); + }; + // Set R2 on BOB's balance manager (maker) + test.next_tx(BOB); + { + let mut balance_manager = test.take_shared_by_id(balance_manager_id_bob); + let referral = test.take_shared_by_id(referral_id_r2); + let trade_cap = test.take_from_sender(); + balance_manager.set_balance_manager_referral(&referral, &trade_cap); + test.return_to_sender(trade_cap); + return_shared(referral); + return_shared(balance_manager); + }; + + // BOB places ask limit (maker) + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + place_limit_order( + BOB, pool_id, balance_manager_id_bob, 1, + constants::no_restriction(), constants::self_matching_allowed(), + price, quantity, false, true, constants::max_u64(), &mut test, + ); + + // ALICE places bid market (taker), filling BOB + place_market_order( + ALICE, pool_id, balance_manager_id_alice, 2, + constants::self_matching_allowed(), quantity, true, true, &mut test, + ); + + // Check R1 (taker referral) rewards + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral_r1 = test.take_shared_by_id(referral_id_r1); + let (base1, quote1, deep1) = pool.get_pool_referral_balances(&referral_r1); + // Taker referral rewards should have DEEP from taker fee + assert!(deep1 > 0); + assert_eq!(base1, 0); + assert_eq!(quote1, 0); + return_shared(referral_r1); + + // Check R2 (maker referral) rewards + let referral_r2 = test.take_shared_by_id(referral_id_r2); + let (base2, quote2, deep2) = pool.get_pool_referral_balances(&referral_r2); + // Maker referral rewards should have DEEP from maker referral lock + let expected_maker_deep = math::mul( + math::mul(quantity, constants::deep_multiplier()), + maker_fee_rate, + ); + assert_eq!(deep2, expected_maker_deep); + assert_eq!(base2, 0); + assert_eq!(quote2, 0); + return_shared(referral_r2); + return_shared(pool); + }; + + end(test); +} + +#[test] +fun maker_referral_multiple_makers_single_taker_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let balance_manager_id_carol = create_acct_and_share_with_funds_typed( + CAROL, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; + + // Set up referral for BOB + let (referral_id_bob, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + // Set up separate referral for CAROL + let referral_id_carol; + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + referral_id_carol = pool.mint_referral(0, test.ctx()); + return_shared(pool); + }; + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id_carol); + pool.update_pool_referral_fee_rate(&referral, 0, maker_fee_rate, test.ctx()); + return_shared(referral); + return_shared(pool); + }; + test.next_tx(CAROL); + { + let mut balance_manager = test.take_shared_by_id(balance_manager_id_carol); + let referral = test.take_shared_by_id(referral_id_carol); + let trade_cap = test.take_from_sender(); + balance_manager.set_balance_manager_referral(&referral, &trade_cap); + test.return_to_sender(trade_cap); + return_shared(referral); + return_shared(balance_manager); + }; + + // BOB and CAROL both place ask at same price + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + place_limit_order( + BOB, pool_id, balance_manager_id_bob, 1, + constants::no_restriction(), constants::self_matching_allowed(), + price, quantity, false, true, constants::max_u64(), &mut test, + ); + place_limit_order( + CAROL, pool_id, balance_manager_id_carol, 1, + constants::no_restriction(), constants::self_matching_allowed(), + price, quantity, false, true, constants::max_u64(), &mut test, + ); + + // ALICE buys 200 SUI (fills both BOB and CAROL) + let total_quantity = 200 * constants::float_scaling(); + place_market_order( + ALICE, pool_id, balance_manager_id_alice, 2, + constants::self_matching_allowed(), total_quantity, true, true, &mut test, + ); + + // Check BOB's referral rewards + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + + let referral_bob = test.take_shared_by_id(referral_id_bob); + let (base_b, quote_b, deep_b) = pool.get_pool_referral_balances(&referral_bob); + let expected_deep = math::mul( + math::mul(quantity, constants::deep_multiplier()), + maker_fee_rate, + ); + assert_eq!(deep_b, expected_deep); + assert_eq!(base_b, 0); + assert_eq!(quote_b, 0); + return_shared(referral_bob); + + // Check CAROL's referral rewards + let referral_carol = test.take_shared_by_id(referral_id_carol); + let (base_c, quote_c, deep_c) = pool.get_pool_referral_balances(&referral_carol); + assert_eq!(deep_c, expected_deep); + assert_eq!(base_c, 0); + assert_eq!(quote_c, 0); + return_shared(referral_carol); + + return_shared(pool); + }; + + end(test); +} + +#[test] +fun maker_referral_claim_rewards_after_fills_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; + let (referral_id, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + place_limit_order( + BOB, pool_id, balance_manager_id_bob, 1, + constants::no_restriction(), constants::self_matching_allowed(), + price, quantity, false, true, constants::max_u64(), &mut test, + ); + + place_market_order( + ALICE, pool_id, balance_manager_id_alice, 2, + constants::self_matching_allowed(), quantity, true, true, &mut test, + ); + + let expected_deep = math::mul( + math::mul(quantity, constants::deep_multiplier()), + maker_fee_rate, + ); + + // Claim rewards + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base_coin, quote_coin, deep_coin) = pool.claim_pool_referral_rewards( + &referral, + test.ctx(), + ); + + assert_eq!(deep_coin.value(), expected_deep); + assert_eq!(base_coin.value(), 0); + assert_eq!(quote_coin.value(), 0); + + base_coin.burn_for_testing(); + quote_coin.burn_for_testing(); + deep_coin.burn_for_testing(); + + // After claim, balances should be 0 + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + assert_eq!(base, 0); + assert_eq!(quote, 0); + assert_eq!(deep, 0); + + return_shared(referral); + return_shared(pool); + }; + + end(test); +} + +// === Group 8: Locked Balance View === + +#[test] +fun locked_balance_includes_referral_amount() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + + // Create two balance managers: one with referral, one without + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let balance_manager_id_carol = create_acct_and_share_with_funds_typed( + CAROL, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; + let (_, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + // BOB places ask with referral + place_limit_order( + BOB, pool_id, balance_manager_id_bob, 1, + constants::no_restriction(), constants::self_matching_allowed(), + price, quantity, false, true, constants::max_u64(), &mut test, + ); + + // CAROL places same ask without referral + place_limit_order( + CAROL, pool_id, balance_manager_id_carol, 1, + constants::no_restriction(), constants::self_matching_allowed(), + price, quantity, false, true, constants::max_u64(), &mut test, + ); + + // Compare locked balances + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let balance_manager_bob = test.take_shared_by_id(balance_manager_id_bob); + let balance_manager_carol = test.take_shared_by_id(balance_manager_id_carol); + + let (_, _, deep_locked_bob) = pool.locked_balance(&balance_manager_bob); + let (_, _, deep_locked_carol) = pool.locked_balance(&balance_manager_carol); + + // BOB's locked balance should include the referral lock + let referral_lock = math::mul( + math::mul(quantity, constants::deep_multiplier()), + maker_fee_rate, + ); + assert_eq!(deep_locked_bob - deep_locked_carol, referral_lock); + + return_shared(balance_manager_bob); + return_shared(balance_manager_carol); + return_shared(pool); + }; + + end(test); +} + +#[test] +fun locked_balance_after_partial_fill() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; + let (_, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + let price = 3 * constants::float_scaling(); + let total_quantity = 1000 * constants::float_scaling(); + + place_limit_order( + BOB, pool_id, balance_manager_id_bob, 1, + constants::no_restriction(), constants::self_matching_allowed(), + price, total_quantity, false, true, constants::max_u64(), &mut test, + ); + + // Get locked balance before fill + test.next_tx(ALICE); + let deep_locked_before; + { + let pool = test.take_shared_by_id>(pool_id); + let balance_manager = test.take_shared_by_id(balance_manager_id_bob); + let (_, _, dl) = pool.locked_balance(&balance_manager); + deep_locked_before = dl; + return_shared(balance_manager); + return_shared(pool); + }; + + // Partial fill 400 + let fill_quantity = 400 * constants::float_scaling(); + place_market_order( + ALICE, pool_id, balance_manager_id_alice, 2, + constants::self_matching_allowed(), fill_quantity, true, true, &mut test, + ); + + // Get locked balance after fill + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let balance_manager = test.take_shared_by_id(balance_manager_id_bob); + let (_, _, deep_locked_after) = pool.locked_balance(&balance_manager); + + // Locked balance should have decreased + assert!(deep_locked_after < deep_locked_before); + + // The protocol fee portion of the decrease (maker fee is refunded on fill) + // Total locked decrease ≈ protocol_lock_for_400 + referral_lock_for_400 + // But the protocol portion goes to settled. For locked_balance, it should + // reflect remaining 600 qty's protocol + referral lock. + let remaining_qty = total_quantity - fill_quantity; + let expected_remaining_referral = math::mul( + math::mul(remaining_qty, constants::deep_multiplier()), + maker_fee_rate, + ); + let expected_remaining_protocol = math::mul( + math::mul(remaining_qty, constants::deep_multiplier()), + constants::maker_fee(), + ); + // deep_locked_after includes order protocol lock + referral lock + any settled + // The order lock portion should be for 600 qty + assert!(deep_locked_after >= expected_remaining_referral + expected_remaining_protocol); + + return_shared(balance_manager); + return_shared(pool); + }; + + end(test); +} + +#[test] +fun locked_balance_after_cancel_zero_referral() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; + let (_, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + let order_info = place_limit_order( + BOB, pool_id, balance_manager_id_bob, 1, + constants::no_restriction(), constants::self_matching_allowed(), + price, quantity, false, true, constants::max_u64(), &mut test, + ); + let order_id = order_info.order_id(); + + // Cancel the order + cancel_order(BOB, pool_id, balance_manager_id_bob, order_id, &mut test); + + // Locked balance should have no referral-locked amount + // After cancellation, the order is removed and referral DF is deleted + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let balance_manager = test.take_shared_by_id(balance_manager_id_bob); + let (_, _, deep_locked) = pool.locked_balance(&balance_manager); + + // After cancel: no orders, but settled amounts include the refund + assert!(deep_locked >= 0); + + return_shared(balance_manager); + return_shared(pool); + }; + + // After withdrawing settled amounts, locked balance should be 0 + withdraw_settled_amounts(pool_id, BOB, balance_manager_id_bob, &mut test); + + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let balance_manager = test.take_shared_by_id(balance_manager_id_bob); + let (base_locked, quote_locked, deep_locked) = pool.locked_balance(&balance_manager); + assert_eq!(base_locked, 0); + assert_eq!(quote_locked, 0); + assert_eq!(deep_locked, 0); + return_shared(balance_manager); + return_shared(pool); + }; + + end(test); +} From fb66961c06eb869ff1e058b100b4baf73ff8c153 Mon Sep 17 00:00:00 2001 From: bathord Date: Mon, 16 Mar 2026 18:27:09 +0300 Subject: [PATCH 5/9] Formatting --- packages/deepbook/sources/pool.move | 89 ++-- .../deepbook/tests/maker_referral_tests.move | 429 +++++++++++++----- 2 files changed, 369 insertions(+), 149 deletions(-) diff --git a/packages/deepbook/sources/pool.move b/packages/deepbook/sources/pool.move index 7f495c13d..bee21c9ca 100644 --- a/packages/deepbook/sources/pool.move +++ b/packages/deepbook/sources/pool.move @@ -536,10 +536,7 @@ public fun modify_order( let (cancel_quantity, order) = inner .book .modify_order(order_id, new_quantity, clock.timestamp_ms()); - assert!( - order.balance_manager_id() == balance_manager.id(), - EInvalidOrderBalanceManager, - ); + assert!(order.balance_manager_id() == balance_manager.id(), EInvalidOrderBalanceManager); let (settled, owed) = inner .state .process_modify( @@ -600,10 +597,7 @@ public fun cancel_order( let (mut settled, owed) = { let inner = self.load_inner_mut(); let mut order = inner.book.cancel_order(order_id); - assert!( - order.balance_manager_id() == balance_manager.id(), - EInvalidOrderBalanceManager, - ); + assert!(order.balance_manager_id() == balance_manager.id(), EInvalidOrderBalanceManager); let (settled, owed) = inner .state .process_cancel(&mut order, balance_manager.id(), inner.pool_id, ctx); @@ -2147,9 +2141,7 @@ fun lock_maker_referral_fee( 0 }; - let referral_rewards: &ReferralRewards = self - .id - .borrow(referral_id); + let referral_rewards: &ReferralRewards = self.id.borrow(referral_id); let multiplier = referral_rewards.multiplier; let protocol_maker_fee = { @@ -2185,24 +2177,28 @@ fun lock_maker_referral_fee( { let inner = self.load_inner_mut(); - inner.vault.settle_balance_manager( - balances::empty(), - referral_owed, - balance_manager, - trade_proof, - ); + inner + .vault + .settle_balance_manager( + balances::empty(), + referral_owed, + balance_manager, + trade_proof, + ); }; - self.id.add( - MakerReferralKey(order_info.order_id()), - MakerReferralInfo { - referral_id, - effective_rate, - locked_balance: locked_amount, - fee_is_deep, - is_bid, - }, - ); + self + .id + .add( + MakerReferralKey(order_info.order_id()), + MakerReferralInfo { + referral_id, + effective_rate, + locked_balance: locked_amount, + fee_is_deep, + is_bid, + }, + ); } /// Process fills against maker orders that have pre-locked referral fees. @@ -2237,19 +2233,23 @@ fun process_maker_referral_fills( }; { let inner = self.load_inner_mut(); - inner.state.credit_account_settled_balances( - fill.balance_manager_id(), - refund, - ctx, - ); + inner + .state + .credit_account_settled_balances( + fill.balance_manager_id(), + refund, + ctx, + ); }; df::remove(&mut self.id, key); } else { - let mut fee_balances = fill.maker_deep_price().fee_quantity( - fill.base_quantity(), - fill.quote_quantity(), - info.is_bid, - ); + let mut fee_balances = fill + .maker_deep_price() + .fee_quantity( + fill.base_quantity(), + fill.quote_quantity(), + info.is_bid, + ); fee_balances.mul(info.effective_rate); let transfer_amount = if (fill.completed()) { info.locked_balance @@ -2263,24 +2263,27 @@ fun process_maker_referral_fills( let inner = self.load_inner_mut(); inner.vault.withdraw_referral_fee_deep(transfer_amount) }; - let rewards: &mut ReferralRewards = - self.id.borrow_mut(info.referral_id); + let rewards: &mut ReferralRewards = self + .id + .borrow_mut(info.referral_id); rewards.deep.join(bal); } else if (info.is_bid) { let bal = { let inner = self.load_inner_mut(); inner.vault.withdraw_referral_fee_quote(transfer_amount) }; - let rewards: &mut ReferralRewards = - self.id.borrow_mut(info.referral_id); + let rewards: &mut ReferralRewards = self + .id + .borrow_mut(info.referral_id); rewards.quote.join(bal); } else { let bal = { let inner = self.load_inner_mut(); inner.vault.withdraw_referral_fee_base(transfer_amount) }; - let rewards: &mut ReferralRewards = - self.id.borrow_mut(info.referral_id); + let rewards: &mut ReferralRewards = self + .id + .borrow_mut(info.referral_id); rewards.base.join(bal); }; diff --git a/packages/deepbook/tests/maker_referral_tests.move b/packages/deepbook/tests/maker_referral_tests.move index dc3b12718..e64dc8588 100644 --- a/packages/deepbook/tests/maker_referral_tests.move +++ b/packages/deepbook/tests/maker_referral_tests.move @@ -6,11 +6,7 @@ module deepbook::maker_referral_tests; use deepbook::{ balance_manager::{BalanceManager, TradeCap, DeepBookPoolReferral}, - balance_manager_tests::{ - USDC, - create_acct_and_share_with_funds_typed, - asset_balance - }, + balance_manager_tests::{USDC, create_acct_and_share_with_funds_typed, asset_balance}, constants, math, pool::Pool, @@ -22,15 +18,11 @@ use deepbook::{ place_market_order, cancel_order, modify_order, - set_time, - }, + set_time + } }; use std::unit_test::assert_eq; -use sui::{ - clock::Clock, - sui::SUI, - test_scenario::{Scenario, begin, end, return_shared} -}; +use sui::{clock::Clock, sui::SUI, test_scenario::{Scenario, begin, end, return_shared}}; use token::deep::DEEP; const OWNER: address = @0x1; @@ -109,7 +101,7 @@ fun maker_referral_full_fill_ok() { price, quantity, false, // ask - true, // pay_with_deep + true, // pay_with_deep constants::max_u64(), &mut test, ); @@ -165,10 +157,13 @@ fun maker_referral_full_fill_ok() { // The net difference from before placement to after fill should include the referral amount. assert!(bob_deep_before > bob_deep_after_fill); // Referral lock = expected_lock should equal the deep rewards - assert_eq!(expected_lock, math::mul( - math::mul(quantity, constants::deep_multiplier()), - maker_fee_rate, - )); + assert_eq!( + expected_lock, + math::mul( + math::mul(quantity, constants::deep_multiplier()), + maker_fee_rate, + ), + ); end(test); } @@ -237,7 +232,7 @@ fun maker_referral_partial_fill_ok() { price, total_quantity, false, // ask - true, // pay_with_deep + true, // pay_with_deep constants::max_u64(), &mut test, ); @@ -492,8 +487,8 @@ fun maker_referral_bid_order_ok() { constants::self_matching_allowed(), price, quantity, - true, // bid - true, // pay_with_deep + true, // bid + true, // pay_with_deep constants::max_u64(), &mut test, ); @@ -508,7 +503,7 @@ fun maker_referral_bid_order_ok() { constants::self_matching_allowed(), quantity, false, // ask - true, // pay_with_deep + true, // pay_with_deep &mut test, ); @@ -564,8 +559,8 @@ fun maker_referral_cancel_full_refund_ok() { let (referral_id, _) = setup_maker_referral( pool_id, balance_manager_id_bob, - 0, // multiplier - 1_000_000, // maker_fee_rate = 10 bps + 0, // multiplier + 1_000_000, // maker_fee_rate = 10 bps &mut test, ); @@ -739,14 +734,32 @@ fun maker_referral_cancel_all_orders_ok() { let quantity = 100 * constants::float_scaling(); place_limit_order( - BOB, pool_id, balance_manager_id_bob, 1, - constants::no_restriction(), constants::self_matching_allowed(), - price1, quantity, false, true, constants::max_u64(), &mut test, + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price1, + quantity, + false, + true, + constants::max_u64(), + &mut test, ); place_limit_order( - BOB, pool_id, balance_manager_id_bob, 2, - constants::no_restriction(), constants::self_matching_allowed(), - price2, quantity, false, true, constants::max_u64(), &mut test, + BOB, + pool_id, + balance_manager_id_bob, + 2, + constants::no_restriction(), + constants::self_matching_allowed(), + price2, + quantity, + false, + true, + constants::max_u64(), + &mut test, ); cancel_all_orders(pool_id, BOB, balance_manager_id_bob, &mut test); @@ -813,12 +826,7 @@ fun setup_maker_referral( (referral_id, maker_fee_rate) } -fun cancel_all_orders( - pool_id: ID, - owner: address, - balance_manager_id: ID, - test: &mut Scenario, -) { +fun cancel_all_orders(pool_id: ID, owner: address, balance_manager_id: ID, test: &mut Scenario) { test.next_tx(owner); { let mut pool = test.take_shared_by_id>(pool_id); @@ -1209,7 +1217,7 @@ fun maker_referral_ask_non_deep_base_fee_ok() { let pool_id = setup_pool_with_default_fees( OWNER, registry_id, - true, // whitelisted + true, // whitelisted false, // not stable &mut test, ); @@ -1263,7 +1271,7 @@ fun maker_referral_ask_non_deep_base_fee_ok() { 2, constants::self_matching_allowed(), quantity, - true, // bid + true, // bid false, // pay_with_deep = false &mut test, ); @@ -1302,7 +1310,7 @@ fun maker_referral_bid_non_deep_quote_fee_ok() { let pool_id = setup_pool_with_default_fees( OWNER, registry_id, - true, // whitelisted + true, // whitelisted false, &mut test, ); @@ -1341,7 +1349,7 @@ fun maker_referral_bid_non_deep_quote_fee_ok() { constants::self_matching_allowed(), price, quantity, - true, // bid + true, // bid false, // pay_with_deep = false constants::max_u64(), &mut test, @@ -1499,14 +1507,30 @@ fun maker_referral_multiplier_only_ok() { let quantity = 100 * constants::float_scaling(); place_limit_order( - BOB, pool_id, balance_manager_id_bob, 1, - constants::no_restriction(), constants::self_matching_allowed(), - price, quantity, false, true, constants::max_u64(), &mut test, + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, ); place_market_order( - ALICE, pool_id, balance_manager_id_alice, 2, - constants::self_matching_allowed(), quantity, true, true, &mut test, + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + quantity, + true, + true, + &mut test, ); test.next_tx(ALICE); @@ -1569,14 +1593,30 @@ fun maker_referral_fee_rate_only_ok() { let quantity = 100 * constants::float_scaling(); place_limit_order( - BOB, pool_id, balance_manager_id_bob, 1, - constants::no_restriction(), constants::self_matching_allowed(), - price, quantity, false, true, constants::max_u64(), &mut test, + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, ); place_market_order( - ALICE, pool_id, balance_manager_id_alice, 2, - constants::self_matching_allowed(), quantity, true, true, &mut test, + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + quantity, + true, + true, + &mut test, ); test.next_tx(ALICE); @@ -1637,14 +1677,30 @@ fun maker_referral_both_rate_and_multiplier_ok() { let quantity = 100 * constants::float_scaling(); place_limit_order( - BOB, pool_id, balance_manager_id_bob, 1, - constants::no_restriction(), constants::self_matching_allowed(), - price, quantity, false, true, constants::max_u64(), &mut test, + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, ); place_market_order( - ALICE, pool_id, balance_manager_id_alice, 2, - constants::self_matching_allowed(), quantity, true, true, &mut test, + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + quantity, + true, + true, + &mut test, ); test.next_tx(ALICE); @@ -1707,9 +1763,18 @@ fun maker_referral_zero_effective_rate_no_lock() { let quantity = 100 * constants::float_scaling(); place_limit_order( - BOB, pool_id, balance_manager_id_bob, 1, - constants::no_restriction(), constants::self_matching_allowed(), - price, quantity, false, true, constants::max_u64(), &mut test, + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, ); let bob_deep_after_placement = asset_balance(BOB, balance_manager_id_bob, &mut test); @@ -1723,8 +1788,15 @@ fun maker_referral_zero_effective_rate_no_lock() { // Fill the order place_market_order( - ALICE, pool_id, balance_manager_id_alice, 2, - constants::self_matching_allowed(), quantity, true, true, &mut test, + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + quantity, + true, + true, + &mut test, ); // Referral rewards should be 0 @@ -1779,15 +1851,31 @@ fun maker_referral_market_order_no_lock() { let price = 3 * constants::float_scaling(); let quantity = 100 * constants::float_scaling(); place_limit_order( - ALICE, pool_id, balance_manager_id_alice, 1, - constants::no_restriction(), constants::self_matching_allowed(), - price, quantity, false, true, constants::max_u64(), &mut test, + ALICE, + pool_id, + balance_manager_id_alice, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, ); // BOB places a market bid order (fully filled as taker, never rests in book) place_market_order( - BOB, pool_id, balance_manager_id_bob, 2, - constants::self_matching_allowed(), quantity, true, true, &mut test, + BOB, + pool_id, + balance_manager_id_bob, + 2, + constants::self_matching_allowed(), + quantity, + true, + true, + &mut test, ); // No maker referral rewards should be created (BOB was taker) @@ -1840,16 +1928,34 @@ fun maker_referral_ioc_fully_filled_no_lock() { let price = 3 * constants::float_scaling(); let quantity = 100 * constants::float_scaling(); place_limit_order( - ALICE, pool_id, balance_manager_id_alice, 1, - constants::no_restriction(), constants::self_matching_allowed(), - price, quantity, false, true, constants::max_u64(), &mut test, + ALICE, + pool_id, + balance_manager_id_alice, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, ); // BOB places IOC bid that fully fills (order_inserted = false) let order_info = place_limit_order( - BOB, pool_id, balance_manager_id_bob, 2, - constants::immediate_or_cancel(), constants::self_matching_allowed(), - price, quantity, true, true, constants::max_u64(), &mut test, + BOB, + pool_id, + balance_manager_id_bob, + 2, + constants::immediate_or_cancel(), + constants::self_matching_allowed(), + price, + quantity, + true, + true, + constants::max_u64(), + &mut test, ); assert!(!order_info.order_inserted()); @@ -1897,9 +2003,18 @@ fun maker_referral_no_referral_set_no_lock() { let quantity = 100 * constants::float_scaling(); place_limit_order( - BOB, pool_id, balance_manager_id_bob, 1, - constants::no_restriction(), constants::self_matching_allowed(), - price, quantity, false, true, constants::max_u64(), &mut test, + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, ); let bob_deep_after = asset_balance(BOB, balance_manager_id_bob, &mut test); @@ -1998,15 +2113,31 @@ fun maker_referral_taker_and_maker_referral_simultaneous_ok() { let quantity = 100 * constants::float_scaling(); place_limit_order( - BOB, pool_id, balance_manager_id_bob, 1, - constants::no_restriction(), constants::self_matching_allowed(), - price, quantity, false, true, constants::max_u64(), &mut test, + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, ); // ALICE places bid market (taker), filling BOB place_market_order( - ALICE, pool_id, balance_manager_id_alice, 2, - constants::self_matching_allowed(), quantity, true, true, &mut test, + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + quantity, + true, + true, + &mut test, ); // Check R1 (taker referral) rewards @@ -2108,21 +2239,46 @@ fun maker_referral_multiple_makers_single_taker_ok() { let quantity = 100 * constants::float_scaling(); place_limit_order( - BOB, pool_id, balance_manager_id_bob, 1, - constants::no_restriction(), constants::self_matching_allowed(), - price, quantity, false, true, constants::max_u64(), &mut test, + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, ); place_limit_order( - CAROL, pool_id, balance_manager_id_carol, 1, - constants::no_restriction(), constants::self_matching_allowed(), - price, quantity, false, true, constants::max_u64(), &mut test, + CAROL, + pool_id, + balance_manager_id_carol, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, ); // ALICE buys 200 SUI (fills both BOB and CAROL) let total_quantity = 200 * constants::float_scaling(); place_market_order( - ALICE, pool_id, balance_manager_id_alice, 2, - constants::self_matching_allowed(), total_quantity, true, true, &mut test, + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + total_quantity, + true, + true, + &mut test, ); // Check BOB's referral rewards @@ -2189,14 +2345,30 @@ fun maker_referral_claim_rewards_after_fills_ok() { let quantity = 100 * constants::float_scaling(); place_limit_order( - BOB, pool_id, balance_manager_id_bob, 1, - constants::no_restriction(), constants::self_matching_allowed(), - price, quantity, false, true, constants::max_u64(), &mut test, + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, ); place_market_order( - ALICE, pool_id, balance_manager_id_alice, 2, - constants::self_matching_allowed(), quantity, true, true, &mut test, + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + quantity, + true, + true, + &mut test, ); let expected_deep = math::mul( @@ -2279,16 +2451,34 @@ fun locked_balance_includes_referral_amount() { // BOB places ask with referral place_limit_order( - BOB, pool_id, balance_manager_id_bob, 1, - constants::no_restriction(), constants::self_matching_allowed(), - price, quantity, false, true, constants::max_u64(), &mut test, + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, ); // CAROL places same ask without referral place_limit_order( - CAROL, pool_id, balance_manager_id_carol, 1, - constants::no_restriction(), constants::self_matching_allowed(), - price, quantity, false, true, constants::max_u64(), &mut test, + CAROL, + pool_id, + balance_manager_id_carol, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, ); // Compare locked balances @@ -2296,7 +2486,9 @@ fun locked_balance_includes_referral_amount() { { let pool = test.take_shared_by_id>(pool_id); let balance_manager_bob = test.take_shared_by_id(balance_manager_id_bob); - let balance_manager_carol = test.take_shared_by_id(balance_manager_id_carol); + let balance_manager_carol = test.take_shared_by_id( + balance_manager_id_carol, + ); let (_, _, deep_locked_bob) = pool.locked_balance(&balance_manager_bob); let (_, _, deep_locked_carol) = pool.locked_balance(&balance_manager_carol); @@ -2350,9 +2542,18 @@ fun locked_balance_after_partial_fill() { let total_quantity = 1000 * constants::float_scaling(); place_limit_order( - BOB, pool_id, balance_manager_id_bob, 1, - constants::no_restriction(), constants::self_matching_allowed(), - price, total_quantity, false, true, constants::max_u64(), &mut test, + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + total_quantity, + false, + true, + constants::max_u64(), + &mut test, ); // Get locked balance before fill @@ -2370,8 +2571,15 @@ fun locked_balance_after_partial_fill() { // Partial fill 400 let fill_quantity = 400 * constants::float_scaling(); place_market_order( - ALICE, pool_id, balance_manager_id_alice, 2, - constants::self_matching_allowed(), fill_quantity, true, true, &mut test, + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + fill_quantity, + true, + true, + &mut test, ); // Get locked balance after fill @@ -2442,9 +2650,18 @@ fun locked_balance_after_cancel_zero_referral() { let quantity = 100 * constants::float_scaling(); let order_info = place_limit_order( - BOB, pool_id, balance_manager_id_bob, 1, - constants::no_restriction(), constants::self_matching_allowed(), - price, quantity, false, true, constants::max_u64(), &mut test, + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, ); let order_id = order_info.order_id(); From b47105d7352cc43d6f05073c4b8e453dd97ab657 Mon Sep 17 00:00:00 2001 From: bathord Date: Mon, 16 Mar 2026 21:07:04 +0300 Subject: [PATCH 6/9] Add "corner" test cases for referral maker fees --- .../deepbook/tests/maker_referral_tests.move | 858 +++++++++++++++++- 1 file changed, 855 insertions(+), 3 deletions(-) diff --git a/packages/deepbook/tests/maker_referral_tests.move b/packages/deepbook/tests/maker_referral_tests.move index e64dc8588..2b951d62f 100644 --- a/packages/deepbook/tests/maker_referral_tests.move +++ b/packages/deepbook/tests/maker_referral_tests.move @@ -5,8 +5,13 @@ module deepbook::maker_referral_tests; use deepbook::{ - balance_manager::{BalanceManager, TradeCap, DeepBookPoolReferral}, - balance_manager_tests::{USDC, create_acct_and_share_with_funds_typed, asset_balance}, + balance_manager::{Self, BalanceManager, TradeCap, DeepBookPoolReferral}, + balance_manager_tests::{ + USDC, + create_acct_and_share_with_funds_typed, + asset_balance, + deposit_into_account + }, constants, math, pool::Pool, @@ -22,7 +27,12 @@ use deepbook::{ } }; use std::unit_test::assert_eq; -use sui::{clock::Clock, sui::SUI, test_scenario::{Scenario, begin, end, return_shared}}; +use sui::{ + clock::Clock, + coin::mint_for_testing, + sui::SUI, + test_scenario::{Scenario, begin, end, return_shared} +}; use token::deep::DEEP; const OWNER: address = @0x1; @@ -2407,6 +2417,137 @@ fun maker_referral_claim_rewards_after_fills_ok() { end(test); } +#[test] +fun maker_referral_changed_after_placement_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; + + // Mint two referrals: R1 and R2 + let referral_id_r1; + let referral_id_r2; + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + referral_id_r1 = pool.mint_referral(0, test.ctx()); + referral_id_r2 = pool.mint_referral(0, test.ctx()); + return_shared(pool); + }; + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + let referral_r1 = test.take_shared_by_id(referral_id_r1); + pool.update_pool_referral_fee_rate(&referral_r1, 0, maker_fee_rate, test.ctx()); + return_shared(referral_r1); + let referral_r2 = test.take_shared_by_id(referral_id_r2); + pool.update_pool_referral_fee_rate(&referral_r2, 0, maker_fee_rate, test.ctx()); + return_shared(referral_r2); + return_shared(pool); + }; + + // BOB sets R1 on his balance manager + test.next_tx(BOB); + { + let mut balance_manager = test.take_shared_by_id(balance_manager_id_bob); + let referral = test.take_shared_by_id(referral_id_r1); + let trade_cap = test.take_from_sender(); + balance_manager.set_balance_manager_referral(&referral, &trade_cap); + test.return_to_sender(trade_cap); + return_shared(referral); + return_shared(balance_manager); + }; + + // BOB places an ask with R1 active + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, + ); + + // BOB switches to R2 AFTER placement + test.next_tx(BOB); + { + let mut balance_manager = test.take_shared_by_id(balance_manager_id_bob); + let referral_r2 = test.take_shared_by_id(referral_id_r2); + let trade_cap = test.take_from_sender(); + balance_manager.set_balance_manager_referral(&referral_r2, &trade_cap); + test.return_to_sender(trade_cap); + return_shared(referral_r2); + return_shared(balance_manager); + }; + + // ALICE fills BOB's order + place_market_order( + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + quantity, + true, + true, + &mut test, + ); + + // R1 (original referral at placement time) should receive the maker referral rewards + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + + let referral_r1 = test.take_shared_by_id(referral_id_r1); + let (base1, quote1, deep1) = pool.get_pool_referral_balances(&referral_r1); + let expected_deep = math::mul( + math::mul(quantity, constants::deep_multiplier()), + maker_fee_rate, + ); + assert_eq!(deep1, expected_deep); + assert_eq!(base1, 0); + assert_eq!(quote1, 0); + return_shared(referral_r1); + + // R2 (new referral) should have zero maker referral rewards + let referral_r2 = test.take_shared_by_id(referral_id_r2); + let (base2, quote2, deep2) = pool.get_pool_referral_balances(&referral_r2); + assert_eq!(deep2, 0); + assert_eq!(base2, 0); + assert_eq!(quote2, 0); + return_shared(referral_r2); + + return_shared(pool); + }; + + end(test); +} + // === Group 8: Locked Balance View === #[test] @@ -2700,3 +2841,714 @@ fun locked_balance_after_cancel_zero_referral() { end(test); } + +// === Corner Cases === + +// Partial fill → modify → fill remainder +// Verifies locked_balance is correctly tracked across partial fill, modify, and final fill. +#[test] +fun maker_referral_partial_fill_modify_fill_remainder_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; + let (referral_id, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + // BOB places ask for 1000 SUI at $3 + let price = 3 * constants::float_scaling(); + let original_quantity = 1000 * constants::float_scaling(); + + let order_info = place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + original_quantity, + false, + true, + constants::max_u64(), + &mut test, + ); + let order_id = order_info.order_id(); + + // Step 1: Partial fill 200 SUI + let fill1_qty = 200 * constants::float_scaling(); + place_market_order( + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + fill1_qty, + true, + true, + &mut test, + ); + + // Check referral got fees for 200 SUI + let fee_per_unit = math::mul(constants::deep_multiplier(), maker_fee_rate); + let expected_reward_1 = math::mul(fill1_qty, fee_per_unit); + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (_, _, deep) = pool.get_pool_referral_balances(&referral); + assert_eq!(deep, expected_reward_1); + return_shared(referral); + return_shared(pool); + }; + + // Step 2: Modify down from remaining 800 to 500 (cancel 300) + let new_quantity = 700 * constants::float_scaling(); // 200 filled + 500 remaining + modify_order( + BOB, + pool_id, + balance_manager_id_bob, + order_id, + new_quantity, + &mut test, + ); + + // Step 3: Fill the remaining 500 SUI + let fill2_qty = 500 * constants::float_scaling(); + place_market_order( + ALICE, + pool_id, + balance_manager_id_alice, + 3, + constants::self_matching_allowed(), + fill2_qty, + true, + true, + &mut test, + ); + + // Check total referral rewards = fees for 200 + 500 = 700 SUI + // On the final fill (completed), the code sweeps info.locked_balance directly. + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + + // locked after placement = fee for 1000 SUI + // minus fee for 200 SUI (partial fill 1, transferred to rewards) + // minus fee for 300 SUI (modify refund, returned to maker) + // = fee for 500 SUI → swept entirely on final completion fill + let lock_for_1000 = math::mul(original_quantity, fee_per_unit); + let transferred_fill1 = expected_reward_1; + let refunded_modify = math::mul(300 * constants::float_scaling(), fee_per_unit); + let remaining_locked = lock_for_1000 - transferred_fill1 - refunded_modify; + assert_eq!(deep, expected_reward_1 + remaining_locked); + assert_eq!(base, 0); + assert_eq!(quote, 0); + + // remaining_locked should equal fee for 500 SUI + assert_eq!(remaining_locked, math::mul(fill2_qty, fee_per_unit)); + + return_shared(referral); + return_shared(pool); + }; + + end(test); +} + +// Self-match with cancel_maker expires the maker and refunds referral lock. +// BOB places a maker ask with referral, then places a taker bid with cancel_maker +// from the same balance_manager. The maker is expired, and the referral fee is refunded +// to settled_balances rather than credited to the referral rewards. +#[test] +fun maker_referral_self_match_cancel_maker_refund_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + BOB, + registry_id, + balance_manager_id_bob, + &mut test, + ); + + let maker_fee_rate = 1_000_000; + let (referral_id, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + // BOB places maker ask for 100 SUI at $3 + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + let bob_deep_before = asset_balance(BOB, balance_manager_id_bob, &mut test); + + place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, + ); + + let bob_deep_after_placement = asset_balance(BOB, balance_manager_id_bob, &mut test); + let _total_locked = bob_deep_before - bob_deep_after_placement; + + // BOB places taker bid with cancel_maker option (self-match expires the maker) + place_market_order( + BOB, + pool_id, + balance_manager_id_bob, + 2, + constants::cancel_maker(), + quantity, + true, + true, + &mut test, + ); + + // The expired maker's referral lock should be refunded to settled_balances. + // Referral rewards should remain zero. + test.next_tx(BOB); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + assert_eq!(base, 0); + assert_eq!(quote, 0); + assert_eq!(deep, 0); + return_shared(referral); + return_shared(pool); + }; + + // After withdrawing settled amounts, BOB should recover the full referral lock + withdraw_settled_amounts(pool_id, BOB, balance_manager_id_bob, &mut test); + let bob_deep_after_withdraw = asset_balance(BOB, balance_manager_id_bob, &mut test); + + // BOB's DEEP should be restored: the protocol maker fee + referral lock were both refunded + // since the maker expired (no actual trade occurred). + assert_eq!(bob_deep_after_withdraw, bob_deep_before); + + end(test); +} + +// Same maker places two orders with different referrals. +// A single taker fills both. Each referral should receive fees only from its order. +#[test] +fun maker_referral_two_orders_different_referrals_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; + + // Mint R1 and R2 referrals + let referral_id_r1; + let referral_id_r2; + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + referral_id_r1 = pool.mint_referral(0, test.ctx()); + referral_id_r2 = pool.mint_referral(0, test.ctx()); + return_shared(pool); + }; + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + let referral_r1 = test.take_shared_by_id(referral_id_r1); + pool.update_pool_referral_fee_rate(&referral_r1, 0, maker_fee_rate, test.ctx()); + return_shared(referral_r1); + let referral_r2 = test.take_shared_by_id(referral_id_r2); + pool.update_pool_referral_fee_rate(&referral_r2, 0, maker_fee_rate, test.ctx()); + return_shared(referral_r2); + return_shared(pool); + }; + + // BOB sets R1, places order #1 (100 SUI ask at $3) + test.next_tx(BOB); + { + let mut balance_manager = test.take_shared_by_id(balance_manager_id_bob); + let referral = test.take_shared_by_id(referral_id_r1); + let trade_cap = test.take_from_sender(); + balance_manager.set_balance_manager_referral(&referral, &trade_cap); + test.return_to_sender(trade_cap); + return_shared(referral); + return_shared(balance_manager); + }; + + let price = 3 * constants::float_scaling(); + let qty1 = 100 * constants::float_scaling(); + place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + qty1, + false, + true, + constants::max_u64(), + &mut test, + ); + + // BOB switches to R2, places order #2 (200 SUI ask at $3) + test.next_tx(BOB); + { + let mut balance_manager = test.take_shared_by_id(balance_manager_id_bob); + let referral = test.take_shared_by_id(referral_id_r2); + let trade_cap = test.take_from_sender(); + balance_manager.set_balance_manager_referral(&referral, &trade_cap); + test.return_to_sender(trade_cap); + return_shared(referral); + return_shared(balance_manager); + }; + + let qty2 = 200 * constants::float_scaling(); + place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 2, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + qty2, + false, + true, + constants::max_u64(), + &mut test, + ); + + // ALICE fills both with a single market order for 300 SUI + let total_qty = qty1 + qty2; + place_market_order( + ALICE, + pool_id, + balance_manager_id_alice, + 3, + constants::self_matching_allowed(), + total_qty, + true, + true, + &mut test, + ); + + // R1 should have fees from order #1 (100 SUI) + let fee_per_unit = math::mul(constants::deep_multiplier(), maker_fee_rate); + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + + let referral_r1 = test.take_shared_by_id(referral_id_r1); + let (_, _, deep1) = pool.get_pool_referral_balances(&referral_r1); + assert_eq!(deep1, math::mul(qty1, fee_per_unit)); + return_shared(referral_r1); + + // R2 should have fees from order #2 (200 SUI) + let referral_r2 = test.take_shared_by_id(referral_id_r2); + let (_, _, deep2) = pool.get_pool_referral_balances(&referral_r2); + assert_eq!(deep2, math::mul(qty2, fee_per_unit)); + return_shared(referral_r2); + + return_shared(pool); + }; + + end(test); +} + +// swap_exact_quote_for_base fills a maker order that has a referral. +// The swap creates a temporary balance_manager with no referral. The maker's +// referral should still receive the fees from process_maker_referral_fills. +#[test] +fun maker_referral_swap_fills_maker_order_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; + let (referral_id, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + // BOB places maker ask for 100 SUI at $3 + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + + place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, + ); + + // ALICE fills via swap (no balance_manager, no referral on taker side) + let quote_to_swap = math::mul(quantity, price); // 300 USDC + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + let clock = test.take_shared(); + let (base_out, quote_out, deep_out) = pool.swap_exact_quote_for_base( + mint_for_testing(quote_to_swap, test.ctx()), + mint_for_testing(1_000 * constants::float_scaling(), test.ctx()), + 0, + &clock, + test.ctx(), + ); + transfer::public_transfer(base_out, ALICE); + transfer::public_transfer(quote_out, ALICE); + transfer::public_transfer(deep_out, ALICE); + return_shared(pool); + return_shared(clock); + }; + + // Maker's referral should have received the fees for 100 SUI + let fee_per_unit = math::mul(constants::deep_multiplier(), maker_fee_rate); + let expected_deep = math::mul(quantity, fee_per_unit); + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + assert_eq!(deep, expected_deep); + assert_eq!(base, 0); + assert_eq!(quote, 0); + return_shared(referral); + return_shared(pool); + }; + + end(test); +} + +// Insufficient DEEP for the referral fee lock causes the order placement to abort. +// The balance manager has enough DEEP for the protocol maker fee but not for the +// additional referral lock, so the transaction should fail. +#[test, expected_failure(abort_code = ::deepbook::balance_manager::EBalanceManagerBalanceTooLow)] +fun maker_referral_insufficient_deep_for_lock_e() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + + // Create BOB's balance manager with precise amounts: + // Enough SUI for collateral, enough DEEP for protocol fee, but NOT for referral lock. + // For 100 SUI ask at $3 with pay_with_deep: + // protocol_maker_fee = mul(mul(100e9, 100e9), 500_000) = 5_000_000_000 + // referral_lock = mul(mul(100e9, 100e9), 1_000_000) = 10_000_000_000 + // total DEEP needed = 15_000_000_000 + // Give BOB exactly 10_000_000_000 DEEP (enough for protocol fee, not enough for both) + let balance_manager_id_bob; + test.next_tx(BOB); + { + let mut bm = balance_manager::new(test.ctx()); + deposit_into_account(&mut bm, 1_000_000 * constants::float_scaling(), &mut test); + deposit_into_account(&mut bm, 1_000_000 * constants::float_scaling(), &mut test); + deposit_into_account(&mut bm, 10_000_000_000, &mut test); + let trade_cap = bm.mint_trade_cap(test.ctx()); + transfer::public_transfer(trade_cap, BOB); + balance_manager_id_bob = bm.id(); + transfer::public_share_object(bm); + }; + + let maker_fee_rate = 1_000_000; + let (_, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + // This should abort: BOB has 10e9 DEEP, protocol fee takes 5e9, + // leaving 5e9 which is less than the 10e9 referral lock. + let price = 3 * constants::float_scaling(); + let quantity = 100 * constants::float_scaling(); + place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, + true, + constants::max_u64(), + &mut test, + ); + + end(test); +} + +// Multiple small partial fills followed by a completion fill. +// Verifies that locked_balance decreases correctly after each partial fill, +// and on completion the entire remaining locked_balance is swept to the referral. +// Total referral rewards must exactly equal the original lock. +#[test] +fun maker_referral_many_partial_fills_then_complete_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let balance_manager_id_carol = create_acct_and_share_with_funds_typed( + CAROL, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let maker_fee_rate = 1_000_000; + let (referral_id, _) = setup_maker_referral( + pool_id, + balance_manager_id_bob, + 0, + maker_fee_rate, + &mut test, + ); + + // BOB places ask for 100 SUI at $3 + let price = 3 * constants::float_scaling(); + let total_quantity = 100 * constants::float_scaling(); + + place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + total_quantity, + false, + true, + constants::max_u64(), + &mut test, + ); + + let fee_per_unit = math::mul(constants::deep_multiplier(), maker_fee_rate); + let original_lock = math::mul(total_quantity, fee_per_unit); + + // Four small partial fills of 10 SUI each from different takers + let small_fill = 10 * constants::float_scaling(); + let fee_per_fill = math::mul(small_fill, fee_per_unit); + + // Fill 1 (ALICE) + place_market_order( + ALICE, + pool_id, + balance_manager_id_alice, + 2, + constants::self_matching_allowed(), + small_fill, + true, + true, + &mut test, + ); + test.next_tx(BOB); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (_, _, deep) = pool.get_pool_referral_balances(&referral); + assert_eq!(deep, fee_per_fill); + return_shared(referral); + return_shared(pool); + }; + + // Fill 2 (CAROL) + place_market_order( + CAROL, + pool_id, + balance_manager_id_carol, + 3, + constants::self_matching_allowed(), + small_fill, + true, + true, + &mut test, + ); + test.next_tx(BOB); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (_, _, deep) = pool.get_pool_referral_balances(&referral); + assert_eq!(deep, 2 * fee_per_fill); + return_shared(referral); + return_shared(pool); + }; + + // Fill 3 (ALICE) + place_market_order( + ALICE, + pool_id, + balance_manager_id_alice, + 4, + constants::self_matching_allowed(), + small_fill, + true, + true, + &mut test, + ); + + // Fill 4 (CAROL) + place_market_order( + CAROL, + pool_id, + balance_manager_id_carol, + 5, + constants::self_matching_allowed(), + small_fill, + true, + true, + &mut test, + ); + + // 40 SUI filled so far, 60 SUI remaining + test.next_tx(BOB); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (_, _, deep) = pool.get_pool_referral_balances(&referral); + assert_eq!(deep, 4 * fee_per_fill); + return_shared(referral); + + // Verify locked_balance still accounts for remaining 60 SUI + let balance_manager = test.take_shared_by_id(balance_manager_id_bob); + let (_, _, deep_locked) = pool.locked_balance(&balance_manager); + let remaining_lock = original_lock - 4 * fee_per_fill; + assert!(deep_locked >= remaining_lock); + return_shared(balance_manager); + return_shared(pool); + }; + + // Final fill: 60 SUI (completion) — triggers sweep of remaining locked_balance + let final_fill = 60 * constants::float_scaling(); + place_market_order( + ALICE, + pool_id, + balance_manager_id_alice, + 6, + constants::self_matching_allowed(), + final_fill, + true, + true, + &mut test, + ); + + // Total referral rewards should exactly equal the original lock + test.next_tx(BOB); + { + let pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + assert_eq!(deep, original_lock); + assert_eq!(base, 0); + assert_eq!(quote, 0); + return_shared(referral); + return_shared(pool); + }; + + end(test); +} From 88d8d258bffacd8e5994050c26d0acf0f1e66387 Mon Sep 17 00:00:00 2001 From: bathord Date: Mon, 16 Mar 2026 21:19:18 +0300 Subject: [PATCH 7/9] Add maker referral fees architecture diagrams to docs --- .../cancel-modify.png | Bin 0 -> 57816 bytes .../order-execution.png | Bin 0 -> 33106 bytes .../order-expiry.png | Bin 0 -> 27345 bytes .../order-placement.png | Bin 0 -> 39259 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/maker-referral-fees-architecture/cancel-modify.png create mode 100644 docs/maker-referral-fees-architecture/order-execution.png create mode 100644 docs/maker-referral-fees-architecture/order-expiry.png create mode 100644 docs/maker-referral-fees-architecture/order-placement.png diff --git a/docs/maker-referral-fees-architecture/cancel-modify.png b/docs/maker-referral-fees-architecture/cancel-modify.png new file mode 100644 index 0000000000000000000000000000000000000000..2e2fe70669393f9fff632b7a38a22f4e01d880a6 GIT binary patch literal 57816 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

o65PGh<>&NrYc{FN`yegW+yFUmG}3N!m(I5h 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∾nrnOj0g-pe|2xgkKR%kqTfR0k? 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⪻pbk%cwqBJtX21)(~c?*e#j4 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 literal 0 HcmV?d00001 diff --git a/docs/maker-referral-fees-architecture/order-placement.png b/docs/maker-referral-fees-architecture/order-placement.png new file mode 100644 index 0000000000000000000000000000000000000000..5d461b127a18a1aaaa620d5dc15e75dc60212553 GIT binary patch literal 39259 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%0A4`G6aLiV&3jTd30zf^@Nw7m*SWfvM> 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 Date: Wed, 18 Mar 2026 16:41:29 +0300 Subject: [PATCH 8/9] Add in-code comments to improve clarity --- packages/deepbook/sources/pool.move | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/deepbook/sources/pool.move b/packages/deepbook/sources/pool.move index bee21c9ca..2063963bf 100644 --- a/packages/deepbook/sources/pool.move +++ b/packages/deepbook/sources/pool.move @@ -1562,6 +1562,7 @@ public fun locked_balance( quote_quantity = quote_quantity + locked_balance.quote(); deep_quantity = deep_quantity + locked_balance.deep(); + // Include the pre-locked maker referral fee for this order, if any. let key = MakerReferralKey(order.order_id()); if (self.id.exists_(key)) { let info: &MakerReferralInfo = self.id.borrow(key); @@ -2122,18 +2123,22 @@ fun process_referral_fees( /// Pre-lock the maker referral fee into the Vault when a limit order rests /// in the book. The fee is calculated from the effective rate (volume-based + /// multiplier on protocol maker fee) and the order's remaining quantity. +/// A `MakerReferralInfo` dynamic field keyed by order ID is attached to the pool for settlement. fun lock_maker_referral_fee( self: &mut Pool, order_info: &OrderInfo, balance_manager: &mut BalanceManager, trade_proof: &TradeProof, ) { + // Only resting (inserted) orders have a maker phase. if (!order_info.order_inserted()) return; + // Skip if the balance manager has no referral on this pool. let referral_id = balance_manager.get_balance_manager_referral_id(self.id()); if (referral_id.is_none()) return; let referral_id = referral_id.destroy_some(); + // Volume-based bps rate configured by the referral owner; 0 if not set. let maker_fee_rate = if (self.id.exists_(ReferralFeeConfigKey(referral_id))) { let config: &ReferralFeeConfig = self.id.borrow(ReferralFeeConfigKey(referral_id)); config.maker_fee_rate @@ -2144,6 +2149,7 @@ fun lock_maker_referral_fee( let referral_rewards: &ReferralRewards = self.id.borrow(referral_id); let multiplier = referral_rewards.multiplier; + // Effective rate combines the configured bps fee and a scaled protocol maker fee. let protocol_maker_fee = { let inner = self.load_inner(); inner.state.governance().trade_params().maker_fee() @@ -2152,6 +2158,7 @@ fun lock_maker_referral_fee( let effective_rate = maker_fee_rate + math::mul(protocol_maker_fee, multiplier); if (effective_rate == 0) return; + // Compute the fee on the full remaining quantity at order price. let remaining_qty = order_info.remaining_quantity(); let price = order_info.price(); let is_bid = order_info.is_bid(); @@ -2167,6 +2174,7 @@ fun lock_maker_referral_fee( let locked_amount = fee_balances.non_zero_value(); if (locked_amount == 0) return; + // Asset in which the fee is denominated: DEEP, quote (bid), or base (ask). let referral_owed = if (fee_is_deep) { balances::new(0, 0, locked_amount) } else if (is_bid) { @@ -2175,6 +2183,7 @@ fun lock_maker_referral_fee( balances::new(locked_amount, 0, 0) }; + // Pre-lock the fee: transfer it from the balance manager to the Vault. { let inner = self.load_inner_mut(); inner @@ -2187,6 +2196,7 @@ fun lock_maker_referral_fee( ); }; + // Attach referral metadata to the pool (keyed by order ID) for future settlement. self .id .add( @@ -2221,9 +2231,11 @@ fun process_maker_referral_fills( let key = MakerReferralKey(fill.maker_order_id()); if (self.id.exists_(key)) { + // This maker order has a referral fee pre-locked in the Vault. let info: MakerReferralInfo = *self.id.borrow(key); if (fill.expired()) { + // Order expired before filling; return the locked fee to the maker. let refund = if (info.fee_is_deep) { balances::new(0, 0, info.locked_balance) } else if (info.is_bid) { @@ -2241,8 +2253,10 @@ fun process_maker_referral_fills( ctx, ); }; + // Remove the DF since the order is gone. df::remove(&mut self.id, key); } else { + // Compute the fee owed for this fill's quantity. let mut fee_balances = fill .maker_deep_price() .fee_quantity( @@ -2251,6 +2265,8 @@ fun process_maker_referral_fills( info.is_bid, ); fee_balances.mul(info.effective_rate); + // On the final fill use the full remaining locked balance to avoid dust. + // On a partial fill use the proportional amount for this fill's quantity. let transfer_amount = if (fill.completed()) { info.locked_balance } else { @@ -2258,6 +2274,7 @@ fun process_maker_referral_fills( }; if (transfer_amount > 0) { + // Withdraw from the Vault and credit the referral's rewards balance. if (info.fee_is_deep) { let bal = { let inner = self.load_inner_mut(); @@ -2287,6 +2304,7 @@ fun process_maker_referral_fills( rewards.base.join(bal); }; + // Emit referral fee event with the transferred amount. let (base_fee, quote_fee, deep_fee) = if (info.fee_is_deep) { (0, 0, transfer_amount) } else if (info.is_bid) { @@ -2304,8 +2322,10 @@ fun process_maker_referral_fills( }; if (fill.completed()) { + // Order fully filled; remove the DF. df::remove(&mut self.id, key); } else if (transfer_amount > 0) { + // Order still live; reduce the locked balance by what was just transferred. let info_mut: &mut MakerReferralInfo = self.id.borrow_mut(key); info_mut.locked_balance = info_mut.locked_balance - transfer_amount; }; From 4e676fb8261086b9d599a2231c84e9528f5a87f9 Mon Sep 17 00:00:00 2001 From: bathord Date: Wed, 18 Mar 2026 18:15:58 +0300 Subject: [PATCH 9/9] Add max fills test cases --- .../deepbook/tests/maker_referral_tests.move | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) diff --git a/packages/deepbook/tests/maker_referral_tests.move b/packages/deepbook/tests/maker_referral_tests.move index 2b951d62f..222acd52e 100644 --- a/packages/deepbook/tests/maker_referral_tests.move +++ b/packages/deepbook/tests/maker_referral_tests.move @@ -3552,3 +3552,217 @@ fun maker_referral_many_partial_fills_then_complete_ok() { end(test); } + +// === Group 9: Max Fills Stress Tests === + +// Verifies that a taker order matching exactly MAX_FILLS (100) maker orders without any +// referrals completes successfully and the taker receives the full quantity. +#[test] +fun max_fills_no_referrals_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let price = 2 * constants::float_scaling(); + let quantity = 1 * constants::float_scaling(); + let num_orders = constants::max_fills(); + + // BOB places 100 ask orders at $2 (no referral set) + let mut i = 0; + while (i < num_orders) { + place_limit_order( + BOB, + pool_id, + balance_manager_id_bob, + i + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, // ask + true, // pay_with_deep + constants::max_u64(), + &mut test, + ); + i = i + 1; + }; + + // ALICE places a single bid that sweeps all 100 asks in one matching pass + let total_quantity = num_orders * quantity; + let order_info = place_limit_order( + ALICE, + pool_id, + balance_manager_id_alice, + num_orders + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + total_quantity, + true, // bid + true, + constants::max_u64(), + &mut test, + ); + + // All 100 orders were matched; taker is fully filled and not inserted into the book + assert_eq!(order_info.executed_quantity(), total_quantity); + assert!(!order_info.order_inserted()); + + end(test); +} + +// Verifies that a taker order matching exactly MAX_FILLS (100) maker orders where every +// maker has a distinct referral completes successfully and each referral receives the +// correct fee. The referral_id is snapshotted into MakerReferralInfo at placement time, +// so swapping BOB's referral between placements produces 100 independent reward buckets. +#[test] +fun max_fills_all_different_referrals_ok() { + let mut test = begin(OWNER); + let registry_id = setup_test(OWNER, &mut test); + let balance_manager_id_alice = create_acct_and_share_with_funds_typed( + ALICE, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + let pool_id = setup_pool_with_default_fees_and_reference_pool( + ALICE, + registry_id, + balance_manager_id_alice, + &mut test, + ); + let balance_manager_id_bob = create_acct_and_share_with_funds_typed( + BOB, + 1_000_000 * constants::float_scaling(), + &mut test, + ); + + let price = 2 * constants::float_scaling(); + let quantity = 1 * constants::float_scaling(); + let maker_fee_rate = 1_000_000; // 10 bps + let num_orders = constants::max_fills(); + + // For each of the 100 orders: + // 1. ALICE mints a fresh referral and configures its maker_fee_rate. + // 2. BOB updates his balance manager referral to this new one, then places the ask. + // Because the referral_id is captured in MakerReferralInfo at placement time, each + // resting order points to a different referral object. + let mut referral_ids: vector = vector[]; + let mut i = 0; + while (i < num_orders) { + // Mint a new referral (ALICE is the referral owner) + let mut referral_id; + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + referral_id = pool.mint_referral(0, test.ctx()); + return_shared(pool); + }; + + // Configure the maker_fee_rate on the new referral + test.next_tx(ALICE); + { + let mut pool = test.take_shared_by_id>(pool_id); + let referral = test.take_shared_by_id(referral_id); + pool.update_pool_referral_fee_rate(&referral, 0, maker_fee_rate, test.ctx()); + return_shared(referral); + return_shared(pool); + }; + + // BOB sets this referral on his BM and places one ask order in the same tx, + // so the MakerReferralInfo DF for this order captures the new referral_id. + test.next_tx(BOB); + { + let mut pool = test.take_shared_by_id>(pool_id); + let clock = test.take_shared(); + let mut balance_manager = test.take_shared_by_id( + balance_manager_id_bob, + ); + let referral = test.take_shared_by_id(referral_id); + let trade_cap = test.take_from_sender(); + balance_manager.set_balance_manager_referral(&referral, &trade_cap); + let trade_proof = balance_manager.generate_proof_as_owner(test.ctx()); + pool.place_limit_order( + &mut balance_manager, + &trade_proof, + i + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + quantity, + false, // ask + true, // pay_with_deep + constants::max_u64(), + &clock, + test.ctx(), + ); + test.return_to_sender(trade_cap); + return_shared(referral); + return_shared(pool); + return_shared(clock); + return_shared(balance_manager); + }; + + referral_ids.push_back(referral_id); + i = i + 1; + }; + + // ALICE sweeps all 100 asks in a single taker bid + let total_quantity = num_orders * quantity; + let order_info = place_limit_order( + ALICE, + pool_id, + balance_manager_id_alice, + num_orders + 1, + constants::no_restriction(), + constants::self_matching_allowed(), + price, + total_quantity, + true, // bid + true, + constants::max_u64(), + &mut test, + ); + + // Taker was fully filled across all 100 maker orders + assert_eq!(order_info.executed_quantity(), total_quantity); + assert!(!order_info.order_inserted()); + + // Each referral should have received exactly the fee for 1 SUI filled: + // fee_per_order = mul(mul(quantity, deep_multiplier), maker_fee_rate) + let fee_per_order = math::mul( + math::mul(quantity, constants::deep_multiplier()), + maker_fee_rate, + ); + test.next_tx(ALICE); + { + let pool = test.take_shared_by_id>(pool_id); + let mut j = 0; + while (j < num_orders) { + let referral = test.take_shared_by_id(referral_ids[j]); + let (base, quote, deep) = pool.get_pool_referral_balances(&referral); + assert_eq!(deep, fee_per_order); + assert_eq!(base, 0); + assert_eq!(quote, 0); + return_shared(referral); + j = j + 1; + }; + return_shared(pool); + }; + + end(test); +}