diff --git a/deepbook/sources/deepbook.move b/deepbook/sources/deepbook.move index 0fbaab00..57e6914c 100644 --- a/deepbook/sources/deepbook.move +++ b/deepbook/sources/deepbook.move @@ -133,7 +133,7 @@ module deepbook::deepbook { expire_timestamp: u64, // Expiration timestamp in ms clock: &Clock, ctx: &mut TxContext, - ): u128 { + ): (u64, u64, u128) { pool.place_limit_order( account, proof, @@ -151,13 +151,15 @@ module deepbook::deepbook { public fun place_market_order( pool: &mut Pool, account: &mut Account, + proof: &TradeProof, client_order_id: u64, quantity: u64, is_bid: bool, ctx: &mut TxContext, - ): u128 { + ): (u64, u64) { pool.place_market_order( account, + proof, client_order_id, quantity, is_bid, diff --git a/deepbook/sources/helper/big_vector.move b/deepbook/sources/helper/big_vector.move index f9ff41af..13f27480 100644 --- a/deepbook/sources/helper/big_vector.move +++ b/deepbook/sources/helper/big_vector.move @@ -251,6 +251,52 @@ module deepbook::big_vector { &mut slice[offset] } + /// This assumes SliceRef is not null. Returns value at offset `offset` in slice `ref` + public fun borrow_mut_ref_offset(self: &mut BigVector, ref: SliceRef, offset: u64): &mut E { + let slice = self.borrow_slice_mut(ref); + &mut slice[offset] + } + + /// Return whether there is a valid next value in BigVector + public fun valid_next(self: &BigVector, ref: SliceRef, offset: u64): bool { + let slice = self.borrow_slice(ref); + (offset + 1 < slice.vals.length() || !slice.next().is_null()) + } + + /// Gets the next value within slice if exists, if at maximum gets the next element of the next slice + /// Assumes valid_next is true + public fun borrow_mut_next(self: &mut BigVector, ref: SliceRef, offset: u64): (SliceRef, u64, &mut E) { + let slice = self.borrow_slice_mut(ref); + if (offset + 1 < slice.vals.length()) { + (ref, offset + 1, &mut slice[offset + 1]) + } else { + let next_ref = slice.next(); + let next_slice = self.borrow_slice_mut(next_ref); + (next_ref, 0, &mut next_slice.vals[0]) + } + } + + /// Return whether there is a valid prev value in BigVector + public fun valid_prev(self: &BigVector, ref: SliceRef, offset: u64): bool { + let slice = self.borrow_slice(ref); + (offset > 0 || !slice.prev().is_null()) + } + + /// Gets the prev value within slice if exists, if at minimum gets the last element of the prev slice + /// Assumes valid_prev is true + public fun borrow_prev_mut(self: &mut BigVector, ref: SliceRef, offset: u64): (SliceRef, u64, &mut E) { + let slice = self.borrow_slice_mut(ref); + if (offset > 0) { + (ref, offset - 1, &mut slice[offset - 1]) + } else { + let prev_ref = slice.prev(); + // Borrow the previous slice and get the last element + let prev_slice = self.borrow_slice_mut(prev_ref); + let last_index = prev_slice.vals.length() - 1; + (prev_ref, last_index, &mut prev_slice[last_index]) + } + } + // === BigVector Mutators === /// Add `val` to `self` at index `key`. Aborts if `key` is already @@ -398,6 +444,35 @@ module deepbook::big_vector { } } + public fun slice_before( + self: &BigVector, + key: u128, + ): (SliceRef, u64) { + if (self.root_id == NO_SLICE) { + return (SliceRef { ix: NO_SLICE }, 0) + }; + + let (ix, leaf, off) = self.find_leaf(key); + + // If the key index is 0 or the key is less than the first key in the leaf + if (off == 0 || key < leaf.keys[0]) { + let prev_ref = leaf.prev(); + if (prev_ref.is_null()) { + // If there is no previous slice, return NO_SLICE + (SliceRef { ix: NO_SLICE }, 0) + } else { + // Borrow the previous slice to get the last key's index + let prev_slice = self.borrow_slice(prev_ref); + (prev_ref, prev_slice.keys.length() - 1) + } + } else { + // Return the current slice with the index decremented by one if the key does not exactly match + // or use the found offset otherwise + let actual_offset = off - 1; + (SliceRef { ix }, actual_offset) + } + } + /// Borrow a slice from this vector. public fun borrow_slice( self: &BigVector, diff --git a/deepbook/sources/helper/math.move b/deepbook/sources/helper/math.move index 03563173..910e2888 100644 --- a/deepbook/sources/helper/math.move +++ b/deepbook/sources/helper/math.move @@ -9,6 +9,25 @@ module deepbook::math { const EUnderflow: u64 = 1; // <<<<<<<<<<<<<<<<<<<<<<<< Error codes <<<<<<<<<<<<<<<<<<<<<<<< + // multiply two floating numbers and assert the result is non zero + // Note that this function will still round down + public(package) fun mul(x: u64, y: u64): u64 { + let (_, result) = unsafe_mul_round(x, y); + assert!(result > 0, EUnderflow); + result + } + + // TODO: verify logic here + public(package) fun mul_round_up(x: u64, y: u64): u64 { + let (is_round_down, result) = unsafe_mul_round(x, y); + assert!(result > 0, EUnderflow); + if (is_round_down) { + result + 1 + } else { + result + } + } + // multiply two floating numbers // also returns whether the result is rounded down public(package) fun unsafe_mul_round(x: u64, y: u64): (bool, u64) { @@ -19,14 +38,30 @@ module deepbook::math { (is_round_down, (x * y / FLOAT_SCALING_U128) as u64) } - // multiply two floating numbers and assert the result is non zero - // Note that this function will still round down - public(package) fun mul(x: u64, y: u64): u64 { - let (_, result) = unsafe_mul_round(x, y); - assert!(result > 0, EUnderflow); + /// divide two floating numbers + public(package) fun div(x: u64, y: u64): u64 { + let (_, result) = unsafe_div_round(x, y); result } + /// divide two floating numbers + /// also returns whether the result is rounded down + public(package) fun unsafe_div_round(x: u64, y: u64): (bool, u64) { + let x = x as u128; + let y = y as u128; + let mut is_round_down = true; + if ((x * FLOAT_SCALING_U128 % y) == 0) is_round_down = false; + (is_round_down, (x * FLOAT_SCALING_U128 / y) as u64) + } + + public(package) fun min(x: u64, y: u64): u64 { + if (x <= y) { + x + } else { + y + } + } + public(package) fun max(x: u64, y: u64): u64 { if (x > y) { x diff --git a/deepbook/sources/pool/account.move b/deepbook/sources/pool/account.move index 18184291..24fa5b69 100644 --- a/deepbook/sources/pool/account.move +++ b/deepbook/sources/pool/account.move @@ -1,10 +1,10 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -/// The Account is a shared object that holds all of the balances for a user. A combination of `Account` and -/// `TradeProof` are passed into a pool to perform trades. A `TradeProof` can be generated in two ways: by the -/// owner directly, or by any `TradeCap` owner. The owner can generate a `TradeProof` without the risk of -/// equivocation. The `TradeCap` owner, due to it being an owned object, risks equivocation when generating +/// The Account is a shared object that holds all of the balances for a user. A combination of `Account` and +/// `TradeProof` are passed into a pool to perform trades. A `TradeProof` can be generated in two ways: by the +/// owner directly, or by any `TradeCap` owner. The owner can generate a `TradeProof` without the risk of +/// equivocation. The `TradeCap` owner, due to it being an owned object, risks equivocation when generating /// a `TradeProof`. Generally, a high frequency trading engine will trade as the default owner. module deepbook::account { use sui::{ @@ -23,7 +23,7 @@ module deepbook::account { const MAX_TRADE_CAPS: u64 = 1000; - /// A shared object that is passed into pools for placing orders. + /// A shared object that is passed into pools for placing orders. public struct Account has key { id: UID, owner: address, @@ -55,6 +55,7 @@ module deepbook::account { } } + #[allow(lint(share_owned))] public fun share(account: Account) { transfer::share_object(account); } diff --git a/deepbook/sources/pool/pool.move b/deepbook/sources/pool/pool.move index de1473d8..35a9cd14 100644 --- a/deepbook/sources/pool/pool.move +++ b/deepbook/sources/pool/pool.move @@ -42,9 +42,12 @@ module deepbook::pool { // <<<<<<<<<<<<<<<<<<<<<<<< Constants <<<<<<<<<<<<<<<<<<<<<<<< const POOL_CREATION_FEE: u64 = 100 * 1_000_000_000; // 100 SUI, can be updated const TREASURY_ADDRESS: address = @0x0; // TODO: if different per pool, move to pool struct + // Assuming 10k orders per second in a pool, would take over 50 million years to overflow const START_BID_ORDER_ID: u64 = (1u128 << 64 - 1) as u64; const START_ASK_ORDER_ID: u64 = 1; const MIN_ASK_ORDER_ID: u128 = 1 << 127; + const MIN_ORDER_ID: u128 = 0; + const MAX_ORDER_ID: u128 = 1 << 128 - 1; const MIN_PRICE: u64 = 1; const MAX_PRICE: u64 = (1u128 << 63 - 1) as u64; @@ -73,7 +76,6 @@ module deepbook::pool { /// owner ID of the `AccountCap` that placed the order owner: address, original_quantity: u64, - base_asset_quantity_placed: u64, price: u64, expire_timestamp: u64 } @@ -94,6 +96,26 @@ module deepbook::pool { price: u64 } + /// Emitted when a maker order is filled. + public struct OrderFilled has copy, store, drop { + /// object ID of the pool the order was placed on + pool_id: ID, + /// ID of the order within the pool + maker_order_id: u128, + /// ID of the taker order + taker_order_id: u128, + /// ID of the order defined by maker client + maker_client_order_id: u64, + /// ID of the order defined by taker client + taker_client_order_id: u64, + base_quantity: u64, + quote_quantity: u64, + price: u64, + maker_address: address, + taker_address: address, + is_bid: bool, + } + // <<<<<<<<<<<<<<<<<<<<<<<< Structs <<<<<<<<<<<<<<<<<<<<<<<< /// Temporary to represent DEEP token, remove after we have the open-sourced the DEEP token contract @@ -105,7 +127,7 @@ module deepbook::pool { // ID of the order within the pool order_id: u128, // ID of the order defined by client - client_order_id: u64, // TODO: What does this ID do? + client_order_id: u64, // Price, only used for limit orders price: u64, // Quantity (in base asset terms) when the order is placed @@ -157,6 +179,8 @@ module deepbook::pool { // <<<<<<<<<<<<<<<<<<<<<<<< Package Functions <<<<<<<<<<<<<<<<<<<<<<<< /// Place a limit order to the order book. + /// Will return (settled_base_quantity, settled_quote_quantity, order_id + /// if limit order placed and 0 otherwise) public(package) fun place_limit_order( self: &mut Pool, account: &mut Account, @@ -168,7 +192,7 @@ module deepbook::pool { expire_timestamp: u64, // Expiration timestamp in ms clock: &Clock, ctx: &mut TxContext, - ): u128 { + ): (u64, u64, u128) { // Refresh state as necessary if first order of epoch self.refresh(ctx); @@ -178,63 +202,364 @@ module deepbook::pool { assert!(quantity % self.lot_size == 0, EOrderInvalidLotSize); assert!(expire_timestamp > clock.timestamp_ms(), EInvalidExpireTimestamp); - // Calculate total Base, Quote, DEEP to be transferred. - let maker_fee = self.pool_state.maker_fee(); - let (base_quantity, quote_quantity) = if (is_bid) { - (0, math::mul(quantity, price)) + let order_id = encode_order_id(is_bid, price, self.get_order_id(is_bid)); + let (net_base_quantity, net_quote_quantity) = + if (is_bid) { + self.match_bid(account.owner(), order_id, client_order_id, quantity, ctx) + } else { + self.match_ask(account.owner(), order_id, client_order_id, quantity, ctx) + }; + + let (settled_base_quantity, settled_quote_quantity) = if (is_bid) { + (net_base_quantity, 0) } else { - (quantity, 0) + (0, net_quote_quantity) }; - let (base_fee, quote_fee, deep_fee) = if (self.fee_is_deep()) { - let config = self.deep_config.borrow(); - let quote_fee = math::mul(config.deep_per_base(), base_quantity) + - math::mul(config.deep_per_quote(), quote_quantity); - - (0, 0, quote_fee) + + self.transfer_taker(account, proof, net_base_quantity, net_quote_quantity, is_bid, ctx); + let remaining_quantity = quantity - net_base_quantity; + if (remaining_quantity == 0) { + (settled_base_quantity, settled_quote_quantity, 0) + } else { + let fee_quantity = self.transfer_maker(account, proof, remaining_quantity, price, is_bid, ctx); + + self.internal_inject_limit_order( + order_id, + client_order_id, + price, + remaining_quantity, + fee_quantity, + is_bid, + expire_timestamp, + ctx + ); + + event::emit(OrderPlaced { + pool_id: self.id.to_inner(), + order_id, + client_order_id, + is_bid, + owner: account.owner(), + original_quantity: remaining_quantity, + price, + expire_timestamp, + }); + + (settled_base_quantity, settled_quote_quantity, order_id) + } + } + + /// Given output from order matching, deposits assets from account into pool and withdraws from pool to account + fun transfer_taker( + self: &mut Pool, + account: &mut Account, + proof: &TradeProof, + net_base_quantity: u64, + net_quote_quantity: u64, + is_bid: bool, + ctx: &mut TxContext, + ) { + // Discounted taker fees if above minimum stake + let (cur_stake, _) = self.users[account.owner()].stake(); + let above_min_stake = (cur_stake >= self.pool_state.stake_required()); + let taker_fee = if (above_min_stake) { + math::div(self.pool_state.taker_fee(), 2) } else { - let base_fee = math::mul(base_quantity, maker_fee); // TODO: taker_fee, decimal places - let quote_fee = math::mul(quote_quantity, maker_fee); - - (base_fee, quote_fee, 0) + self.pool_state.taker_fee() }; - // Transfer Base, Quote, Fee to Pool. - if (base_quantity + base_fee > 0) self.deposit_base(account, proof, base_quantity + base_fee, ctx); - if (quote_quantity + quote_fee > 0) self.deposit_quote(account, proof, quote_quantity + quote_fee, ctx); - if (deep_fee > 0) self.deposit_deep(account, proof, deep_fee, ctx); + if (is_bid) { + // transfer quote out from account to pool, transfer base from pool to account + if (self.fee_is_deep()) { + let deep_quantity = math::mul(taker_fee, math::mul(net_quote_quantity, self.deep_config.borrow().deep_per_quote())); + self.deposit_deep(account, proof, deep_quantity, ctx); + self.deposit_quote(account, proof, net_quote_quantity, ctx); + } else { + let quote_fee = math::mul(taker_fee, net_quote_quantity); + self.deposit_quote(account, proof, net_quote_quantity + quote_fee, ctx); + }; + self.withdraw_base(account, proof, net_base_quantity, ctx); + } else { + // transfer base out from account to pool, transfer quote from pool to account + if (self.fee_is_deep()) { + let deep_quantity = math::mul(taker_fee, math::mul(net_base_quantity, self.deep_config.borrow().deep_per_base())); + self.deposit_deep(account, proof, deep_quantity, ctx); + self.deposit_base(account, proof, net_base_quantity, ctx); + } else { + let base_fee = math::mul(taker_fee, net_base_quantity); + self.deposit_quote(account, proof, net_base_quantity + base_fee, ctx); + }; + self.withdraw_base(account, proof,net_quote_quantity, ctx); + }; + } - let place_quantity = math::max(base_quantity, quote_quantity); - let fee_quantity = math::max(math::max(base_fee, quote_fee), deep_fee); - let order_id = self.internal_place_limit_order(client_order_id, price, place_quantity, fee_quantity, is_bid, expire_timestamp, ctx); - event::emit(OrderPlaced { - pool_id: self.id.to_inner(), - order_id, - client_order_id, - is_bid, - owner: account.owner(), - original_quantity: quantity, - base_asset_quantity_placed: quantity, - price, - expire_timestamp: 0, // TODO - }); + /// Given output from order matching, deposits assets from account into pool to prepare order placement. Returns fee quantity + fun transfer_maker( + self: &mut Pool, + account: &mut Account, + proof: &TradeProof, + remaining_quantity: u64, + price: u64, + is_bid: bool, + ctx: &mut TxContext, + ): u64 { + let maker_fee = self.pool_state.maker_fee(); + let quote_quantity = math::mul(remaining_quantity, price); - order_id + if (is_bid) { + // transfer quote out from account to pool, transfer base from pool to account + if (self.fee_is_deep()) { + let deep_quantity = math::mul(maker_fee, math::mul(quote_quantity, self.deep_config.borrow().deep_per_quote())); + self.deposit_deep(account, proof, deep_quantity, ctx); + self.deposit_quote(account, proof, quote_quantity, ctx); + deep_quantity + } else { + let quote_fee = math::mul(maker_fee, quote_quantity); + self.deposit_quote(account, proof, quote_quantity + quote_fee, ctx); + quote_fee + } + } else { + // transfer base out from account to pool, transfer quote from pool to account + if (self.fee_is_deep()) { + let deep_quantity = math::mul(maker_fee, math::mul(remaining_quantity, self.deep_config.borrow().deep_per_base())); + self.deposit_deep(account, proof, deep_quantity, ctx); + self.deposit_base(account, proof, remaining_quantity, ctx); + deep_quantity + } else { + let base_fee = math::mul(maker_fee, remaining_quantity); + self.deposit_quote(account, proof, remaining_quantity + base_fee, ctx); + base_fee + } + } + } + + /// Matches bid, returns (base_quantity_matched, quote_quantity_matched) + fun match_bid( + self: &mut Pool, + taker: address, + order_id: u128, + client_order_id: u64, + quantity: u64, // in base asset + ctx: &TxContext, + ): (u64, u64) { + let (mut ref, mut offset) = self.asks.slice_following(MIN_ORDER_ID); + // This means there are no asks in the book + if (ref.is_null()) { + return (0, 0) + }; + + let mut remaining_quantity = quantity; + let mut net_base_quantity = 0; + let mut net_quote_quantity = 0; + let mut matched_orders = vector[]; + + // Fetches initial order + let mut ask = self.asks.borrow_mut_ref_offset(ref, offset); + while (remaining_quantity > 0 && order_id > ask.order_id) { + // Match with existing asks + // We want to buy 1 BTC, if there's 0.5BTC at $50k, we want to buy 0.5BTC at $50k + let base_matched_quantity = math::min(ask.quantity, remaining_quantity); + ask.quantity = ask.quantity - base_matched_quantity; + remaining_quantity = remaining_quantity - base_matched_quantity; + // fee_subtracted is rounded down (in case of very small fills, this can be 0) + let fee_subtracted = math::div(math::mul(base_matched_quantity, ask.original_fee_quantity), ask.original_quantity); + ask.fee_quantity = ask.fee_quantity - fee_subtracted; + + // Rounded up, because maker gets rounding advantage + let quote_quantity = math::mul_round_up(base_matched_quantity, ask.price); + + // refresh this user as necessary + let ask_owner = &mut self.users[ask.owner]; + ask_owner.refresh(ctx); + // Update maker quote balances + ask_owner.add_settled_quote_amount(quote_quantity); + // Update individual maker volume + ask_owner.increase_maker_volume(base_matched_quantity); + // Update all volume + self.pool_state.increase_maker_volume(base_matched_quantity); + + // Check if user is a valid staker. Could separate out into a function + let (cur_stake, _) = ask_owner.stake(); + if (cur_stake >= self.pool_state.stake_required()) { + self.pool_state.increase_staked_maker_volume(base_matched_quantity); + }; + + event::emit(OrderFilled{ + pool_id: self.id.to_inner(), + maker_order_id: ask.order_id, + taker_order_id: order_id, + maker_client_order_id: ask.client_order_id, + taker_client_order_id: client_order_id, + base_quantity: base_matched_quantity, + quote_quantity, + price: ask.price, + maker_address: ask.owner, + taker_address: taker, + is_bid: true, // is a bid + }); + + net_base_quantity = net_base_quantity + base_matched_quantity; + net_quote_quantity = net_quote_quantity + quote_quantity; + // If ask quantity is 0, remove the order + if (ask.quantity == 0) { + // Remove order from user's open orders + ask_owner.remove_open_order(ask.order_id); + // Add order id to be removed + matched_orders.push_back(ask.order_id); + }; + + // Traverse to valid next order if exists, otherwise break from loop + if (self.asks.valid_next(ref, offset)){ + (ref, offset, ask) = self.asks.borrow_mut_next(ref, offset); + } else { + break + } + }; + + // Iterate over matched_orders and remove from asks + let mut i = 0; + while (i < matched_orders.length()) { + self.asks.remove(matched_orders[i]); + i = i + 1; + }; + + (net_base_quantity, net_quote_quantity) + } + + /// Matches ask, returns (base_quantity_matched, quote_quantity_matched) + fun match_ask( + self: &mut Pool, + taker: address, + order_id: u128, + client_order_id: u64, + quantity: u64, // in base asset + ctx: &TxContext, + ): (u64, u64) { + let (mut ref, mut offset) = self.bids.slice_before(MAX_ORDER_ID); + // This means there are no bids in the book + if (ref.is_null()) { + return (0, 0) + }; + + let mut remaining_quantity = quantity; + let mut net_base_quantity = 0; + let mut net_quote_quantity = 0; + let mut matched_orders = vector[]; + + let mut bid = self.bids.borrow_mut_ref_offset(ref, offset); + while (remaining_quantity > 0 && order_id < bid.order_id ) { + // Match with existing bids + // We want to sell 1 BTC, if there's bid 0.5BTC at $50k, we want to sell 0.5BTC at $50k + let base_matched_quantity = math::min(bid.quantity, remaining_quantity); + bid.quantity = bid.quantity - base_matched_quantity; + remaining_quantity = remaining_quantity - base_matched_quantity; + // fee_subtracted is rounded down (in case of very small fills, this can be 0) + let fee_subtracted = math::div(math::mul(base_matched_quantity, bid.original_fee_quantity), bid.original_quantity); + bid.fee_quantity = bid.fee_quantity - fee_subtracted; + + // Rounded up, because maker gets rounding advantage + let quote_quantity = math::mul_round_up(base_matched_quantity, bid.price); + + // Refresh this user as necessary + let bid_owner = &mut self.users[bid.owner]; + bid_owner.refresh(ctx); + // Update maker quote balances + bid_owner.add_settled_base_amount(base_matched_quantity); + // Update individual maker volume + bid_owner.increase_maker_volume(base_matched_quantity); + // Update all volume + self.pool_state.increase_maker_volume(base_matched_quantity); + + // Check if user is a valid staker. Could separate out into a function + let (cur_stake, _) = bid_owner.stake(); + if (cur_stake >= self.pool_state.stake_required()) { + self.pool_state.increase_staked_maker_volume(base_matched_quantity); + }; + + event::emit(OrderFilled{ + pool_id: self.id.to_inner(), + maker_order_id: bid.order_id, + taker_order_id: order_id, + maker_client_order_id: bid.client_order_id, + taker_client_order_id: client_order_id, + base_quantity: base_matched_quantity, + quote_quantity, + price: bid.price, + maker_address: bid.owner, + taker_address: taker, + is_bid: false, // is an ask + }); + + net_base_quantity = net_base_quantity + base_matched_quantity; + net_quote_quantity = net_quote_quantity + math::mul(base_matched_quantity, bid.price); + // If bid quantity is 0, remove the order + if (bid.quantity == 0) { + // Remove order from user's open orders + self.users[bid.owner].remove_open_order(bid.order_id); + // Add order id to be removed + matched_orders.push_back(bid.order_id); + }; + + // Traverse to valid next order if exists, otherwise break from loop + if (self.bids.valid_prev(ref, offset)){ + (ref, offset, bid) = self.bids.borrow_prev_mut(ref, offset); + } else { + break + } + }; + + // Iterate over matched_orders and remove from bids + let mut i = 0; + while (i < matched_orders.length()) { + self.bids.remove(matched_orders[i]); + i = i + 1; + }; + + (net_base_quantity, net_quote_quantity) } /// Place a market order to the order book. + /// Will return (settled_base_quantity, settled_quote_quantity) public(package) fun place_market_order( - _self: &mut Pool, - _account: &mut Account, - _client_order_id: u64, - _quantity: u64, // in base asset - _is_bid: bool, // true for bid, false for ask - _ctx: &mut TxContext, - ): u128 { - // TODO: implement - 0 + self: &mut Pool, + account: &mut Account, + proof: &TradeProof, + client_order_id: u64, + quantity: u64, // in base asset + is_bid: bool, // true for bid, false for ask + ctx: &mut TxContext, + ): (u64, u64) { + // Refresh state as necessary if first order of epoch + self.refresh(ctx); + + assert!(quantity >= self.min_size, EOrderBelowMinimumSize); + assert!(quantity % self.lot_size == 0, EOrderInvalidLotSize); + + let price = if (is_bid) { + MAX_PRICE + } else { + MIN_PRICE + }; + + let order_id = encode_order_id(is_bid, price, self.get_order_id(is_bid)); + let (net_base_quantity, net_quote_quantity) = + if (is_bid) { + self.match_bid(account.owner(), order_id, client_order_id, quantity, ctx) + } else { + self.match_ask(account.owner(), order_id, client_order_id, quantity, ctx) + }; + + self.transfer_taker(account, proof, net_base_quantity, net_quote_quantity, is_bid, ctx); + + if (is_bid) { + (net_base_quantity, 0) + } else { + (0, net_quote_quantity) + } } /// Given an amount in and direction, calculate amount out + /// Will return (amount_out, amount_in_used) public(package) fun get_amount_out( _self: &Pool, _amount_in: u64, @@ -348,7 +673,7 @@ module deepbook::pool { ctx: &mut TxContext, ): vector{ let mut cancelled_orders = vector[]; - let user_open_orders = self.users[ctx.sender()].open_orders(); + let user_open_orders = self.users[account.owner()].open_orders(); let orders_vector = user_open_orders.into_keys(); let len = orders_vector.length(); @@ -619,8 +944,9 @@ module deepbook::pool { } /// Balance accounting happens before this function is called - fun internal_place_limit_order( + fun internal_inject_limit_order( self: &mut Pool, + order_id: u128, client_order_id: u64, price: u64, quantity: u64, @@ -628,8 +954,7 @@ module deepbook::pool { is_bid: bool, // true for bid, false for ask expire_timestamp: u64, // Expiration timestamp in ms ctx: &TxContext, - ): u128 { - let order_id = encode_order_id(is_bid, price, get_order_id(self, is_bid)); + ) { // Create Order let order = Order { @@ -657,8 +982,6 @@ module deepbook::pool { // Add order to user's open orders let user_data = &mut self.users[ctx.sender()]; user_data.add_open_order(order_id); - - order_id } /// Cancels an order and returns the order details diff --git a/deepbook/sources/pool/pool_state.move b/deepbook/sources/pool/pool_state.move index d31d18b8..4ffdd7a0 100644 --- a/deepbook/sources/pool/pool_state.move +++ b/deepbook/sources/pool/pool_state.move @@ -87,6 +87,21 @@ module deepbook::pool_state { } } + public(package) fun increase_maker_volume( + state: &mut PoolState, + volume: u64, + ) { + state.current_state.total_maker_volume = state.current_state.total_maker_volume + volume; + } + + public(package) fun increase_staked_maker_volume( + state: &mut PoolState, + volume: u64, + ) { + state.current_state.total_staked_maker_volume = state.current_state.total_staked_maker_volume + volume; + } + + public(package) fun maker_fee(state: &PoolState): u64 { state.current_state.maker_fee } diff --git a/deepbook/sources/pool/user.move b/deepbook/sources/pool/user.move index 615529b5..7d754d61 100644 --- a/deepbook/sources/pool/user.move +++ b/deepbook/sources/pool/user.move @@ -4,7 +4,7 @@ /// The user module manages user data and operations for the current epoch. /// This includes the user's open orders, maker volume, stake amount, and unclaimed rebates. /// When the epoch changes, the first user action triggers the necessary calculation for that user -/// for the previous epoch. +/// for the previous epoch. module deepbook::user { use sui::vec_set::{Self, VecSet}; @@ -16,9 +16,11 @@ module deepbook::user { stake_amount: u64, new_stake_amount: u64, unclaimed_rebates: u64, + settled_base_amount: u64, + settled_quote_amount: u64, } - public(package) fun new_user(user: address): User { + public(package) fun new(user: address): User { User { user, last_refresh_epoch: 0, @@ -27,6 +29,8 @@ module deepbook::user { stake_amount: 0, new_stake_amount: 0, unclaimed_rebates: 0, + settled_base_amount: 0, + settled_quote_amount: 0, } } @@ -107,4 +111,28 @@ module deepbook::user { order_id } + + /// Add to the user's settled base amount. + public(package) fun add_settled_base_amount( + self: &mut User, + amount: u64, + ) { + self.settled_base_amount = self.settled_base_amount + amount; + } + + /// Add to the user's settled quote amount. + public(package) fun add_settled_quote_amount( + self: &mut User, + amount: u64, + ) { + self.settled_quote_amount = self.settled_quote_amount + amount; + } + + /// Increase maker volume for user + public(package) fun increase_maker_volume( + self: &mut User, + volume: u64, + ) { + self.maker_volume = self.maker_volume + volume; + } }