github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/order/match/periodicauction/fill.go (about) 1 package periodicauction 2 3 import ( 4 "fmt" 5 "strings" 6 7 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 8 orderkeeper "github.com/fibonacci-chain/fbc/x/order/keeper" 9 "github.com/fibonacci-chain/fbc/x/order/types" 10 token "github.com/fibonacci-chain/fbc/x/token/types" 11 ) 12 13 func fillBuyOrders(ctx sdk.Context, keeper orderkeeper.Keeper, product string, 14 bestPrice, maxExecution sdk.Dec, buyExecuted *sdk.Dec, 15 blockRemainDeals int64, feeParams *types.Params) ([]types.Deal, int64) { 16 17 var buyDeals []types.Deal 18 book := keeper.GetDepthBookCopy(product) 19 20 // Fill buy orders, prices from high to low 21 index := 0 22 for index < len(book.Items) { 23 if !(book.Items[index].Price.GTE(bestPrice) && buyExecuted.LT(maxExecution)) { 24 break 25 } 26 // item.InitPrice >= bestPrice, fill buy orders 27 fillAmount := sdk.MinDec(book.Items[index].BuyQuantity, maxExecution.Sub(*buyExecuted)) 28 if fillAmount.IsZero() { // no buyer at this price 29 index++ 30 continue 31 } 32 33 // Fill buy orders at this price 34 key := types.FormatOrderIDsKey(product, book.Items[index].Price, types.BuyOrder) 35 filledBuyDeals, filledBuyAmount, filledDealsCnt := fillOrderByKey(ctx, keeper, key, 36 fillAmount, bestPrice, feeParams, blockRemainDeals) 37 blockRemainDeals -= filledDealsCnt 38 39 buyDeals = append(buyDeals, filledBuyDeals...) 40 *buyExecuted = buyExecuted.Add(filledBuyAmount) 41 42 book.Sub(index, filledBuyAmount, types.BuyOrder) 43 44 res := book.RemoveIfEmpty(index) 45 if !res { 46 index++ 47 } 48 49 if blockRemainDeals <= 0 { 50 break 51 } 52 } 53 keeper.SetDepthBook(product, book) 54 55 return buyDeals, blockRemainDeals 56 } 57 58 func fillSellOrders(ctx sdk.Context, keeper orderkeeper.Keeper, product string, 59 bestPrice, maxExecution sdk.Dec, sellExecuted *sdk.Dec, 60 blockRemainDeals int64, feeParams *types.Params) ([]types.Deal, int64) { 61 62 var sellDeals []types.Deal 63 book := keeper.GetDepthBookCopy(product) 64 65 // Fill sell orders, prices from low to high 66 index := len(book.Items) - 1 67 for index >= 0 { 68 69 if !(book.Items[index].Price.LTE(bestPrice) && sellExecuted.LT(maxExecution)) { 70 break 71 } 72 73 // item.InitPrice <= bestPrice, fill sell orders 74 fillAmount := sdk.MinDec(book.Items[index].SellQuantity, maxExecution.Sub(*sellExecuted)) 75 76 if fillAmount.IsZero() { 77 index-- 78 continue 79 } 80 // Fill sell orders at this price 81 key := types.FormatOrderIDsKey(product, book.Items[index].Price, types.SellOrder) 82 83 filledSellDeals, filledSellAmount, filledDealsCnt := fillOrderByKey(ctx, keeper, 84 key, fillAmount, bestPrice, feeParams, blockRemainDeals) 85 86 blockRemainDeals -= filledDealsCnt 87 sellDeals = append(sellDeals, filledSellDeals...) 88 *sellExecuted = sellExecuted.Add(filledSellAmount) 89 90 book.Sub(index, filledSellAmount, types.SellOrder) 91 book.RemoveIfEmpty(index) 92 93 if blockRemainDeals <= 0 { 94 break 95 } 96 index-- 97 } 98 keeper.SetDepthBook(product, book) // update depthbook on filled 99 100 return sellDeals, blockRemainDeals 101 } 102 103 // fillDepthBook will fill orders in depth book with bestPrice. 104 // It will update book and orderIDsMap, also update orders, charge fees, and transfer tokens, 105 // then return all deals. 106 func fillDepthBook(ctx sdk.Context, 107 keeper orderkeeper.Keeper, 108 product string, 109 bestPrice, 110 maxExecution sdk.Dec, 111 buyExecutedCnt, 112 sellExecutedCnt *sdk.Dec, 113 blockRemainDeals int64, 114 feeParams *types.Params) ([]types.Deal, int64) { 115 116 var deals []types.Deal 117 if maxExecution.IsZero() { 118 return deals, blockRemainDeals 119 } 120 121 buyDeals, blockRemainDeals := fillBuyOrders(ctx, keeper, product, bestPrice, maxExecution, 122 buyExecutedCnt, blockRemainDeals, feeParams) 123 deals = append(deals, buyDeals...) 124 if blockRemainDeals <= 0 { 125 return deals, blockRemainDeals 126 } 127 128 sellDeals, blockRemainDeals := fillSellOrders(ctx, keeper, product, bestPrice, maxExecution, 129 sellExecutedCnt, blockRemainDeals, feeParams) 130 deals = append(deals, sellDeals...) 131 132 return deals, blockRemainDeals 133 } 134 135 // Fill orders in orderIDsMap at specific key 136 func fillOrderByKey(ctx sdk.Context, keeper orderkeeper.Keeper, key string, 137 needFillAmount sdk.Dec, fillPrice sdk.Dec, feeParams *types.Params, 138 remainDeals int64) ([]types.Deal, sdk.Dec, int64) { 139 140 deals := []types.Deal{} 141 filledAmount := sdk.ZeroDec() 142 orderIDsMap := keeper.GetDiskCache().GetOrderIDsMapCopy() 143 filledDealsCnt := int64(0) 144 145 orderIDs, ok := orderIDsMap.Data[key] 146 // if key not found in orderIDsMap, return 147 if !ok { 148 return deals, filledAmount, filledDealsCnt 149 } 150 151 index := 0 152 for filledDealsCnt < remainDeals && filledAmount.LT(needFillAmount) { 153 order := keeper.GetOrder(ctx, orderIDs[index]) 154 if order == nil { 155 ctx.Logger().Error("[Order] Not exist orderID: ", orderIDs[index]) 156 } 157 if filledAmount.Add(order.RemainQuantity).LTE(needFillAmount) { 158 filledAmount = filledAmount.Add(order.RemainQuantity) 159 if deal := fillOrder(order, ctx, keeper, fillPrice, order.RemainQuantity, feeParams); deal != nil { 160 deals = append(deals, *deal) 161 } 162 163 filledDealsCnt++ 164 index++ 165 } else { 166 if deal := fillOrder(order, ctx, keeper, fillPrice, needFillAmount.Sub(filledAmount), feeParams); deal != nil { 167 deals = append(deals, *deal) 168 } 169 filledAmount = needFillAmount 170 171 break 172 } 173 174 } 175 176 unFilledOrderIDs := orderIDs[index:] // update orderIDs, remove filled orderIDs 177 // Note: orderIDs cannot be nil, we will use empty slice to remove Data on keeper 178 if len(unFilledOrderIDs) == 0 { 179 unFilledOrderIDs = []string{} 180 } 181 keeper.SetOrderIDs(key, unFilledOrderIDs) // update orderIDsMap on filled 182 183 return deals, filledAmount, filledDealsCnt 184 } 185 186 func balanceAccount(order *types.Order, ctx sdk.Context, keeper orderkeeper.Keeper, 187 fillPrice, fillQuantity sdk.Dec) { 188 189 symbols := strings.Split(order.Product, "_") 190 // transfer tokens 191 var outputCoins, inputCoins sdk.SysCoins 192 if order.Side == types.BuyOrder { 193 outputCoins = sdk.SysCoins{{Denom: symbols[1], Amount: fillPrice.Mul(fillQuantity)}} 194 inputCoins = sdk.SysCoins{{Denom: symbols[0], Amount: fillQuantity}} 195 } else { 196 outputCoins = sdk.SysCoins{{Denom: symbols[0], Amount: fillQuantity}} 197 inputCoins = sdk.SysCoins{{Denom: symbols[1], Amount: fillPrice.Mul(fillQuantity)}} 198 } 199 keeper.BalanceAccount(ctx, order.Sender, outputCoins, inputCoins) 200 } 201 202 func chargeFee(order *types.Order, ctx sdk.Context, keeper orderkeeper.Keeper, fillQuantity sdk.Dec, 203 feeParams *types.Params) (dealFee sdk.SysCoins, feeReceiver string) { 204 // charge fee 205 fee := orderkeeper.GetZeroFee() 206 if order.Status == types.OrderStatusFilled { 207 lockedFee := orderkeeper.GetOrderNewFee(order) 208 fee = orderkeeper.GetOrderCostFee(order, ctx) 209 receiveFee := lockedFee.Sub(fee) 210 211 keeper.UnlockCoins(ctx, order.Sender, lockedFee, token.LockCoinsTypeFee) 212 keeper.AddFeeDetail(ctx, order.Sender, receiveFee, types.FeeTypeOrderReceive) 213 order.RecordOrderReceiveFee(receiveFee) 214 215 err := keeper.AddCollectedFees(ctx, fee, order.Sender, types.FeeTypeOrderNew, false) 216 if err != nil { 217 ctx.Logger().Error(fmt.Sprintf("Send fee failed:%s\n", err.Error())) 218 } 219 } 220 dealFee = orderkeeper.GetDealFee(order, fillQuantity, ctx, keeper, feeParams) 221 feeReceiver, err := keeper.SendFeesToProductOwner(ctx, dealFee, order.Sender, types.FeeTypeOrderDeal, order.Product) 222 if err == nil { 223 order.RecordOrderDealFee(fee) 224 } 225 return 226 } 227 228 // Fill an order. Update order, charge fee and transfer tokens. Return a deal. 229 // If an order is fully filled but still lock some coins, unlock it. 230 func fillOrder(order *types.Order, ctx sdk.Context, keeper orderkeeper.Keeper, 231 fillPrice, fillQuantity sdk.Dec, feeParams *types.Params) *types.Deal { 232 233 // update order 234 order.Fill(fillPrice, fillQuantity) 235 236 balanceAccount(order, ctx, keeper, fillPrice, fillQuantity) 237 // if fully filled and still need unlock coins 238 if order.Status == types.OrderStatusFilled && order.RemainLocked.IsPositive() { 239 needUnlockCoins := order.NeedUnlockCoins() 240 keeper.UnlockCoins(ctx, order.Sender, needUnlockCoins, token.LockCoinsTypeQuantity) 241 order.Unlock() 242 } 243 244 dealFee, feeReceiver := chargeFee(order, ctx, keeper, fillQuantity, feeParams) 245 keeper.UpdateOrder(order, ctx) // update order info on filled 246 return &types.Deal{OrderID: order.OrderID, Side: order.Side, Quantity: fillQuantity, Fee: dealFee.String(), FeeReceiver: feeReceiver} 247 }