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  }