github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/order/handler.go (about)

     1  package order
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"math"
     7  
     8  	storetypes "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/store/types"
     9  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    10  	sdkerrors "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/errors"
    11  	"github.com/fibonacci-chain/fbc/libs/tendermint/libs/log"
    12  	types2 "github.com/fibonacci-chain/fbc/libs/tendermint/types"
    13  	"github.com/fibonacci-chain/fbc/x/common"
    14  	"github.com/fibonacci-chain/fbc/x/common/perf"
    15  	"github.com/fibonacci-chain/fbc/x/order/keeper"
    16  	"github.com/fibonacci-chain/fbc/x/order/types"
    17  	"github.com/willf/bitset"
    18  )
    19  
    20  func CalculateGas(msg sdk.Msg, params *types.Params) (gas uint64) {
    21  	switch msg := msg.(type) {
    22  	case types.MsgNewOrders:
    23  		gas = msg.CalculateGas(params.NewOrderMsgGasUnit)
    24  	case types.MsgCancelOrders:
    25  		gas = msg.CalculateGas(params.CancelOrderMsgGasUnit)
    26  	default:
    27  		gas = math.MaxUint64
    28  	}
    29  
    30  	return gas
    31  }
    32  
    33  // NewOrderHandler returns the handler with version 0.
    34  func NewOrderHandler(keeper keeper.Keeper) sdk.Handler {
    35  	return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
    36  		// disable order tx handler
    37  		return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "Order messages are not allowd.")
    38  
    39  		gas := CalculateGas(msg, keeper.GetParams(ctx))
    40  
    41  		// consume gas that msg required, it will panic if gas is insufficient
    42  		ctx.GasMeter().ConsumeGas(gas, storetypes.GasWriteCostFlatDesc)
    43  
    44  		if ctx.IsCheckTx() {
    45  			return &sdk.Result{}, nil
    46  		} else {
    47  			// set an infinite gas meter and recovery it when return
    48  			gasMeter := ctx.GasMeter()
    49  			ctx.SetGasMeter(sdk.NewInfiniteGasMeter())
    50  			defer func() { ctx.SetGasMeter(gasMeter) }()
    51  		}
    52  
    53  		ctx.SetEventManager(sdk.NewEventManager())
    54  		var handlerFun func() (*sdk.Result, error)
    55  		var name string
    56  		logger := ctx.Logger().With("module", "order")
    57  		switch msg := msg.(type) {
    58  		case types.MsgNewOrders:
    59  			name = "handleMsgNewOrders"
    60  			handlerFun = func() (*sdk.Result, error) {
    61  				return handleMsgNewOrders(ctx, keeper, msg, logger)
    62  			}
    63  		case types.MsgCancelOrders:
    64  			name = "handleMsgCancelOrders"
    65  			handlerFun = func() (*sdk.Result, error) {
    66  				return handleMsgCancelOrders(ctx, keeper, msg, logger)
    67  			}
    68  		default:
    69  			errMsg := fmt.Sprintf("Invalid msg type: %v", msg.Type())
    70  			return sdk.ErrUnknownRequest(errMsg).Result()
    71  		}
    72  		seq := perf.GetPerf().OnDeliverTxEnter(ctx, types.ModuleName, name)
    73  		defer perf.GetPerf().OnDeliverTxExit(ctx, types.ModuleName, name, seq)
    74  
    75  		res, err := handlerFun()
    76  		common.SanityCheckHandler(res, err)
    77  		return res, err
    78  	}
    79  }
    80  
    81  // checkOrderNewMsg: check msg product, price & quantity fields
    82  func checkOrderNewMsg(ctx sdk.Context, keeper keeper.Keeper, msg types.MsgNewOrder) error {
    83  	tokenPair := keeper.GetDexKeeper().GetTokenPair(ctx, msg.Product)
    84  	if tokenPair == nil {
    85  		return types.ErrTokenPairNotExist(msg.Product)
    86  	}
    87  
    88  	// check if the order is involved with the tokenpair in dex Delist
    89  	isDelisting, err := keeper.GetDexKeeper().CheckTokenPairUnderDexDelist(ctx, msg.Product)
    90  	if err != nil {
    91  		return err
    92  	}
    93  	if isDelisting {
    94  		return types.ErrTradingPairIsDelisting(msg.Product)
    95  	}
    96  
    97  	priceDigit := tokenPair.MaxPriceDigit
    98  	quantityDigit := tokenPair.MaxQuantityDigit
    99  	roundedPrice := msg.Price.RoundDecimal(priceDigit)
   100  	roundedQuantity := msg.Quantity.RoundDecimal(quantityDigit)
   101  	if !roundedPrice.Equal(msg.Price) {
   102  		return types.ErrPriceOverAccuracy(msg.Price, priceDigit)
   103  	}
   104  	if !roundedQuantity.Equal(msg.Quantity) {
   105  		return types.ErrQuantityOverAccuracy(msg.Quantity, quantityDigit)
   106  	}
   107  
   108  	if msg.Quantity.LT(tokenPair.MinQuantity) {
   109  		return types.ErrMsgQuantityLessThan(tokenPair.MinQuantity.String())
   110  	}
   111  	return nil
   112  }
   113  
   114  func getOrderFromMsg(ctx sdk.Context, k keeper.Keeper, msg types.MsgNewOrder, ratio string) *types.Order {
   115  	feeParams := k.GetParams(ctx)
   116  	feePerBlockAmount := feeParams.FeePerBlock.Amount.Mul(sdk.MustNewDecFromStr(ratio))
   117  	feePerBlock := sdk.NewDecCoinFromDec(feeParams.FeePerBlock.Denom, feePerBlockAmount)
   118  	return types.NewOrder(
   119  		fmt.Sprintf("%X", types2.Tx(ctx.TxBytes()).Hash(ctx.BlockHeight())),
   120  		msg.Sender,
   121  		msg.Product,
   122  		msg.Side,
   123  		msg.Price,
   124  		msg.Quantity,
   125  		ctx.BlockHeader().Time.Unix(),
   126  		feeParams.OrderExpireBlocks,
   127  		feePerBlock,
   128  	)
   129  }
   130  
   131  func handleNewOrder(ctx sdk.Context, k Keeper, sender sdk.AccAddress,
   132  	item types.OrderItem, ratio string, logger log.Logger) (types.OrderResult, sdk.CacheMultiStore, error) {
   133  
   134  	cacheItem := ctx.MultiStore().CacheMultiStore()
   135  	ctxItem := ctx
   136  	ctxItem.SetMultiStore(cacheItem)
   137  	msg := MsgNewOrder{
   138  		Sender:   sender,
   139  		Product:  item.Product,
   140  		Side:     item.Side,
   141  		Price:    item.Price,
   142  		Quantity: item.Quantity,
   143  	}
   144  	order := getOrderFromMsg(ctxItem, k, msg, ratio)
   145  	err := checkOrderNewMsg(ctxItem, k, msg)
   146  
   147  	if err == nil {
   148  		if k.IsProductLocked(ctx, msg.Product) {
   149  			err = types.ErrIsProductLocked(order.Product)
   150  		} else {
   151  			err = k.PlaceOrder(ctxItem, order)
   152  		}
   153  	}
   154  
   155  	res := types.OrderResult{
   156  		Error:   err,
   157  		OrderID: order.OrderID,
   158  	}
   159  
   160  	if err == nil {
   161  		logger.Debug(fmt.Sprintf("BlockHeight<%d>, handler<%s>\n"+
   162  			"    msg<Product:%s,Sender:%s,Price:%s,Quantity:%s,Side:%s>\n"+
   163  			"    TxHash<%s>, Status<%s>\n"+
   164  			"    result<The User have created an order {ID:%s,RemainQuantity:%s,Status:%s} >\n",
   165  			ctx.BlockHeight(), "handleMsgNewOrder",
   166  			msg.Product, msg.Sender, msg.Price.String(), msg.Quantity.String(), msg.Side,
   167  			order.TxHash, types.OrderStatus(types.OrderStatusOpen),
   168  			order.OrderID, order.RemainQuantity.String(), types.OrderStatus(order.Status)))
   169  	} else {
   170  		res.Message = err.Error()
   171  	}
   172  
   173  	return res, cacheItem, err
   174  }
   175  
   176  func handleMsgNewOrders(ctx sdk.Context, k Keeper, msg types.MsgNewOrders,
   177  	logger log.Logger) (*sdk.Result, error) {
   178  	event := sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName))
   179  
   180  	ratio := "1"
   181  	if len(msg.OrderItems) > 1 {
   182  		ratio = "0.8"
   183  	}
   184  
   185  	rs := make([]types.OrderResult, 0, len(msg.OrderItems))
   186  	var handlerResult bitset.BitSet
   187  	for idx, item := range msg.OrderItems {
   188  		res, cacheItem, err := handleNewOrder(ctx, k, msg.Sender, item, ratio, logger)
   189  		if err == nil {
   190  			cacheItem.Write()
   191  			handlerResult.Set(uint(idx))
   192  		}
   193  		rs = append(rs, res)
   194  	}
   195  	rss, err := json.Marshal(&rs)
   196  	if err != nil {
   197  		rss = []byte(fmt.Sprintf("failed to marshal result to JSON: %s", err))
   198  	}
   199  	event = event.AppendAttributes(sdk.NewAttribute("orders", string(rss)))
   200  	ctx.EventManager().EmitEvent(event)
   201  
   202  	if handlerResult.None() {
   203  		return types.ErrAllOrderFailedToExecute().Result()
   204  	}
   205  
   206  	k.AddTxHandlerMsgResult(handlerResult)
   207  	return &sdk.Result{
   208  		Events: ctx.EventManager().Events(),
   209  	}, nil
   210  }
   211  
   212  // ValidateMsgNewOrders validates whether the msg of newOrders is valid.
   213  func ValidateMsgNewOrders(ctx sdk.Context, k keeper.Keeper, msg types.MsgNewOrders) (*sdk.Result, error) {
   214  	ratio := "1"
   215  	if len(msg.OrderItems) > 1 {
   216  		ratio = "0.8"
   217  	}
   218  
   219  	for _, item := range msg.OrderItems {
   220  		msg := MsgNewOrder{
   221  			Sender:   msg.Sender,
   222  			Product:  item.Product,
   223  			Side:     item.Side,
   224  			Price:    item.Price,
   225  			Quantity: item.Quantity,
   226  		}
   227  		err := checkOrderNewMsg(ctx, k, msg)
   228  		if err != nil {
   229  			return nil, err
   230  		}
   231  		if k.IsProductLocked(ctx, msg.Product) {
   232  			return types.ErrIsProductLocked(msg.Product).Result()
   233  		}
   234  
   235  		order := getOrderFromMsg(ctx, k, msg, ratio)
   236  		_, err = k.TryPlaceOrder(ctx, order)
   237  		if err != nil {
   238  			return common.ErrInsufficientCoins(DefaultParamspace, err.Error()).Result()
   239  		}
   240  	}
   241  
   242  	return &sdk.Result{}, nil
   243  
   244  }
   245  
   246  func handleCancelOrder(context sdk.Context, k Keeper, sender sdk.AccAddress, orderID string, logger log.Logger) (
   247  	types.OrderResult, sdk.CacheMultiStore) {
   248  
   249  	cacheItem := context.MultiStore().CacheMultiStore()
   250  	ctx := context
   251  	ctx.SetMultiStore(cacheItem)
   252  
   253  	// Check order
   254  	msg := MsgCancelOrder{
   255  		Sender:  sender,
   256  		OrderID: orderID,
   257  	}
   258  	err := validateCancelOrder(ctx, k, msg)
   259  	var message string
   260  
   261  	if err == nil {
   262  		// cancel order
   263  		order := k.GetOrder(ctx, orderID)
   264  		fee := k.CancelOrder(ctx, order, logger)
   265  		message = fee.String()
   266  	}
   267  
   268  	cancelRes := types.OrderResult{
   269  		Error:   err,
   270  		Message: message,
   271  		OrderID: orderID,
   272  	}
   273  
   274  	return cancelRes, cacheItem
   275  }
   276  
   277  func handleMsgCancelOrders(ctx sdk.Context, k Keeper, msg types.MsgCancelOrders, logger log.Logger) (*sdk.Result, error) {
   278  	cancelRes := []types.OrderResult{}
   279  	var handlerResult bitset.BitSet
   280  	for idx, orderID := range msg.OrderIDs {
   281  
   282  		res, cacheItem := handleCancelOrder(ctx, k, msg.Sender, orderID, logger)
   283  		cancelRes = append(cancelRes, res)
   284  		cacheItem.Write()
   285  		if res.Error == nil {
   286  			handlerResult.Set(uint(idx))
   287  		}
   288  
   289  		logger.Debug(fmt.Sprintf("BlockHeight<%d>, handler<%s>\n"+
   290  			"    msg<Sender:%s,ID:%s>\n"+
   291  			"    result<The User have canceled an order {ID:%s} >\n",
   292  			ctx.BlockHeight(), "handleMsgCancelOrder",
   293  			msg.Sender, orderID, orderID))
   294  
   295  	}
   296  	rss, err := json.Marshal(&cancelRes)
   297  	if err != nil {
   298  		rss = []byte(fmt.Sprintf("failed to marshal result to JSON: %s", err))
   299  	}
   300  
   301  	event := sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName))
   302  	event = event.AppendAttributes(sdk.NewAttribute("orders", string(rss)))
   303  	ctx.EventManager().EmitEvent(event)
   304  
   305  	if handlerResult.None() {
   306  		return types.ErrNoOrdersIsCanceled().Result()
   307  	}
   308  
   309  	k.AddTxHandlerMsgResult(handlerResult)
   310  	return &sdk.Result{
   311  		Events: ctx.EventManager().Events(),
   312  	}, nil
   313  }
   314  
   315  func validateCancelOrder(ctx sdk.Context, keeper keeper.Keeper, msg types.MsgCancelOrder) error {
   316  	order := keeper.GetOrder(ctx, msg.OrderID)
   317  
   318  	// Check order
   319  	if order == nil {
   320  		return types.ErrOrderIsNotExistOrClosed(msg.OrderID)
   321  	}
   322  	if order.Status != types.OrderStatusOpen {
   323  		return types.ErrOrderStatusIsNotOpen()
   324  	}
   325  	if !order.Sender.Equals(msg.Sender) {
   326  		return types.ErrNotOrderOwner(msg.OrderID)
   327  	}
   328  	if keeper.IsProductLocked(ctx, order.Product) {
   329  		return types.ErrIsProductLocked(order.Product)
   330  	}
   331  	return nil
   332  }
   333  
   334  // ValidateMsgCancelOrders validates whether the msg of cancelOrders is valid.
   335  func ValidateMsgCancelOrders(ctx sdk.Context, keeper keeper.Keeper, msg types.MsgCancelOrders) error {
   336  	for _, orderID := range msg.OrderIDs {
   337  		msg := MsgCancelOrder{
   338  			Sender:  msg.Sender,
   339  			OrderID: orderID,
   340  		}
   341  		err := validateCancelOrder(ctx, keeper, msg)
   342  		if err != nil {
   343  			return err
   344  		}
   345  	}
   346  
   347  	return nil
   348  }