code.vegaprotocol.io/vega@v0.79.0/commands/order_submission_test.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package commands_test 17 18 import ( 19 "errors" 20 "math" 21 "testing" 22 23 "code.vegaprotocol.io/vega/commands" 24 "code.vegaprotocol.io/vega/libs/test" 25 types "code.vegaprotocol.io/vega/protos/vega" 26 commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1" 27 28 "github.com/stretchr/testify/assert" 29 ) 30 31 func TestCheckOrderSubmission(t *testing.T) { 32 t.Run("Submitting a nil command fails", testNilOrderSubmissionFails) 33 t.Run("Submitting an empty order fails", testEmptyOrderSubmissionFails) 34 t.Run("Submitting an order without market ID fails", testOrderSubmissionWithoutMarketIDFails) 35 t.Run("Submitting an order with unspecified side fails", testOrderSubmissionWithUnspecifiedSideFails) 36 t.Run("Submitting an order with undefined side fails", testOrderSubmissionWithUndefinedSideFails) 37 t.Run("Submitting an order with unspecified type fails", testOrderSubmissionWithUnspecifiedTypeFails) 38 t.Run("Submitting an order with undefined type fails", testOrderSubmissionWithUndefinedTypeFails) 39 t.Run("Submitting an order with NETWORK type fails", testOrderSubmissionWithNetworkTypeFails) 40 t.Run("Submitting an order with undefined time in force fails", testOrderSubmissionWithUndefinedTimeInForceFails) 41 t.Run("Submitting an order with unspecified time in force fails", testOrderSubmissionWithUnspecifiedTimeInForceFails) 42 t.Run("Submitting an order with non-positive size fails", testOrderSubmissionWithInvalidSizeFails) 43 t.Run("Submitting an order with GTT and non-positive expiration date fails", testOrderSubmissionWithGTTAndNonPositiveExpirationDateFails) 44 t.Run("Submitting an order without GTT and expiration date fails", testOrderSubmissionWithoutGTTAndExpirationDateFails) 45 t.Run("Submitting an order with MARKET type and price fails", testOrderSubmissionWithMarketTypeAndPriceFails) 46 t.Run("Submitting an order with MARKET type and wrong time in force fails", testOrderSubmissionWithMarketTypeAndWrongTimeInForceFails) 47 t.Run("Submitting an order with LIMIT type and no price fails", testOrderSubmissionWithLimitTypeAndNoPriceFails) 48 t.Run("Submitting an order with LIMIT type and negative price fails", testOrderSubmissionWithLimitTypeAndNegativePriceFails) 49 t.Run("Submitting a pegged order with LIMIT type and no price succeeds", testPeggedOrderSubmissionWithLimitTypeAndNoPriceSucceeds) 50 t.Run("Submitting a pegged order with undefined time in force fails", testPeggedOrderSubmissionWithUndefinedReferenceFails) 51 t.Run("Submitting a pegged order with unspecified time in force fails", testPeggedOrderSubmissionWithUnspecifiedReferenceFails) 52 t.Run("Submitting a pegged order without LIMIT type fails", testPeggedOrderSubmissionWithoutLimitTypeFails) 53 t.Run("Submitting a pegged order with LIMIT type succeeds", testPeggedOrderSubmissionWithLimitTypeSucceeds) 54 t.Run("Submitting a pegged order with wrong time in force fails", testPeggedOrderSubmissionWithWrongTimeInForceFails) 55 t.Run("Submitting a pegged order with right time in force succeeds", testPeggedOrderSubmissionWithRightTimeInForceSucceeds) 56 t.Run("Submitting a pegged order with side buy and best ask reference fails", testPeggedOrderSubmissionWithSideBuyAndBestAskReferenceFails) 57 t.Run("Submitting a pegged order with side buy and best bid reference succeeds", testPeggedOrderSubmissionWithSideBuyAndBestBidReferenceSucceeds) 58 t.Run("Submitting a pegged order with side buy and best bid reference and negative offset fails", testPeggedOrderSubmissionWithSideBuyAndBestBidReferenceAndNegativeOffsetFails) 59 t.Run("Submitting a pegged order with side buy and best bid reference and non-negative offset succeeds", testPeggedOrderSubmissionWithSideBuyAndBestBidReferenceAndNonNegativeOffsetSucceeds) 60 t.Run("Submitting a pegged order with side buy and mid reference and non-negative offset fails", testPeggedOrderSubmissionWithSideBuyAndMidReferenceAndNonPositiveOffsetFails) 61 t.Run("Submitting a pegged order with side buy and mid reference and negative offset succeeds", testPeggedOrderSubmissionWithSideBuyAndMidReferenceAndNegativeOffsetSucceeds) 62 t.Run("Submitting a pegged order with side sell and best bid reference fails", testPeggedOrderSubmissionWithSideSellAndBestBidReferenceFails) 63 t.Run("Submitting a pegged order with side sell and best ask reference succeeds", testPeggedOrderSubmissionWithSideSellAndBestAskReferenceSucceeds) 64 t.Run("Submitting a pegged order with side sell and best ask reference and negative offset fails", testPeggedOrderSubmissionWithSideSellAndBestAskReferenceAndNegativeOffsetFails) 65 t.Run("Submitting a pegged order with side sell and best ask reference and non negative offset succeeds", testPeggedOrderSubmissionWithSideSellAndBestAskReferenceAndNonNegativeOffsetSucceeds) 66 t.Run("Submitting a pegged order with side sell and mid reference and non-positive offset fails", testPeggedOrderSubmissionWithSideSellAndMidReferenceAndNonPositiveOffsetFails) 67 t.Run("Submitting a pegged order with side sell and mid reference and positive offset succeeds", testPeggedOrderSubmissionWithSideSellAndMidReferenceAndPositiveOffsetSucceeds) 68 t.Run("Submitting Post or Reduce only orders", testSubmittingPostOrReduceOnlyOrders) 69 t.Run("Submitting iceberg orders", testSubmittingIcebergOrders) 70 } 71 72 func testSubmittingIcebergOrders(t *testing.T) { 73 testCases := []struct { 74 submission commandspb.OrderSubmission 75 errString string 76 field string 77 }{ 78 { 79 submission: commandspb.OrderSubmission{ 80 IcebergOpts: &commandspb.IcebergOpts{ 81 PeakSize: 5, 82 MinimumVisibleSize: 100, 83 }, 84 }, 85 errString: "must be >= order_submission.iceberg_opts.minimum_visible_size", 86 field: "order_submission.iceberg_opts.peak_size", 87 }, 88 { 89 submission: commandspb.OrderSubmission{ 90 IcebergOpts: &commandspb.IcebergOpts{ 91 PeakSize: 100, 92 MinimumVisibleSize: 10, 93 }, 94 TimeInForce: types.Order_TIME_IN_FORCE_FOK, 95 }, 96 errString: "iceberg order must be a persistent order", 97 field: "order_submission.time_in_force", 98 }, 99 { 100 submission: commandspb.OrderSubmission{ 101 IcebergOpts: &commandspb.IcebergOpts{ 102 PeakSize: 100, 103 MinimumVisibleSize: 10, 104 }, 105 TimeInForce: types.Order_TIME_IN_FORCE_IOC, 106 }, 107 errString: "iceberg order must be a persistent order", 108 field: "order_submission.time_in_force", 109 }, 110 { 111 submission: commandspb.OrderSubmission{ 112 IcebergOpts: &commandspb.IcebergOpts{ 113 PeakSize: 100, 114 MinimumVisibleSize: 10, 115 }, 116 Type: types.Order_TYPE_MARKET, 117 }, 118 errString: "iceberg order must be of type LIMIT", 119 field: "order_submission.type", 120 }, 121 { 122 submission: commandspb.OrderSubmission{ 123 Size: 50, 124 IcebergOpts: &commandspb.IcebergOpts{ 125 PeakSize: 100, 126 MinimumVisibleSize: 10, 127 }, 128 }, 129 errString: "must be <= order_submission.size", 130 field: "order_submission.iceberg_opts.peak_size", 131 }, 132 { 133 submission: commandspb.OrderSubmission{ 134 Size: 200, 135 ReduceOnly: true, 136 IcebergOpts: &commandspb.IcebergOpts{ 137 PeakSize: 100, 138 MinimumVisibleSize: 10, 139 }, 140 }, 141 errString: "iceberg order must not be reduce-only", 142 field: "order_submission.reduce_only", 143 }, 144 } 145 146 for _, tc := range testCases { 147 errs := checkOrderSubmission(&tc.submission).Get(tc.field) 148 if len(tc.errString) == 0 { 149 assert.Len(t, errs, 0) 150 continue 151 } 152 assert.Contains(t, errs, errors.New(tc.errString)) 153 } 154 } 155 156 func testSubmittingPostOrReduceOnlyOrders(t *testing.T) { 157 testCases := []struct { 158 submission commandspb.OrderSubmission 159 errString string 160 field string 161 }{ 162 { 163 submission: commandspb.OrderSubmission{ 164 PostOnly: true, 165 ReduceOnly: true, 166 }, 167 errString: "cannot be true at the same time as order_submission.reduce_only", 168 field: "order_submission.post_only", 169 }, 170 { 171 submission: commandspb.OrderSubmission{ 172 Type: types.Order_TYPE_MARKET, 173 PostOnly: true, 174 }, 175 errString: "only valid for limit orders", 176 field: "order_submission.post_only", 177 }, 178 { 179 submission: commandspb.OrderSubmission{ 180 Type: types.Order_TYPE_MARKET, 181 TimeInForce: types.Order_TIME_IN_FORCE_FOK, 182 PostOnly: true, 183 }, 184 errString: "only valid for persistent orders", 185 field: "order_submission.post_only", 186 }, 187 { 188 submission: commandspb.OrderSubmission{ 189 Type: types.Order_TYPE_MARKET, 190 TimeInForce: types.Order_TIME_IN_FORCE_FOK, 191 PostOnly: true, 192 }, 193 errString: "only valid for persistent orders", 194 field: "order_submission.post_only", 195 }, 196 { 197 submission: commandspb.OrderSubmission{ 198 Type: types.Order_TYPE_LIMIT, 199 TimeInForce: types.Order_TIME_IN_FORCE_GTC, 200 ReduceOnly: true, 201 }, 202 errString: "only valid for non-persistent orders", 203 field: "order_submission.reduce_only", 204 }, 205 { 206 submission: commandspb.OrderSubmission{ 207 Type: types.Order_TYPE_LIMIT, 208 TimeInForce: types.Order_TIME_IN_FORCE_IOC, 209 ReduceOnly: true, 210 PeggedOrder: &types.PeggedOrder{ 211 Reference: types.PeggedReference_PEGGED_REFERENCE_BEST_ASK, 212 }, 213 }, 214 errString: "cannot be pegged", 215 field: "order_submission.reduce_only", 216 }, 217 // valid cases 218 { 219 submission: commandspb.OrderSubmission{ 220 Type: types.Order_TYPE_LIMIT, 221 TimeInForce: types.Order_TIME_IN_FORCE_IOC, 222 ReduceOnly: true, 223 }, 224 errString: "", 225 field: "order_submission.reduce_only", 226 }, 227 { 228 submission: commandspb.OrderSubmission{ 229 Type: types.Order_TYPE_LIMIT, 230 TimeInForce: types.Order_TIME_IN_FORCE_GTC, 231 PostOnly: true, 232 }, 233 errString: "", 234 field: "order_submission.post_only", 235 }, 236 } 237 238 for _, tc := range testCases { 239 errs := checkOrderSubmission(&tc.submission).Get(tc.field) 240 if len(tc.errString) == 0 { 241 assert.Len(t, errs, 0) 242 continue 243 } 244 assert.Contains(t, errs, errors.New(tc.errString)) 245 } 246 } 247 248 func testEmptyOrderSubmissionFails(t *testing.T) { 249 err := checkOrderSubmission(&commandspb.OrderSubmission{}) 250 251 assert.Error(t, err) 252 } 253 254 func testNilOrderSubmissionFails(t *testing.T) { 255 err := checkOrderSubmission(nil) 256 257 assert.Contains(t, err.Get("order_submission"), commands.ErrIsRequired) 258 } 259 260 func testOrderSubmissionWithoutMarketIDFails(t *testing.T) { 261 err := checkOrderSubmission(&commandspb.OrderSubmission{ 262 MarketId: "", 263 }) 264 265 assert.Contains(t, err.Get("order_submission.market_id"), commands.ErrIsRequired) 266 } 267 268 func testOrderSubmissionWithUnspecifiedSideFails(t *testing.T) { 269 err := checkOrderSubmission(&commandspb.OrderSubmission{ 270 Side: types.Side_SIDE_UNSPECIFIED, 271 }) 272 273 assert.Contains(t, err.Get("order_submission.side"), commands.ErrIsRequired) 274 } 275 276 func testOrderSubmissionWithUndefinedSideFails(t *testing.T) { 277 err := checkOrderSubmission(&commandspb.OrderSubmission{ 278 Side: types.Side(-42), 279 }) 280 281 assert.Contains(t, err.Get("order_submission.side"), commands.ErrIsNotValid) 282 } 283 284 func testOrderSubmissionWithUnspecifiedTypeFails(t *testing.T) { 285 err := checkOrderSubmission(&commandspb.OrderSubmission{ 286 Type: types.Order_TYPE_UNSPECIFIED, 287 }) 288 289 assert.Contains(t, err.Get("order_submission.type"), commands.ErrIsRequired) 290 } 291 292 func testOrderSubmissionWithUndefinedTypeFails(t *testing.T) { 293 err := checkOrderSubmission(&commandspb.OrderSubmission{ 294 Type: types.Order_Type(-42), 295 }) 296 297 assert.Contains(t, err.Get("order_submission.type"), commands.ErrIsNotValid) 298 } 299 300 func testOrderSubmissionWithNetworkTypeFails(t *testing.T) { 301 err := checkOrderSubmission(&commandspb.OrderSubmission{ 302 Type: types.Order_TYPE_NETWORK, 303 }) 304 305 assert.Contains(t, err.Get("order_submission.type"), commands.ErrIsUnauthorised) 306 } 307 308 func testOrderSubmissionWithUnspecifiedTimeInForceFails(t *testing.T) { 309 err := checkOrderSubmission(&commandspb.OrderSubmission{ 310 TimeInForce: types.Order_TIME_IN_FORCE_UNSPECIFIED, 311 }) 312 313 assert.Contains(t, err.Get("order_submission.time_in_force"), commands.ErrIsRequired) 314 } 315 316 func testOrderSubmissionWithUndefinedTimeInForceFails(t *testing.T) { 317 err := checkOrderSubmission(&commandspb.OrderSubmission{ 318 TimeInForce: types.Order_TimeInForce(-42), 319 }) 320 321 assert.Contains(t, err.Get("order_submission.time_in_force"), commands.ErrIsNotValid) 322 } 323 324 func testOrderSubmissionWithInvalidSizeFails(t *testing.T) { 325 // FIXME(big int) doesn't test negative numbers since it's an unsigned int 326 // but that will definitely be needed when moving to big int. 327 err := checkOrderSubmission(&commandspb.OrderSubmission{ 328 Size: 0, 329 }) 330 331 assert.Contains(t, err.Get("order_submission.size"), commands.ErrMustBePositive) 332 333 err = checkOrderSubmission(&commandspb.OrderSubmission{ 334 Size: math.MaxInt64, 335 }) 336 assert.Contains(t, err.Get("order_submission.size"), commands.ErrSizeIsTooLarge) 337 } 338 339 func testOrderSubmissionWithGTTAndNonPositiveExpirationDateFails(t *testing.T) { 340 testCases := []struct { 341 msg string 342 value int64 343 }{ 344 { 345 msg: "with 0 as expiration date", 346 value: 0, 347 }, { 348 msg: "with negative expiration date", 349 value: test.RandomNegativeI64(), 350 }, 351 } 352 for _, tc := range testCases { 353 t.Run(tc.msg, func(t *testing.T) { 354 err := checkOrderSubmission(&commandspb.OrderSubmission{ 355 TimeInForce: types.Order_TIME_IN_FORCE_GTT, 356 ExpiresAt: tc.value, 357 }) 358 359 assert.Contains(t, err.Get("order_submission.expires_at"), commands.ErrMustBePositive) 360 }) 361 } 362 } 363 364 func testOrderSubmissionWithoutGTTAndExpirationDateFails(t *testing.T) { 365 testCases := []struct { 366 msg string 367 value types.Order_TimeInForce 368 }{ 369 { 370 msg: "with GTC", 371 value: types.Order_TIME_IN_FORCE_GTC, 372 }, { 373 msg: "with IOC", 374 value: types.Order_TIME_IN_FORCE_IOC, 375 }, { 376 msg: "with FOK", 377 value: types.Order_TIME_IN_FORCE_FOK, 378 }, { 379 msg: "with GFA", 380 value: types.Order_TIME_IN_FORCE_GFA, 381 }, { 382 msg: "with GFN", 383 value: types.Order_TIME_IN_FORCE_GFN, 384 }, 385 } 386 for _, tc := range testCases { 387 t.Run(tc.msg, func(t *testing.T) { 388 err := checkOrderSubmission(&commandspb.OrderSubmission{ 389 TimeInForce: tc.value, 390 ExpiresAt: test.RandomI64(), 391 }) 392 393 assert.Contains(t, err.Get("order_submission.expires_at"), errors.New("is only available when the time in force is of type GTT")) 394 }) 395 } 396 } 397 398 func testOrderSubmissionWithMarketTypeAndPriceFails(t *testing.T) { 399 err := checkOrderSubmission(&commandspb.OrderSubmission{ 400 Type: types.Order_TYPE_MARKET, 401 Price: test.RandomPositiveU64AsString(), 402 }) 403 404 assert.Contains(t, err.Get("order_submission.price"), errors.New("is unavailable when the order is of type MARKET")) 405 } 406 407 func testOrderSubmissionWithMarketTypeAndWrongTimeInForceFails(t *testing.T) { 408 testCases := []struct { 409 msg string 410 value types.Order_TimeInForce 411 }{ 412 { 413 msg: "with GTC", 414 value: types.Order_TIME_IN_FORCE_GTC, 415 }, { 416 msg: "with GTT", 417 value: types.Order_TIME_IN_FORCE_GTT, 418 }, { 419 msg: "with GFA", 420 value: types.Order_TIME_IN_FORCE_GFA, 421 }, { 422 msg: "with GFN", 423 value: types.Order_TIME_IN_FORCE_GFN, 424 }, 425 } 426 for _, tc := range testCases { 427 t.Run(tc.msg, func(t *testing.T) { 428 err := checkOrderSubmission(&commandspb.OrderSubmission{ 429 Type: types.Order_TYPE_MARKET, 430 TimeInForce: tc.value, 431 }) 432 433 assert.Contains(t, err.Get("order_submission.time_in_force"), errors.New("is expected to be of type FOK or IOC when order is of type MARKET")) 434 }) 435 } 436 } 437 438 func testOrderSubmissionWithLimitTypeAndNoPriceFails(t *testing.T) { 439 err := checkOrderSubmission(&commandspb.OrderSubmission{ 440 Type: types.Order_TYPE_LIMIT, 441 }) 442 443 assert.Contains(t, err.Get("order_submission.price"), errors.New("is required when the order is of type LIMIT")) 444 } 445 446 func testOrderSubmissionWithLimitTypeAndNegativePriceFails(t *testing.T) { 447 err := checkOrderSubmission(&commandspb.OrderSubmission{ 448 Type: types.Order_TYPE_LIMIT, 449 Price: "-1000", 450 }) 451 452 assert.Contains(t, err.Get("order_submission.price"), errors.New("must be positive when the order is of type LIMIT")) 453 } 454 455 func testPeggedOrderSubmissionWithLimitTypeAndNoPriceSucceeds(t *testing.T) { 456 err := checkOrderSubmission(&commandspb.OrderSubmission{ 457 Type: types.Order_TYPE_LIMIT, 458 PeggedOrder: &types.PeggedOrder{}, 459 }) 460 461 assert.NotContains(t, err.Get("order_submission.price"), errors.New("is required when the order is of type LIMIT")) 462 } 463 464 func testPeggedOrderSubmissionWithUnspecifiedReferenceFails(t *testing.T) { 465 err := checkOrderSubmission(&commandspb.OrderSubmission{ 466 PeggedOrder: &types.PeggedOrder{ 467 Reference: types.PeggedReference_PEGGED_REFERENCE_UNSPECIFIED, 468 }, 469 }) 470 471 assert.Contains(t, err.Get("order_submission.pegged_order.reference"), commands.ErrIsRequired) 472 } 473 474 func testPeggedOrderSubmissionWithUndefinedReferenceFails(t *testing.T) { 475 err := checkOrderSubmission(&commandspb.OrderSubmission{ 476 PeggedOrder: &types.PeggedOrder{ 477 Reference: types.PeggedReference(-42), 478 }, 479 }) 480 481 assert.Contains(t, err.Get("order_submission.pegged_order.reference"), commands.ErrIsNotValid) 482 } 483 484 func testPeggedOrderSubmissionWithoutLimitTypeFails(t *testing.T) { 485 testCases := []struct { 486 msg string 487 value types.Order_Type 488 }{ 489 { 490 msg: "with MARKET", 491 value: types.Order_TYPE_MARKET, 492 }, { 493 msg: "with NETWORK", 494 value: types.Order_TYPE_NETWORK, 495 }, 496 } 497 for _, tc := range testCases { 498 t.Run(tc.msg, func(t *testing.T) { 499 err := checkOrderSubmission(&commandspb.OrderSubmission{ 500 Type: tc.value, 501 PeggedOrder: &types.PeggedOrder{}, 502 }) 503 504 assert.Contains(t, err.Get("order_submission.type"), errors.New("is expected to be an order of type LIMIT when the order is pegged")) 505 }) 506 } 507 } 508 509 func testPeggedOrderSubmissionWithLimitTypeSucceeds(t *testing.T) { 510 err := checkOrderSubmission(&commandspb.OrderSubmission{ 511 Type: types.Order_TYPE_LIMIT, 512 PeggedOrder: &types.PeggedOrder{}, 513 }) 514 515 assert.NotContains(t, err.Get("order_submission.type"), errors.New("is expected to be an order of type LIMIT when the order is pegged")) 516 } 517 518 func testPeggedOrderSubmissionWithWrongTimeInForceFails(t *testing.T) { 519 testCases := []struct { 520 msg string 521 value types.Order_TimeInForce 522 }{ 523 { 524 msg: "with IOC", 525 value: types.Order_TIME_IN_FORCE_IOC, 526 }, { 527 msg: "with FOK", 528 value: types.Order_TIME_IN_FORCE_FOK, 529 }, { 530 msg: "with GFA", 531 value: types.Order_TIME_IN_FORCE_GFA, 532 }, 533 } 534 for _, tc := range testCases { 535 t.Run(tc.msg, func(t *testing.T) { 536 err := checkOrderSubmission(&commandspb.OrderSubmission{ 537 TimeInForce: tc.value, 538 PeggedOrder: &types.PeggedOrder{}, 539 }) 540 541 assert.Contains(t, err.Get("order_submission.time_in_force"), errors.New("is expected to have a time in force of type GTT, GTC or GFN when the order is pegged")) 542 }) 543 } 544 } 545 546 func testPeggedOrderSubmissionWithRightTimeInForceSucceeds(t *testing.T) { 547 testCases := []struct { 548 msg string 549 value types.Order_TimeInForce 550 }{ 551 { 552 msg: "with GTC", 553 value: types.Order_TIME_IN_FORCE_GTC, 554 }, { 555 msg: "with GTT", 556 value: types.Order_TIME_IN_FORCE_GTT, 557 }, { 558 msg: "with GFN", 559 value: types.Order_TIME_IN_FORCE_GFN, 560 }, 561 } 562 for _, tc := range testCases { 563 t.Run(tc.msg, func(t *testing.T) { 564 err := checkOrderSubmission(&commandspb.OrderSubmission{ 565 TimeInForce: tc.value, 566 PeggedOrder: &types.PeggedOrder{}, 567 }) 568 569 assert.NotContains(t, err.Get("order_submission.time_in_force"), errors.New("is expected to have a time in force of type GTT, GTC or GFN when the order is pegged")) 570 }) 571 } 572 } 573 574 func testPeggedOrderSubmissionWithSideBuyAndBestAskReferenceFails(t *testing.T) { 575 err := checkOrderSubmission(&commandspb.OrderSubmission{ 576 Side: types.Side_SIDE_BUY, 577 PeggedOrder: &types.PeggedOrder{ 578 Reference: types.PeggedReference_PEGGED_REFERENCE_BEST_ASK, 579 }, 580 }) 581 582 assert.Contains(t, err.Get("order_submission.pegged_order.reference"), errors.New("cannot have a reference of type BEST_ASK when on BUY side")) 583 } 584 585 func testPeggedOrderSubmissionWithSideBuyAndBestBidReferenceSucceeds(t *testing.T) { 586 err := checkOrderSubmission(&commandspb.OrderSubmission{ 587 Side: types.Side_SIDE_BUY, 588 PeggedOrder: &types.PeggedOrder{ 589 Reference: types.PeggedReference_PEGGED_REFERENCE_BEST_BID, 590 }, 591 }) 592 593 assert.NotContains(t, err.Get("order_submission.pegged_order.reference"), errors.New("cannot have a reference of type BEST_ASK when on BUY side")) 594 } 595 596 func testPeggedOrderSubmissionWithSideBuyAndBestBidReferenceAndNegativeOffsetFails(t *testing.T) { 597 err := checkOrderSubmission(&commandspb.OrderSubmission{ 598 Side: types.Side_SIDE_BUY, 599 PeggedOrder: &types.PeggedOrder{ 600 Reference: types.PeggedReference_PEGGED_REFERENCE_BEST_BID, 601 Offset: "-1", 602 }, 603 }) 604 605 assert.Contains(t, err.Get("order_submission.pegged_order.offset"), errors.New("must be positive or zero")) 606 } 607 608 func testPeggedOrderSubmissionWithSideBuyAndBestBidReferenceAndNonNegativeOffsetSucceeds(t *testing.T) { 609 testCases := []struct { 610 msg string 611 value string 612 }{ 613 { 614 msg: "with 0 offset", 615 value: "0", 616 }, { 617 msg: "with positive offset", 618 value: test.RandomPositiveU64AsString(), 619 }, 620 } 621 for _, tc := range testCases { 622 t.Run(tc.msg, func(t *testing.T) { 623 err := checkOrderSubmission(&commandspb.OrderSubmission{ 624 Side: types.Side_SIDE_BUY, 625 PeggedOrder: &types.PeggedOrder{ 626 Reference: types.PeggedReference_PEGGED_REFERENCE_BEST_BID, 627 Offset: tc.value, 628 }, 629 }) 630 631 assert.NotContains(t, err.Get("order_submission.pegged_order.offset"), errors.New("must be positive or zero")) 632 }) 633 } 634 } 635 636 func testPeggedOrderSubmissionWithSideBuyAndMidReferenceAndNonPositiveOffsetFails(t *testing.T) { 637 testCases := []struct { 638 msg string 639 value string 640 }{ 641 { 642 msg: "with 0 offset", 643 value: "0", 644 }, { 645 msg: "with negative offset", 646 value: test.RandomNegativeI64AsString(), 647 }, 648 } 649 for _, tc := range testCases { 650 t.Run(tc.msg, func(t *testing.T) { 651 err := checkOrderSubmission(&commandspb.OrderSubmission{ 652 Side: types.Side_SIDE_BUY, 653 PeggedOrder: &types.PeggedOrder{ 654 Reference: types.PeggedReference_PEGGED_REFERENCE_MID, 655 Offset: tc.value, 656 }, 657 }) 658 659 assert.Contains(t, err.Get("order_submission.pegged_order.offset"), errors.New("must be positive")) 660 }) 661 } 662 } 663 664 func testPeggedOrderSubmissionWithSideBuyAndMidReferenceAndNegativeOffsetSucceeds(t *testing.T) { 665 err := checkOrderSubmission(&commandspb.OrderSubmission{ 666 Side: types.Side_SIDE_BUY, 667 PeggedOrder: &types.PeggedOrder{ 668 Reference: types.PeggedReference_PEGGED_REFERENCE_MID, 669 Offset: test.RandomPositiveU64AsString(), 670 }, 671 }) 672 673 assert.NotContains(t, err.Get("order_submission.pegged_order.offset"), errors.New("must be negative")) 674 } 675 676 func testPeggedOrderSubmissionWithSideSellAndBestBidReferenceFails(t *testing.T) { 677 err := checkOrderSubmission(&commandspb.OrderSubmission{ 678 Side: types.Side_SIDE_SELL, 679 PeggedOrder: &types.PeggedOrder{ 680 Reference: types.PeggedReference_PEGGED_REFERENCE_BEST_BID, 681 }, 682 }) 683 684 assert.Contains(t, err.Get("order_submission.pegged_order.reference"), errors.New("cannot have a reference of type BEST_BID when on SELL side")) 685 } 686 687 func testPeggedOrderSubmissionWithSideSellAndBestAskReferenceSucceeds(t *testing.T) { 688 err := checkOrderSubmission(&commandspb.OrderSubmission{ 689 Side: types.Side_SIDE_SELL, 690 PeggedOrder: &types.PeggedOrder{ 691 Reference: types.PeggedReference_PEGGED_REFERENCE_BEST_ASK, 692 }, 693 }) 694 695 assert.NotContains(t, err.Get("order_submission.pegged_order.reference"), errors.New("cannot have a reference of type BEST_BID when on SELL side")) 696 } 697 698 func testPeggedOrderSubmissionWithSideSellAndBestAskReferenceAndNegativeOffsetFails(t *testing.T) { 699 err := checkOrderSubmission(&commandspb.OrderSubmission{ 700 Side: types.Side_SIDE_SELL, 701 PeggedOrder: &types.PeggedOrder{ 702 Reference: types.PeggedReference_PEGGED_REFERENCE_BEST_ASK, 703 Offset: test.RandomNegativeI64AsString(), 704 }, 705 }) 706 707 assert.Contains(t, err.Get("order_submission.pegged_order.offset"), errors.New("must be positive or zero")) 708 } 709 710 func testPeggedOrderSubmissionWithSideSellAndBestAskReferenceAndNonNegativeOffsetSucceeds(t *testing.T) { 711 testCases := []struct { 712 msg string 713 value string 714 }{ 715 { 716 msg: "with 0 offset", 717 value: "0", 718 }, { 719 msg: "with positive offset", 720 value: test.RandomPositiveU64AsString(), 721 }, 722 } 723 for _, tc := range testCases { 724 t.Run(tc.msg, func(t *testing.T) { 725 err := checkOrderSubmission(&commandspb.OrderSubmission{ 726 Side: types.Side_SIDE_SELL, 727 PeggedOrder: &types.PeggedOrder{ 728 Reference: types.PeggedReference_PEGGED_REFERENCE_BEST_ASK, 729 Offset: tc.value, 730 }, 731 }) 732 733 assert.NotContains(t, err.Get("order_submission.pegged_order.offset"), errors.New("must be positive or zero")) 734 }) 735 } 736 } 737 738 func testPeggedOrderSubmissionWithSideSellAndMidReferenceAndNonPositiveOffsetFails(t *testing.T) { 739 testCases := []struct { 740 msg string 741 value string 742 }{ 743 { 744 msg: "with 0 offset", 745 value: "0", 746 }, { 747 msg: "with negative offset", 748 value: "-1", 749 }, 750 } 751 for _, tc := range testCases { 752 t.Run(tc.msg, func(t *testing.T) { 753 err := checkOrderSubmission(&commandspb.OrderSubmission{ 754 Side: types.Side_SIDE_SELL, 755 PeggedOrder: &types.PeggedOrder{ 756 Reference: types.PeggedReference_PEGGED_REFERENCE_MID, 757 Offset: tc.value, 758 }, 759 }) 760 761 assert.Contains(t, err.Get("order_submission.pegged_order.offset"), errors.New("must be positive")) 762 }) 763 } 764 } 765 766 func testPeggedOrderSubmissionWithSideSellAndMidReferenceAndPositiveOffsetSucceeds(t *testing.T) { 767 err := checkOrderSubmission(&commandspb.OrderSubmission{ 768 Side: types.Side_SIDE_SELL, 769 PeggedOrder: &types.PeggedOrder{ 770 Reference: types.PeggedReference_PEGGED_REFERENCE_MID, 771 Offset: test.RandomPositiveU64AsString(), 772 }, 773 }) 774 775 assert.NotContains(t, err.Get("order_submission.pegged_order.offset"), errors.New("must be positive")) 776 } 777 778 func checkOrderSubmission(cmd *commandspb.OrderSubmission) commands.Errors { 779 err := commands.CheckOrderSubmission(cmd) 780 781 var e commands.Errors 782 if ok := errors.As(err, &e); !ok { 783 return commands.NewErrors() 784 } 785 786 return e 787 }