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 }