github.com/XinFinOrg/XDPoSChain@v1.6.0/XDCx/order_processor.go (about)

     1  package XDCx
     2  
     3  import (
     4  	"encoding/json"
     5  	"math/big"
     6  	"strconv"
     7  	"time"
     8  
     9  	"github.com/XinFinOrg/XDPoSChain/core/types"
    10  
    11  	"github.com/XinFinOrg/XDPoSChain/consensus"
    12  
    13  	"fmt"
    14  
    15  	"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
    16  	"github.com/XinFinOrg/XDPoSChain/common"
    17  	"github.com/XinFinOrg/XDPoSChain/core/state"
    18  	"github.com/XinFinOrg/XDPoSChain/log"
    19  )
    20  
    21  func (XDCx *XDCX) CommitOrder(header *types.Header, coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDB *tradingstate.TradingStateDB, orderBook common.Hash, order *tradingstate.OrderItem) ([]map[string]string, []*tradingstate.OrderItem, error) {
    22  	XDCxSnap := tradingStateDB.Snapshot()
    23  	dbSnap := statedb.Snapshot()
    24  	trades, rejects, err := XDCx.ApplyOrder(header, coinbase, chain, statedb, tradingStateDB, orderBook, order)
    25  	if err != nil {
    26  		tradingStateDB.RevertToSnapshot(XDCxSnap)
    27  		statedb.RevertToSnapshot(dbSnap)
    28  		return nil, nil, err
    29  	}
    30  	return trades, rejects, err
    31  }
    32  
    33  func (XDCx *XDCX) ApplyOrder(header *types.Header, coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDB *tradingstate.TradingStateDB, orderBook common.Hash, order *tradingstate.OrderItem) ([]map[string]string, []*tradingstate.OrderItem, error) {
    34  	var (
    35  		rejects []*tradingstate.OrderItem
    36  		trades  []map[string]string
    37  		err     error
    38  	)
    39  	nonce := tradingStateDB.GetNonce(order.UserAddress.Hash())
    40  	log.Debug("ApplyOrder", "addr", order.UserAddress, "statenonce", nonce, "ordernonce", order.Nonce)
    41  	if big.NewInt(int64(nonce)).Cmp(order.Nonce) == -1 {
    42  		log.Debug("ApplyOrder ErrNonceTooHigh", "nonce", order.Nonce)
    43  		return nil, nil, ErrNonceTooHigh
    44  	} else if big.NewInt(int64(nonce)).Cmp(order.Nonce) == 1 {
    45  		log.Debug("ApplyOrder ErrNonceTooLow", "nonce", order.Nonce)
    46  		return nil, nil, ErrNonceTooLow
    47  	}
    48  	// increase nonce
    49  	log.Debug("ApplyOrder set nonce", "nonce", nonce+1, "addr", order.UserAddress.Hex(), "status", order.Status, "oldnonce", nonce)
    50  	tradingStateDB.SetNonce(order.UserAddress.Hash(), nonce+1)
    51  	XDCxSnap := tradingStateDB.Snapshot()
    52  	dbSnap := statedb.Snapshot()
    53  	defer func() {
    54  		if err != nil {
    55  			tradingStateDB.RevertToSnapshot(XDCxSnap)
    56  			statedb.RevertToSnapshot(dbSnap)
    57  		}
    58  	}()
    59  
    60  	if err := order.VerifyOrder(statedb); err != nil {
    61  		rejects = append(rejects, order)
    62  		return trades, rejects, nil
    63  	}
    64  	if order.Status == tradingstate.OrderStatusCancelled {
    65  		err, reject := XDCx.ProcessCancelOrder(header, tradingStateDB, statedb, chain, coinbase, orderBook, order)
    66  		if err != nil || reject {
    67  			log.Debug("Reject cancelled order", "err", err)
    68  			rejects = append(rejects, order)
    69  		}
    70  		return trades, rejects, nil
    71  	}
    72  	if order.Type != tradingstate.Market {
    73  		if order.Price.Sign() == 0 || common.BigToHash(order.Price).Big().Cmp(order.Price) != 0 {
    74  			log.Debug("Reject order price invalid", "price", order.Price)
    75  			rejects = append(rejects, order)
    76  			return trades, rejects, nil
    77  		}
    78  	}
    79  	if order.Quantity.Sign() == 0 || common.BigToHash(order.Quantity).Big().Cmp(order.Quantity) != 0 {
    80  		log.Debug("Reject order quantity invalid", "quantity", order.Quantity)
    81  		rejects = append(rejects, order)
    82  		return trades, rejects, nil
    83  	}
    84  	orderType := order.Type
    85  	// if we do not use auto-increment orderid, we must set price slot to avoid conflict
    86  	if orderType == tradingstate.Market {
    87  		log.Debug("Process maket order", "side", order.Side, "quantity", order.Quantity, "price", order.Price)
    88  		trades, rejects, err = XDCx.processMarketOrder(coinbase, chain, statedb, tradingStateDB, orderBook, order)
    89  		if err != nil {
    90  			log.Debug("Reject market order", "err", err, "order", tradingstate.ToJSON(order))
    91  			trades = []map[string]string{}
    92  			rejects = append(rejects, order)
    93  		}
    94  	} else {
    95  		log.Debug("Process limit order", "side", order.Side, "quantity", order.Quantity, "price", order.Price)
    96  		trades, rejects, err = XDCx.processLimitOrder(coinbase, chain, statedb, tradingStateDB, orderBook, order)
    97  		if err != nil {
    98  			log.Debug("Reject limit order", "err", err, "order", tradingstate.ToJSON(order))
    99  			trades = []map[string]string{}
   100  			rejects = append(rejects, order)
   101  		}
   102  	}
   103  
   104  	return trades, rejects, nil
   105  }
   106  
   107  // processMarketOrder : process the market order
   108  func (XDCx *XDCX) processMarketOrder(coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDB *tradingstate.TradingStateDB, orderBook common.Hash, order *tradingstate.OrderItem) ([]map[string]string, []*tradingstate.OrderItem, error) {
   109  	var (
   110  		trades     []map[string]string
   111  		newTrades  []map[string]string
   112  		rejects    []*tradingstate.OrderItem
   113  		newRejects []*tradingstate.OrderItem
   114  		err        error
   115  	)
   116  	quantityToTrade := order.Quantity
   117  	side := order.Side
   118  	// speedup the comparison, do not assign because it is pointer
   119  	zero := tradingstate.Zero
   120  	if side == tradingstate.Bid {
   121  		bestPrice, volume := tradingStateDB.GetBestAskPrice(orderBook)
   122  		log.Debug("processMarketOrder ", "side", side, "bestPrice", bestPrice, "quantityToTrade", quantityToTrade, "volume", volume)
   123  		for quantityToTrade.Cmp(zero) > 0 && bestPrice.Cmp(zero) > 0 {
   124  			quantityToTrade, newTrades, newRejects, err = XDCx.processOrderList(coinbase, chain, statedb, tradingStateDB, tradingstate.Ask, orderBook, bestPrice, quantityToTrade, order)
   125  			if err != nil {
   126  				return nil, nil, err
   127  			}
   128  			trades = append(trades, newTrades...)
   129  			rejects = append(rejects, newRejects...)
   130  			bestPrice, volume = tradingStateDB.GetBestAskPrice(orderBook)
   131  			log.Debug("processMarketOrder ", "side", side, "bestPrice", bestPrice, "quantityToTrade", quantityToTrade, "volume", volume)
   132  		}
   133  	} else {
   134  		bestPrice, volume := tradingStateDB.GetBestBidPrice(orderBook)
   135  		log.Debug("processMarketOrder ", "side", side, "bestPrice", bestPrice, "quantityToTrade", quantityToTrade, "volume", volume)
   136  		for quantityToTrade.Cmp(zero) > 0 && bestPrice.Cmp(zero) > 0 {
   137  			quantityToTrade, newTrades, newRejects, err = XDCx.processOrderList(coinbase, chain, statedb, tradingStateDB, tradingstate.Bid, orderBook, bestPrice, quantityToTrade, order)
   138  			if err != nil {
   139  				return nil, nil, err
   140  			}
   141  			trades = append(trades, newTrades...)
   142  			rejects = append(rejects, newRejects...)
   143  			bestPrice, volume = tradingStateDB.GetBestBidPrice(orderBook)
   144  			log.Debug("processMarketOrder ", "side", side, "bestPrice", bestPrice, "quantityToTrade", quantityToTrade, "volume", volume)
   145  		}
   146  	}
   147  	return trades, rejects, nil
   148  }
   149  
   150  // processLimitOrder : process the limit order, can change the quote
   151  // If not care for performance, we should make a copy of quote to prevent further reference problem
   152  func (XDCx *XDCX) processLimitOrder(coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDB *tradingstate.TradingStateDB, orderBook common.Hash, order *tradingstate.OrderItem) ([]map[string]string, []*tradingstate.OrderItem, error) {
   153  	var (
   154  		trades     []map[string]string
   155  		newTrades  []map[string]string
   156  		rejects    []*tradingstate.OrderItem
   157  		newRejects []*tradingstate.OrderItem
   158  		err        error
   159  	)
   160  	quantityToTrade := order.Quantity
   161  	side := order.Side
   162  	price := order.Price
   163  
   164  	// speedup the comparison, do not assign because it is pointer
   165  	zero := tradingstate.Zero
   166  
   167  	if side == tradingstate.Bid {
   168  		minPrice, volume := tradingStateDB.GetBestAskPrice(orderBook)
   169  		log.Debug("processLimitOrder ", "side", side, "minPrice", minPrice, "orderPrice", price, "volume", volume)
   170  		for quantityToTrade.Cmp(zero) > 0 && price.Cmp(minPrice) >= 0 && minPrice.Cmp(zero) > 0 {
   171  			log.Debug("Min price in asks tree", "price", minPrice.String())
   172  			quantityToTrade, newTrades, newRejects, err = XDCx.processOrderList(coinbase, chain, statedb, tradingStateDB, tradingstate.Ask, orderBook, minPrice, quantityToTrade, order)
   173  			if err != nil {
   174  				return nil, nil, err
   175  			}
   176  			trades = append(trades, newTrades...)
   177  			rejects = append(rejects, newRejects...)
   178  			log.Debug("New trade found", "newTrades", newTrades, "quantityToTrade", quantityToTrade)
   179  			minPrice, volume = tradingStateDB.GetBestAskPrice(orderBook)
   180  			log.Debug("processLimitOrder ", "side", side, "minPrice", minPrice, "orderPrice", price, "volume", volume)
   181  		}
   182  	} else {
   183  		maxPrice, volume := tradingStateDB.GetBestBidPrice(orderBook)
   184  		log.Debug("processLimitOrder ", "side", side, "maxPrice", maxPrice, "orderPrice", price, "volume", volume)
   185  		for quantityToTrade.Cmp(zero) > 0 && price.Cmp(maxPrice) <= 0 && maxPrice.Cmp(zero) > 0 {
   186  			log.Debug("Max price in bids tree", "price", maxPrice.String())
   187  			quantityToTrade, newTrades, newRejects, err = XDCx.processOrderList(coinbase, chain, statedb, tradingStateDB, tradingstate.Bid, orderBook, maxPrice, quantityToTrade, order)
   188  			if err != nil {
   189  				return nil, nil, err
   190  			}
   191  			trades = append(trades, newTrades...)
   192  			rejects = append(rejects, newRejects...)
   193  			log.Debug("New trade found", "newTrades", newTrades, "quantityToTrade", quantityToTrade)
   194  			maxPrice, volume = tradingStateDB.GetBestBidPrice(orderBook)
   195  			log.Debug("processLimitOrder ", "side", side, "maxPrice", maxPrice, "orderPrice", price, "volume", volume)
   196  		}
   197  	}
   198  	if quantityToTrade.Cmp(zero) > 0 {
   199  		orderId := tradingStateDB.GetNonce(orderBook)
   200  		order.OrderID = orderId + 1
   201  		order.Quantity = quantityToTrade
   202  		tradingStateDB.SetNonce(orderBook, orderId+1)
   203  		orderIdHash := common.BigToHash(new(big.Int).SetUint64(order.OrderID))
   204  		tradingStateDB.InsertOrderItem(orderBook, orderIdHash, *order)
   205  		log.Debug("After matching, order (unmatched part) is now added to tree", "side", order.Side, "order", order)
   206  	}
   207  	return trades, rejects, nil
   208  }
   209  
   210  // processOrderList : process the order list
   211  func (XDCx *XDCX) processOrderList(coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDB *tradingstate.TradingStateDB, side string, orderBook common.Hash, price *big.Int, quantityStillToTrade *big.Int, order *tradingstate.OrderItem) (*big.Int, []map[string]string, []*tradingstate.OrderItem, error) {
   212  	quantityToTrade := tradingstate.CloneBigInt(quantityStillToTrade)
   213  	log.Debug("Process matching between order and orderlist", "quantityToTrade", quantityToTrade)
   214  	var (
   215  		trades []map[string]string
   216  
   217  		rejects []*tradingstate.OrderItem
   218  	)
   219  	for quantityToTrade.Sign() > 0 {
   220  		orderId, amount, _ := tradingStateDB.GetBestOrderIdAndAmount(orderBook, price, side)
   221  		var oldestOrder tradingstate.OrderItem
   222  		if amount.Sign() > 0 {
   223  			oldestOrder = tradingStateDB.GetOrder(orderBook, orderId)
   224  		}
   225  		log.Debug("found order ", "orderId ", orderId, "side", oldestOrder.Side, "amount", amount)
   226  		if oldestOrder.Quantity == nil || oldestOrder.Quantity.Sign() == 0 && amount.Sign() == 0 {
   227  			break
   228  		}
   229  		var (
   230  			tradedQuantity    *big.Int
   231  			maxTradedQuantity *big.Int
   232  		)
   233  		if quantityToTrade.Cmp(amount) <= 0 {
   234  			maxTradedQuantity = tradingstate.CloneBigInt(quantityToTrade)
   235  		} else {
   236  			maxTradedQuantity = tradingstate.CloneBigInt(amount)
   237  		}
   238  		var quotePrice *big.Int
   239  		if oldestOrder.QuoteToken.String() != common.XDCNativeAddress {
   240  			quotePrice = tradingStateDB.GetLastPrice(tradingstate.GetTradingOrderBookHash(oldestOrder.QuoteToken, common.HexToAddress(common.XDCNativeAddress)))
   241  			log.Debug("TryGet quotePrice QuoteToken/XDC", "quotePrice", quotePrice)
   242  			if quotePrice == nil || quotePrice.Sign() == 0 {
   243  				inversePrice := tradingStateDB.GetLastPrice(tradingstate.GetTradingOrderBookHash(common.HexToAddress(common.XDCNativeAddress), oldestOrder.QuoteToken))
   244  				quoteTokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, oldestOrder.QuoteToken)
   245  				if err != nil || quoteTokenDecimal.Sign() == 0 {
   246  					return nil, nil, nil, fmt.Errorf("Fail to get tokenDecimal. Token: %v . Err: %v", oldestOrder.QuoteToken.String(), err)
   247  				}
   248  				log.Debug("TryGet inversePrice XDC/QuoteToken", "inversePrice", inversePrice)
   249  				if inversePrice != nil && inversePrice.Sign() > 0 {
   250  					quotePrice = new(big.Int).Mul(common.BasePrice, quoteTokenDecimal)
   251  					quotePrice = new(big.Int).Div(quotePrice, inversePrice)
   252  					log.Debug("TryGet quotePrice after get inversePrice XDC/QuoteToken", "quotePrice", quotePrice, "quoteTokenDecimal", quoteTokenDecimal)
   253  				}
   254  			}
   255  		} else {
   256  			quotePrice = common.BasePrice
   257  		}
   258  		tradedQuantity, rejectMaker, settleBalanceResult, err := XDCx.getTradeQuantity(quotePrice, coinbase, chain, statedb, order, &oldestOrder, maxTradedQuantity)
   259  		if err != nil && err == tradingstate.ErrQuantityTradeTooSmall {
   260  			if tradedQuantity.Cmp(maxTradedQuantity) == 0 {
   261  				if quantityToTrade.Cmp(amount) == 0 { // reject Taker & maker
   262  					rejects = append(rejects, order)
   263  					quantityToTrade = tradingstate.Zero
   264  					rejects = append(rejects, &oldestOrder)
   265  					err = tradingStateDB.CancelOrder(orderBook, &oldestOrder)
   266  					if err != nil {
   267  						return nil, nil, nil, err
   268  					}
   269  					break
   270  				} else if quantityToTrade.Cmp(amount) < 0 { // reject Taker
   271  					rejects = append(rejects, order)
   272  					quantityToTrade = tradingstate.Zero
   273  					break
   274  				} else { // reject maker
   275  					rejects = append(rejects, &oldestOrder)
   276  					err = tradingStateDB.CancelOrder(orderBook, &oldestOrder)
   277  					if err != nil {
   278  						return nil, nil, nil, err
   279  					}
   280  					continue
   281  				}
   282  			} else {
   283  				if rejectMaker { // reject maker
   284  					rejects = append(rejects, &oldestOrder)
   285  					err = tradingStateDB.CancelOrder(orderBook, &oldestOrder)
   286  					if err != nil {
   287  						return nil, nil, nil, err
   288  					}
   289  					continue
   290  				} else { // reject Taker
   291  					rejects = append(rejects, order)
   292  					quantityToTrade = tradingstate.Zero
   293  					break
   294  				}
   295  			}
   296  		} else if err != nil {
   297  			return nil, nil, nil, err
   298  		}
   299  		if tradedQuantity.Sign() == 0 && !rejectMaker {
   300  			log.Debug("Reject order Taker ", "tradedQuantity", tradedQuantity, "rejectMaker", rejectMaker)
   301  			rejects = append(rejects, order)
   302  			quantityToTrade = tradingstate.Zero
   303  			break
   304  		}
   305  		if tradedQuantity.Sign() > 0 {
   306  			quantityToTrade = tradingstate.Sub(quantityToTrade, tradedQuantity)
   307  			err := tradingStateDB.SubAmountOrderItem(orderBook, orderId, price, tradedQuantity, side)
   308  			if err != nil {
   309  				log.Warn("processOrderList SubAmountOrderItem", "err", err, "orderBook", orderBook, "orderId", orderId, "price", *price, "tradedQuantity", *tradedQuantity, "side", side)
   310  			}
   311  			tradingStateDB.SetLastPrice(orderBook, price)
   312  			log.Debug("Update quantity for orderId", "orderId", orderId.Hex())
   313  			log.Debug("TRADE", "orderBook", orderBook, "Taker price", price, "maker price", order.Price, "Amount", tradedQuantity, "orderId", orderId, "side", side)
   314  
   315  			tradeRecord := make(map[string]string)
   316  			tradeRecord[tradingstate.TradeTakerOrderHash] = order.Hash.Hex()
   317  			tradeRecord[tradingstate.TradeMakerOrderHash] = oldestOrder.Hash.Hex()
   318  			tradeRecord[tradingstate.TradeTimestamp] = strconv.FormatInt(time.Now().Unix(), 10)
   319  			tradeRecord[tradingstate.TradeQuantity] = tradedQuantity.String()
   320  			tradeRecord[tradingstate.TradeMakerExchange] = oldestOrder.ExchangeAddress.String()
   321  			tradeRecord[tradingstate.TradeMaker] = oldestOrder.UserAddress.String()
   322  			tradeRecord[tradingstate.TradeBaseToken] = oldestOrder.BaseToken.String()
   323  			tradeRecord[tradingstate.TradeQuoteToken] = oldestOrder.QuoteToken.String()
   324  			if settleBalanceResult != nil {
   325  				tradeRecord[tradingstate.MakerFee] = settleBalanceResult.Maker.Fee.Text(10)
   326  				tradeRecord[tradingstate.TakerFee] = settleBalanceResult.Taker.Fee.Text(10)
   327  			}
   328  			// maker price is actual price
   329  			// Taker price is offer price
   330  			// tradedPrice is always actual price
   331  			tradeRecord[tradingstate.TradePrice] = oldestOrder.Price.String()
   332  			tradeRecord[tradingstate.MakerOrderType] = oldestOrder.Type
   333  			trades = append(trades, tradeRecord)
   334  
   335  			oldAveragePrice, oldTotalQuantity := tradingStateDB.GetMediumPriceAndTotalAmount(orderBook)
   336  
   337  			var newAveragePrice, newTotalQuantity *big.Int
   338  			if oldAveragePrice == nil || oldAveragePrice.Sign() <= 0 || oldTotalQuantity == nil || oldTotalQuantity.Sign() <= 0 {
   339  				newAveragePrice = price
   340  				newTotalQuantity = tradedQuantity
   341  			} else {
   342  				//volume = price * quantity
   343  				//=> price = volume /quantity
   344  				// averagePrice = totalVolume / totalQuantity
   345  				// averagePrice = (oldVolume + newTradeVolume) / (oldQuantity + newTradeQuantity)
   346  				// FIXME: average price formula
   347  				// https://user-images.githubusercontent.com/17243442/72722447-ecb83700-3bb0-11ea-9273-1c1028dbade0.jpg
   348  
   349  				oldVolume := new(big.Int).Mul(oldAveragePrice, oldTotalQuantity)
   350  				newTradeVolume := new(big.Int).Mul(price, tradedQuantity)
   351  				newTotalQuantity = new(big.Int).Add(oldTotalQuantity, tradedQuantity)
   352  				newAveragePrice = new(big.Int).Div(new(big.Int).Add(oldVolume, newTradeVolume), newTotalQuantity)
   353  			}
   354  
   355  			tradingStateDB.SetMediumPrice(orderBook, newAveragePrice, newTotalQuantity)
   356  		}
   357  		if rejectMaker {
   358  			rejects = append(rejects, &oldestOrder)
   359  			err := tradingStateDB.CancelOrder(orderBook, &oldestOrder)
   360  			if err != nil {
   361  				return nil, nil, nil, err
   362  			}
   363  		}
   364  	}
   365  	return quantityToTrade, trades, rejects, nil
   366  }
   367  
   368  func (XDCx *XDCX) getTradeQuantity(quotePrice *big.Int, coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, takerOrder *tradingstate.OrderItem, makerOrder *tradingstate.OrderItem, quantityToTrade *big.Int) (*big.Int, bool, *tradingstate.SettleBalance, error) {
   369  	baseTokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, makerOrder.BaseToken)
   370  	if err != nil || baseTokenDecimal.Sign() == 0 {
   371  		return tradingstate.Zero, false, nil, fmt.Errorf("Fail to get tokenDecimal. Token: %v . Err: %v", makerOrder.BaseToken.String(), err)
   372  	}
   373  	quoteTokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, makerOrder.QuoteToken)
   374  	if err != nil || quoteTokenDecimal.Sign() == 0 {
   375  		return tradingstate.Zero, false, nil, fmt.Errorf("Fail to get tokenDecimal. Token: %v . Err: %v", makerOrder.QuoteToken.String(), err)
   376  	}
   377  	if makerOrder.QuoteToken.String() == common.XDCNativeAddress {
   378  		quotePrice = quoteTokenDecimal
   379  	}
   380  	if takerOrder.ExchangeAddress.String() == makerOrder.ExchangeAddress.String() {
   381  		if err := tradingstate.CheckRelayerFee(takerOrder.ExchangeAddress, new(big.Int).Mul(common.RelayerFee, big.NewInt(2)), statedb); err != nil {
   382  			log.Debug("Reject order Taker Exchnage = Maker Exchange , relayer not enough fee ", "err", err)
   383  			return tradingstate.Zero, false, nil, nil
   384  		}
   385  	} else {
   386  		if err := tradingstate.CheckRelayerFee(takerOrder.ExchangeAddress, common.RelayerFee, statedb); err != nil {
   387  			log.Debug("Reject order Taker , relayer not enough fee ", "err", err)
   388  			return tradingstate.Zero, false, nil, nil
   389  		}
   390  		if err := tradingstate.CheckRelayerFee(makerOrder.ExchangeAddress, common.RelayerFee, statedb); err != nil {
   391  			log.Debug("Reject order maker , relayer not enough fee ", "err", err)
   392  			return tradingstate.Zero, true, nil, nil
   393  		}
   394  	}
   395  	takerFeeRate := tradingstate.GetExRelayerFee(takerOrder.ExchangeAddress, statedb)
   396  	makerFeeRate := tradingstate.GetExRelayerFee(makerOrder.ExchangeAddress, statedb)
   397  	var takerBalance, makerBalance *big.Int
   398  	switch takerOrder.Side {
   399  	case tradingstate.Bid:
   400  		takerBalance = tradingstate.GetTokenBalance(takerOrder.UserAddress, makerOrder.QuoteToken, statedb)
   401  		makerBalance = tradingstate.GetTokenBalance(makerOrder.UserAddress, makerOrder.BaseToken, statedb)
   402  	case tradingstate.Ask:
   403  		takerBalance = tradingstate.GetTokenBalance(takerOrder.UserAddress, makerOrder.BaseToken, statedb)
   404  		makerBalance = tradingstate.GetTokenBalance(makerOrder.UserAddress, makerOrder.QuoteToken, statedb)
   405  	default:
   406  		takerBalance = big.NewInt(0)
   407  		makerBalance = big.NewInt(0)
   408  	}
   409  	quantity, rejectMaker := GetTradeQuantity(takerOrder.Side, takerFeeRate, takerBalance, makerOrder.Price, makerFeeRate, makerBalance, baseTokenDecimal, quantityToTrade)
   410  	log.Debug("GetTradeQuantity", "side", takerOrder.Side, "takerBalance", takerBalance, "makerBalance", makerBalance, "BaseToken", makerOrder.BaseToken, "QuoteToken", makerOrder.QuoteToken, "quantity", quantity, "rejectMaker", rejectMaker, "quotePrice", quotePrice)
   411  	var settleBalanceResult *tradingstate.SettleBalance
   412  	if quantity.Sign() > 0 {
   413  		// Apply Match Order
   414  		settleBalanceResult, err = tradingstate.GetSettleBalance(quotePrice, takerOrder.Side, takerFeeRate, makerOrder.BaseToken, makerOrder.QuoteToken, makerOrder.Price, makerFeeRate, baseTokenDecimal, quoteTokenDecimal, quantity)
   415  		log.Debug("GetSettleBalance", "settleBalanceResult", settleBalanceResult, "err", err)
   416  		if err == nil {
   417  			err = DoSettleBalance(coinbase, takerOrder, makerOrder, settleBalanceResult, statedb)
   418  		}
   419  		return quantity, rejectMaker, settleBalanceResult, err
   420  	}
   421  	return quantity, rejectMaker, settleBalanceResult, nil
   422  }
   423  
   424  func GetTradeQuantity(takerSide string, takerFeeRate *big.Int, takerBalance *big.Int, makerPrice *big.Int, makerFeeRate *big.Int, makerBalance *big.Int, baseTokenDecimal *big.Int, quantityToTrade *big.Int) (*big.Int, bool) {
   425  	if takerSide == tradingstate.Bid {
   426  		// maker InQuantity quoteTokenQuantity=(quantityToTrade*maker.Price/baseTokenDecimal)
   427  		quoteTokenQuantity := new(big.Int).Mul(quantityToTrade, makerPrice)
   428  		quoteTokenQuantity = new(big.Int).Div(quoteTokenQuantity, baseTokenDecimal)
   429  		// Fee
   430  		// charge on the token he/she has before the trade, in this case: quoteToken
   431  		// charge on the token he/she has before the trade, in this case: baseToken
   432  		// takerFee = quoteTokenQuantity*takerFeeRate/baseFee=(quantityToTrade*maker.Price/baseTokenDecimal) * makerFeeRate/baseFee
   433  		takerFee := big.NewInt(0).Mul(quoteTokenQuantity, takerFeeRate)
   434  		takerFee = big.NewInt(0).Div(takerFee, common.XDCXBaseFee)
   435  		//takerOutTotal= quoteTokenQuantity + takerFee =  quantityToTrade*maker.Price/baseTokenDecimal + quantityToTrade*maker.Price/baseTokenDecimal * takerFeeRate/baseFee
   436  		// = quantityToTrade *  maker.Price/baseTokenDecimal ( 1 +  takerFeeRate/baseFee)
   437  		// = quantityToTrade * maker.Price * (baseFee + takerFeeRate ) / ( baseTokenDecimal * baseFee)
   438  		takerOutTotal := new(big.Int).Add(quoteTokenQuantity, takerFee)
   439  		makerOutTotal := quantityToTrade
   440  		if takerBalance.Cmp(takerOutTotal) >= 0 && makerBalance.Cmp(makerOutTotal) >= 0 {
   441  			return quantityToTrade, false
   442  		} else if takerBalance.Cmp(takerOutTotal) < 0 && makerBalance.Cmp(makerOutTotal) >= 0 {
   443  			newQuantityTrade := new(big.Int).Mul(takerBalance, baseTokenDecimal)
   444  			newQuantityTrade = new(big.Int).Mul(newQuantityTrade, common.XDCXBaseFee)
   445  			newQuantityTrade = new(big.Int).Div(newQuantityTrade, new(big.Int).Add(common.XDCXBaseFee, takerFeeRate))
   446  			newQuantityTrade = new(big.Int).Div(newQuantityTrade, makerPrice)
   447  			if newQuantityTrade.Sign() == 0 {
   448  				log.Debug("Reject order Taker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "takerOutTotal", takerOutTotal)
   449  			}
   450  			return newQuantityTrade, false
   451  		} else if takerBalance.Cmp(takerOutTotal) >= 0 && makerBalance.Cmp(makerOutTotal) < 0 {
   452  			log.Debug("Reject order maker , not enough balance ", "makerBalance", makerBalance, " makerOutTotal", makerOutTotal)
   453  			return makerBalance, true
   454  		} else {
   455  			// takerBalance.Cmp(takerOutTotal) < 0 && makerBalance.Cmp(makerOutTotal) < 0
   456  			newQuantityTrade := new(big.Int).Mul(takerBalance, baseTokenDecimal)
   457  			newQuantityTrade = new(big.Int).Mul(newQuantityTrade, common.XDCXBaseFee)
   458  			newQuantityTrade = new(big.Int).Div(newQuantityTrade, new(big.Int).Add(common.XDCXBaseFee, takerFeeRate))
   459  			newQuantityTrade = new(big.Int).Div(newQuantityTrade, makerPrice)
   460  			if newQuantityTrade.Cmp(makerBalance) <= 0 {
   461  				if newQuantityTrade.Sign() == 0 {
   462  					log.Debug("Reject order Taker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "makerBalance", makerBalance, " newQuantityTrade ", newQuantityTrade)
   463  				}
   464  				return newQuantityTrade, false
   465  			}
   466  			log.Debug("Reject order maker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "makerBalance", makerBalance, " newQuantityTrade ", newQuantityTrade)
   467  			return makerBalance, true
   468  		}
   469  	} else {
   470  		// Taker InQuantity
   471  		// quoteTokenQuantity = quantityToTrade * makerPrice / baseTokenDecimal
   472  		quoteTokenQuantity := new(big.Int).Mul(quantityToTrade, makerPrice)
   473  		quoteTokenQuantity = new(big.Int).Div(quoteTokenQuantity, baseTokenDecimal)
   474  		// maker InQuantity
   475  
   476  		// Fee
   477  		// charge on the token he/she has before the trade, in this case: baseToken
   478  		// makerFee = quoteTokenQuantity * makerFeeRate / baseFee = quantityToTrade * makerPrice / baseTokenDecimal * makerFeeRate / baseFee
   479  		// charge on the token he/she has before the trade, in this case: quoteToken
   480  		makerFee := new(big.Int).Mul(quoteTokenQuantity, makerFeeRate)
   481  		makerFee = new(big.Int).Div(makerFee, common.XDCXBaseFee)
   482  
   483  		takerOutTotal := quantityToTrade
   484  		// makerOutTotal = quoteTokenQuantity + makerFee  = quantityToTrade * makerPrice / baseTokenDecimal + quantityToTrade * makerPrice / baseTokenDecimal * makerFeeRate / baseFee
   485  		// =  quantityToTrade * makerPrice / baseTokenDecimal * (1+makerFeeRate / baseFee)
   486  		// = quantityToTrade  * makerPrice * (baseFee + makerFeeRate) / ( baseTokenDecimal * baseFee )
   487  		makerOutTotal := new(big.Int).Add(quoteTokenQuantity, makerFee)
   488  		if takerBalance.Cmp(takerOutTotal) >= 0 && makerBalance.Cmp(makerOutTotal) >= 0 {
   489  			return quantityToTrade, false
   490  		} else if takerBalance.Cmp(takerOutTotal) < 0 && makerBalance.Cmp(makerOutTotal) >= 0 {
   491  			if takerBalance.Sign() == 0 {
   492  				log.Debug("Reject order Taker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "takerOutTotal", takerOutTotal)
   493  			}
   494  			return takerBalance, false
   495  		} else if takerBalance.Cmp(takerOutTotal) >= 0 && makerBalance.Cmp(makerOutTotal) < 0 {
   496  			newQuantityTrade := new(big.Int).Mul(makerBalance, baseTokenDecimal)
   497  			newQuantityTrade = new(big.Int).Mul(newQuantityTrade, common.XDCXBaseFee)
   498  			newQuantityTrade = new(big.Int).Div(newQuantityTrade, new(big.Int).Add(common.XDCXBaseFee, makerFeeRate))
   499  			newQuantityTrade = new(big.Int).Div(newQuantityTrade, makerPrice)
   500  			log.Debug("Reject order maker , not enough balance ", "makerBalance", makerBalance, " makerOutTotal", makerOutTotal)
   501  			return newQuantityTrade, true
   502  		} else {
   503  			// takerBalance.Cmp(takerOutTotal) < 0 && makerBalance.Cmp(makerOutTotal) < 0
   504  			newQuantityTrade := new(big.Int).Mul(makerBalance, baseTokenDecimal)
   505  			newQuantityTrade = new(big.Int).Mul(newQuantityTrade, common.XDCXBaseFee)
   506  			newQuantityTrade = new(big.Int).Div(newQuantityTrade, new(big.Int).Add(common.XDCXBaseFee, makerFeeRate))
   507  			newQuantityTrade = new(big.Int).Div(newQuantityTrade, makerPrice)
   508  			if newQuantityTrade.Cmp(takerBalance) <= 0 {
   509  				log.Debug("Reject order maker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "makerBalance", makerBalance, " newQuantityTrade ", newQuantityTrade)
   510  				return newQuantityTrade, true
   511  			}
   512  			if takerBalance.Sign() == 0 {
   513  				log.Debug("Reject order Taker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "makerBalance", makerBalance, " newQuantityTrade ", newQuantityTrade)
   514  			}
   515  			return takerBalance, false
   516  		}
   517  	}
   518  }
   519  
   520  func DoSettleBalance(coinbase common.Address, takerOrder, makerOrder *tradingstate.OrderItem, settleBalance *tradingstate.SettleBalance, statedb *state.StateDB) error {
   521  	takerExOwner := tradingstate.GetRelayerOwner(takerOrder.ExchangeAddress, statedb)
   522  	makerExOwner := tradingstate.GetRelayerOwner(makerOrder.ExchangeAddress, statedb)
   523  	matchingFee := big.NewInt(0)
   524  	// masternodes charges fee of both 2 relayers. If maker and Taker are on same relayer, that relayer is charged fee twice
   525  	matchingFee = new(big.Int).Add(matchingFee, common.RelayerFee)
   526  	matchingFee = new(big.Int).Add(matchingFee, common.RelayerFee)
   527  
   528  	if common.EmptyHash(takerExOwner.Hash()) || common.EmptyHash(makerExOwner.Hash()) {
   529  		return fmt.Errorf("Echange owner empty , Taker: %v , maker : %v ", takerExOwner, makerExOwner)
   530  	}
   531  	mapBalances := map[common.Address]map[common.Address]*big.Int{}
   532  	//Checking balance
   533  	newTakerInTotal, err := tradingstate.CheckAddTokenBalance(takerOrder.UserAddress, settleBalance.Taker.InTotal, settleBalance.Taker.InToken, statedb, mapBalances)
   534  	if err != nil {
   535  		return err
   536  	}
   537  	if mapBalances[settleBalance.Taker.InToken] == nil {
   538  		mapBalances[settleBalance.Taker.InToken] = map[common.Address]*big.Int{}
   539  	}
   540  	mapBalances[settleBalance.Taker.InToken][takerOrder.UserAddress] = newTakerInTotal
   541  	newTakerOutTotal, err := tradingstate.CheckSubTokenBalance(takerOrder.UserAddress, settleBalance.Taker.OutTotal, settleBalance.Taker.OutToken, statedb, mapBalances)
   542  	if err != nil {
   543  		return err
   544  	}
   545  	if mapBalances[settleBalance.Taker.OutToken] == nil {
   546  		mapBalances[settleBalance.Taker.OutToken] = map[common.Address]*big.Int{}
   547  	}
   548  	mapBalances[settleBalance.Taker.OutToken][takerOrder.UserAddress] = newTakerOutTotal
   549  	newMakerInTotal, err := tradingstate.CheckAddTokenBalance(makerOrder.UserAddress, settleBalance.Maker.InTotal, settleBalance.Maker.InToken, statedb, mapBalances)
   550  	if err != nil {
   551  		return err
   552  	}
   553  	if mapBalances[settleBalance.Maker.InToken] == nil {
   554  		mapBalances[settleBalance.Maker.InToken] = map[common.Address]*big.Int{}
   555  	}
   556  	mapBalances[settleBalance.Maker.InToken][makerOrder.UserAddress] = newMakerInTotal
   557  	newMakerOutTotal, err := tradingstate.CheckSubTokenBalance(makerOrder.UserAddress, settleBalance.Maker.OutTotal, settleBalance.Maker.OutToken, statedb, mapBalances)
   558  	if err != nil {
   559  		return err
   560  	}
   561  	if mapBalances[settleBalance.Maker.OutToken] == nil {
   562  		mapBalances[settleBalance.Maker.OutToken] = map[common.Address]*big.Int{}
   563  	}
   564  	mapBalances[settleBalance.Maker.OutToken][makerOrder.UserAddress] = newMakerOutTotal
   565  	newTakerFee, err := tradingstate.CheckAddTokenBalance(takerExOwner, settleBalance.Taker.Fee, makerOrder.QuoteToken, statedb, mapBalances)
   566  	if err != nil {
   567  		return err
   568  	}
   569  	if mapBalances[makerOrder.QuoteToken] == nil {
   570  		mapBalances[makerOrder.QuoteToken] = map[common.Address]*big.Int{}
   571  	}
   572  	mapBalances[makerOrder.QuoteToken][takerExOwner] = newTakerFee
   573  	newMakerFee, err := tradingstate.CheckAddTokenBalance(makerExOwner, settleBalance.Maker.Fee, makerOrder.QuoteToken, statedb, mapBalances)
   574  	if err != nil {
   575  		return err
   576  	}
   577  	mapBalances[makerOrder.QuoteToken][makerExOwner] = newMakerFee
   578  
   579  	mapRelayerFee := map[common.Address]*big.Int{}
   580  	newRelayerTakerFee, err := tradingstate.CheckSubRelayerFee(takerOrder.ExchangeAddress, common.RelayerFee, statedb, mapRelayerFee)
   581  	if err != nil {
   582  		return err
   583  	}
   584  	mapRelayerFee[takerOrder.ExchangeAddress] = newRelayerTakerFee
   585  	newRelayerMakerFee, err := tradingstate.CheckSubRelayerFee(makerOrder.ExchangeAddress, common.RelayerFee, statedb, mapRelayerFee)
   586  	if err != nil {
   587  		return err
   588  	}
   589  	mapRelayerFee[makerOrder.ExchangeAddress] = newRelayerMakerFee
   590  	tradingstate.SetSubRelayerFee(takerOrder.ExchangeAddress, newRelayerTakerFee, common.RelayerFee, statedb)
   591  	tradingstate.SetSubRelayerFee(makerOrder.ExchangeAddress, newRelayerMakerFee, common.RelayerFee, statedb)
   592  
   593  	masternodeOwner := statedb.GetOwner(coinbase)
   594  	statedb.AddBalance(masternodeOwner, matchingFee)
   595  
   596  	err = tradingstate.SetTokenBalance(takerOrder.UserAddress, newTakerInTotal, settleBalance.Taker.InToken, statedb)
   597  	if err != nil {
   598  		log.Warn("DoSettleBalance SetTokenBalance", "err", err, "takerOder.UserAddress", takerOrder.UserAddress, "newTakerInTotal", *newTakerInTotal, "settleBalance.Taker.InToken", settleBalance.Taker.InToken)
   599  	}
   600  	err = tradingstate.SetTokenBalance(takerOrder.UserAddress, newTakerOutTotal, settleBalance.Taker.OutToken, statedb)
   601  	if err != nil {
   602  		log.Warn("DoSettleBalance SetTokenBalance", "err", err, "takerOrder.UserAddress", takerOrder.UserAddress, "newTakerOutTotal", *newTakerOutTotal, "settleBalance.Taker.OutToken", settleBalance.Taker.OutToken)
   603  	}
   604  	err = tradingstate.SetTokenBalance(makerOrder.UserAddress, newMakerInTotal, settleBalance.Maker.InToken, statedb)
   605  	if err != nil {
   606  		log.Warn("DoSettleBalance SetTokenBalance", "err", err, "makerOrder.UserAddress", makerOrder.UserAddress, "newMakerInTotal", *newMakerInTotal, "settleBalance.Maker.InToken", settleBalance.Maker.InToken)
   607  	}
   608  	err = tradingstate.SetTokenBalance(makerOrder.UserAddress, newMakerOutTotal, settleBalance.Maker.OutToken, statedb)
   609  	if err != nil {
   610  		log.Warn("DoSettleBalance SetTokenBalance", "err", err, "makerOrder.UserAddress", makerOrder.UserAddress, "newMakerOutTotal", *newMakerOutTotal, "settleBalance.Maker.OutToken", settleBalance.Maker.OutToken)
   611  	}
   612  
   613  	// add balance for relayers
   614  	//log.Debug("ApplyXDCXMatchedTransaction settle fee for relayers",
   615  	//	"takerRelayerOwner", takerExOwner,
   616  	//	"takerFeeToken", quoteToken, "takerFee", settleBalanceResult[takerAddr][XDCx.Fee].(*big.Int),
   617  	//	"makerRelayerOwner", makerExOwner,
   618  	//	"makerFeeToken", quoteToken, "makerFee", settleBalanceResult[makerAddr][XDCx.Fee].(*big.Int))
   619  	// takerFee
   620  	err = tradingstate.SetTokenBalance(takerExOwner, newTakerFee, makerOrder.QuoteToken, statedb)
   621  	if err != nil {
   622  		log.Warn("DoSettleBalance SetTokenBalance", "err", err, "takerExOwner", takerExOwner, "newTakerFee", *newTakerFee, "makerOrder.QuoteToken", makerOrder.QuoteToken)
   623  	}
   624  	err = tradingstate.SetTokenBalance(makerExOwner, newMakerFee, makerOrder.QuoteToken, statedb)
   625  	if err != nil {
   626  		log.Warn("DoSettleBalance SetTokenBalance", "err", err, "makerExOwner", makerExOwner, "newMakerFee", *newMakerFee, "makerOrder.QuoteToken", makerOrder.QuoteToken)
   627  	}
   628  	return nil
   629  }
   630  
   631  func (XDCx *XDCX) ProcessCancelOrder(header *types.Header, tradingStateDB *tradingstate.TradingStateDB, statedb *state.StateDB, chain consensus.ChainContext, coinbase common.Address, orderBook common.Hash, order *tradingstate.OrderItem) (error, bool) {
   632  	if err := tradingstate.CheckRelayerFee(order.ExchangeAddress, common.RelayerCancelFee, statedb); err != nil {
   633  		log.Debug("Relayer not enough fee when cancel order", "err", err)
   634  		return nil, true
   635  	}
   636  	baseTokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, order.BaseToken)
   637  	if err != nil || baseTokenDecimal.Sign() == 0 {
   638  		log.Debug("Fail to get tokenDecimal ", "Token", order.BaseToken.String(), "err", err)
   639  		return err, false
   640  	}
   641  	// order: basic order information (includes orderId, orderHash, baseToken, quoteToken) which user send to XDCx to cancel order
   642  	// originOrder: full order information getting from order trie
   643  	originOrder := tradingStateDB.GetOrder(orderBook, common.BigToHash(new(big.Int).SetUint64(order.OrderID)))
   644  	if originOrder == tradingstate.EmptyOrder {
   645  		return fmt.Errorf("order not found. OrderId: %v. Base: %s. Quote: %s", order.OrderID, order.BaseToken.Hex(), order.QuoteToken.Hex()), false
   646  	}
   647  	var tokenBalance *big.Int
   648  	switch originOrder.Side {
   649  	case tradingstate.Ask:
   650  		tokenBalance = tradingstate.GetTokenBalance(originOrder.UserAddress, originOrder.BaseToken, statedb)
   651  	case tradingstate.Bid:
   652  		tokenBalance = tradingstate.GetTokenBalance(originOrder.UserAddress, originOrder.QuoteToken, statedb)
   653  	default:
   654  		log.Debug("Not found order side", "Side", originOrder.Side)
   655  		return nil, false
   656  	}
   657  	log.Debug("ProcessCancelOrder", "baseToken", originOrder.BaseToken, "quoteToken", originOrder.QuoteToken)
   658  	feeRate := tradingstate.GetExRelayerFee(originOrder.ExchangeAddress, statedb)
   659  	tokenCancelFee, tokenPriceInXDC := common.Big0, common.Big0
   660  	if !chain.Config().IsTIPXDCXCancellationFee(header.Number) {
   661  		tokenCancelFee = getCancelFeeV1(baseTokenDecimal, feeRate, &originOrder)
   662  	} else {
   663  		tokenCancelFee, tokenPriceInXDC = XDCx.getCancelFee(chain, statedb, tradingStateDB, &originOrder, feeRate)
   664  	}
   665  	if tokenBalance.Cmp(tokenCancelFee) < 0 {
   666  		log.Debug("User not enough balance when cancel order", "Side", originOrder.Side, "balance", tokenBalance, "fee", tokenCancelFee)
   667  		return nil, true
   668  	}
   669  
   670  	err = tradingStateDB.CancelOrder(orderBook, order)
   671  	if err != nil {
   672  		log.Debug("Error when cancel order", "order", order)
   673  		return err, false
   674  	}
   675  	// relayers pay XDC for masternode
   676  	err = tradingstate.SubRelayerFee(originOrder.ExchangeAddress, common.RelayerCancelFee, statedb)
   677  	if err != nil {
   678  		log.Warn("ProcessCancelOrder SubRelayerFee", "err", err, "originOrder.ExchangeAddress", originOrder.ExchangeAddress, "common.RelayerCancelFee", *common.RelayerCancelFee)
   679  	}
   680  	masternodeOwner := statedb.GetOwner(coinbase)
   681  	// relayers pay XDC for masternode
   682  	statedb.AddBalance(masternodeOwner, common.RelayerCancelFee)
   683  
   684  	relayerOwner := tradingstate.GetRelayerOwner(originOrder.ExchangeAddress, statedb)
   685  	switch originOrder.Side {
   686  	case tradingstate.Ask:
   687  		// users pay token (which they have) for relayer
   688  		err := tradingstate.SubTokenBalance(originOrder.UserAddress, tokenCancelFee, originOrder.BaseToken, statedb)
   689  		if err != nil {
   690  			log.Warn("ProcessCancelOrder SubTokenBalance", "err", err, "originOrder.UserAddress", originOrder.UserAddress, "tokenCancelFee", *tokenCancelFee, "originOrder.BaseToken", originOrder.BaseToken)
   691  		}
   692  		err = tradingstate.AddTokenBalance(relayerOwner, tokenCancelFee, originOrder.BaseToken, statedb)
   693  		if err != nil {
   694  			log.Warn("ProcessCancelOrder AddTokenBalance", "err", err, "relayerOwner", relayerOwner, "tokenCancelFee", *tokenCancelFee, "originOrder.BaseToken", originOrder.BaseToken)
   695  		}
   696  	case tradingstate.Bid:
   697  		// users pay token (which they have) for relayer
   698  		err := tradingstate.SubTokenBalance(originOrder.UserAddress, tokenCancelFee, originOrder.QuoteToken, statedb)
   699  		if err != nil {
   700  			log.Warn("ProcessCancelOrder SubTokenBalance", "err", err, "originOrder.UserAddress", originOrder.UserAddress, "tokenCancelFee", *tokenCancelFee, "originOrder.QuoteToken", originOrder.QuoteToken)
   701  		}
   702  		err = tradingstate.AddTokenBalance(relayerOwner, tokenCancelFee, originOrder.QuoteToken, statedb)
   703  		if err != nil {
   704  			log.Warn("ProcessCancelOrder AddTokenBalance", "err", err, "relayerOwner", relayerOwner, "tokenCancelFee", *tokenCancelFee, "originOrder.QuoteToken", originOrder.QuoteToken)
   705  		}
   706  	default:
   707  	}
   708  	// update cancel fee
   709  	extraData, _ := json.Marshal(struct {
   710  		CancelFee       string
   711  		TokenPriceInXDC string
   712  	}{
   713  		CancelFee:       tokenCancelFee.Text(10),
   714  		TokenPriceInXDC: tokenPriceInXDC.Text(10),
   715  	})
   716  	order.ExtraData = string(extraData)
   717  
   718  	return nil, false
   719  }
   720  
   721  // cancellation fee = 1/10 trading fee
   722  // deprecated after hardfork at TIPXDCXCancellationFee
   723  func getCancelFeeV1(baseTokenDecimal *big.Int, feeRate *big.Int, order *tradingstate.OrderItem) *big.Int {
   724  	cancelFee := big.NewInt(0)
   725  	if order.Side == tradingstate.Ask {
   726  		// SELL 1 BTC => XDC ,,
   727  		// order.Quantity =1 && fee rate =2
   728  		// ==> cancel fee = 2/10000
   729  		// order.Quantity already included baseToken decimal
   730  		cancelFee = new(big.Int).Mul(order.Quantity, feeRate)
   731  		cancelFee = new(big.Int).Div(cancelFee, common.XDCXBaseCancelFee)
   732  	} else {
   733  		// BUY 1 BTC => XDC with Price : 10000
   734  		// quoteTokenQuantity = 10000 && fee rate =2
   735  		// => cancel fee =2
   736  		quoteTokenQuantity := new(big.Int).Mul(order.Quantity, order.Price)
   737  		quoteTokenQuantity = new(big.Int).Div(quoteTokenQuantity, baseTokenDecimal)
   738  		// Fee
   739  		// makerFee = quoteTokenQuantity * feeRate / baseFee = quantityToTrade * makerPrice / baseTokenDecimal * feeRate / baseFee
   740  		cancelFee = new(big.Int).Mul(quoteTokenQuantity, feeRate)
   741  		cancelFee = new(big.Int).Div(cancelFee, common.XDCXBaseCancelFee)
   742  	}
   743  	return cancelFee
   744  }
   745  
   746  // return tokenQuantity, tokenPriceInXDC
   747  func (XDCx *XDCX) getCancelFee(chain consensus.ChainContext, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, order *tradingstate.OrderItem, feeRate *big.Int) (*big.Int, *big.Int) {
   748  	if feeRate == nil || feeRate.Sign() == 0 {
   749  		return common.Big0, common.Big0
   750  	}
   751  	cancelFee := big.NewInt(0)
   752  	tokenPriceInXDC := big.NewInt(0)
   753  	var err error
   754  	if order.Side == tradingstate.Ask {
   755  		cancelFee, tokenPriceInXDC, err = XDCx.ConvertXDCToToken(chain, statedb, tradingStateDb, order.BaseToken, common.RelayerCancelFee)
   756  	} else {
   757  		cancelFee, tokenPriceInXDC, err = XDCx.ConvertXDCToToken(chain, statedb, tradingStateDb, order.QuoteToken, common.RelayerCancelFee)
   758  	}
   759  	if err != nil {
   760  		return common.Big0, common.Big0
   761  	}
   762  	return cancelFee, tokenPriceInXDC
   763  }
   764  
   765  func (XDCx *XDCX) UpdateMediumPriceBeforeEpoch(epochNumber uint64, tradingStateDB *tradingstate.TradingStateDB, statedb *state.StateDB) error {
   766  	mapPairs, err := tradingstate.GetAllTradingPairs(statedb)
   767  	log.Debug("UpdateMediumPriceBeforeEpoch", "len(mapPairs)", len(mapPairs))
   768  
   769  	if err != nil {
   770  		return err
   771  	}
   772  	epochPriceResult := map[common.Hash]*big.Int{}
   773  	for orderbook := range mapPairs {
   774  		oldMediumPriceBeforeEpoch := tradingStateDB.GetMediumPriceBeforeEpoch(orderbook)
   775  		mediumPriceCurrent, _ := tradingStateDB.GetMediumPriceAndTotalAmount(orderbook)
   776  
   777  		// if there is no trade in this epoch, use average price of last epoch
   778  		epochPriceResult[orderbook] = oldMediumPriceBeforeEpoch
   779  		if mediumPriceCurrent.Sign() > 0 && mediumPriceCurrent.Cmp(oldMediumPriceBeforeEpoch) != 0 {
   780  			log.Debug("UpdateMediumPriceBeforeEpoch", "mediumPriceCurrent", mediumPriceCurrent)
   781  			tradingStateDB.SetMediumPriceBeforeEpoch(orderbook, mediumPriceCurrent)
   782  			epochPriceResult[orderbook] = mediumPriceCurrent
   783  		}
   784  		tradingStateDB.SetMediumPrice(orderbook, tradingstate.Zero, tradingstate.Zero)
   785  	}
   786  	if XDCx.IsSDKNode() {
   787  		if err := XDCx.LogEpochPrice(epochNumber, epochPriceResult); err != nil {
   788  			log.Error("failed to update epochPrice", "err", err)
   789  		}
   790  	}
   791  	return nil
   792  }
   793  
   794  // put average price of epoch to mongodb for tracking liquidation trades
   795  // epochPriceResult: a map of epoch average price, key is orderbook hash , value is epoch average price
   796  // orderbook hash genereted from baseToken, quoteToken at XDPoSChain/XDCx/tradingstate/common.go:214
   797  func (XDCx *XDCX) LogEpochPrice(epochNumber uint64, epochPriceResult map[common.Hash]*big.Int) error {
   798  	db := XDCx.GetMongoDB()
   799  	db.InitBulk()
   800  
   801  	for orderbook, price := range epochPriceResult {
   802  		if price.Sign() <= 0 {
   803  			continue
   804  		}
   805  		epochPriceItem := &tradingstate.EpochPriceItem{
   806  			Epoch:     epochNumber,
   807  			Orderbook: orderbook,
   808  			Price:     price,
   809  		}
   810  		epochPriceItem.Hash = epochPriceItem.ComputeHash()
   811  		if err := db.PutObject(epochPriceItem.Hash, epochPriceItem); err != nil {
   812  			return err
   813  		}
   814  	}
   815  	if err := db.CommitBulk(); err != nil {
   816  		return err
   817  	}
   818  	return nil
   819  }