code.vegaprotocol.io/vega@v0.79.0/datanode/sqlstore/stop_orders_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 sqlstore_test 17 18 import ( 19 "fmt" 20 "sort" 21 "testing" 22 "time" 23 24 "code.vegaprotocol.io/vega/datanode/entities" 25 "code.vegaprotocol.io/vega/datanode/sqlstore" 26 "code.vegaprotocol.io/vega/datanode/sqlstore/helpers" 27 "code.vegaprotocol.io/vega/libs/ptr" 28 vegapb "code.vegaprotocol.io/vega/protos/vega" 29 commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1" 30 31 "github.com/georgysavva/scany/pgxscan" 32 "github.com/stretchr/testify/assert" 33 "github.com/stretchr/testify/require" 34 ) 35 36 type testStopOrderInputs struct { 37 orderID string 38 vegaTime time.Time 39 createdAt time.Time 40 triggerPrice string 41 partyID entities.PartyID 42 marketID entities.MarketID 43 status entities.StopOrderStatus 44 expiryStrategy entities.StopOrderExpiryStrategy 45 } 46 47 func generateTestStopOrders(t *testing.T, testOrders []testStopOrderInputs) []entities.StopOrder { 48 t.Helper() 49 orders := make([]entities.StopOrder, 0) 50 51 for i, o := range testOrders { 52 so := entities.StopOrder{ 53 ID: entities.StopOrderID(o.orderID), 54 ExpiryStrategy: o.expiryStrategy, 55 TriggerDirection: 0, 56 Status: o.status, 57 CreatedAt: o.createdAt, 58 UpdatedAt: nil, 59 OrderID: "", 60 TriggerPrice: ptr.From(o.triggerPrice), 61 PartyID: o.partyID, 62 MarketID: o.marketID, 63 VegaTime: o.vegaTime, 64 SeqNum: uint64(i), 65 TxHash: txHashFromString(fmt.Sprintf("%s-%s-%03d", o.orderID, o.vegaTime.String(), i)), 66 Submission: &commandspb.OrderSubmission{ 67 MarketId: o.marketID.String(), 68 Price: "100", 69 Size: uint64(100 + i), 70 Side: entities.SideBuy, 71 TimeInForce: entities.OrderTimeInForceUnspecified, 72 ExpiresAt: 0, 73 Type: entities.OrderTypeMarket, 74 Reference: o.orderID, 75 }, 76 RejectionReason: entities.StopOrderRejectionReasonUnspecified, 77 } 78 orders = append(orders, so) 79 } 80 81 return orders 82 } 83 84 func TestStopOrders_Add(t *testing.T) { 85 so := sqlstore.NewStopOrders(connectionSource) 86 bs := sqlstore.NewBlocks(connectionSource) 87 ps := sqlstore.NewParties(connectionSource) 88 ms := sqlstore.NewMarkets(connectionSource) 89 90 ctx := tempTransaction(t) 91 92 blocks := []entities.Block{ 93 addTestBlock(t, ctx, bs), 94 addTestBlock(t, ctx, bs), 95 addTestBlock(t, ctx, bs), 96 } 97 98 parties := []entities.Party{ 99 addTestParty(t, ctx, ps, blocks[0]), 100 addTestParty(t, ctx, ps, blocks[0]), 101 addTestParty(t, ctx, ps, blocks[0]), 102 } 103 104 markets := helpers.GenerateMarkets(t, ctx, 3, blocks[0], ms) 105 106 inputs := make([]testStopOrderInputs, 0) 107 108 for _, b := range blocks { 109 for _, p := range parties { 110 for _, m := range markets { 111 inputs = append(inputs, testStopOrderInputs{ 112 orderID: GenerateID(), 113 vegaTime: b.VegaTime, 114 createdAt: b.VegaTime, 115 triggerPrice: "100", 116 partyID: p.ID, 117 marketID: m.ID, 118 status: entities.StopOrderStatusUnspecified, 119 expiryStrategy: entities.StopOrderExpiryStrategyUnspecified, 120 }) 121 } 122 } 123 } 124 125 stopOrders := generateTestStopOrders(t, inputs) 126 127 t.Run("add should batch orders and not insert the records", func(t *testing.T) { 128 for _, o := range stopOrders { 129 err := so.Add(o) 130 require.NoError(t, err) 131 } 132 133 rows, err := connectionSource.Query(ctx, "select * from stop_orders") 134 require.NoError(t, err) 135 assert.False(t, rows.Next()) 136 137 t.Run("and insert them when flush is called", func(t *testing.T) { 138 orders, err := so.Flush(ctx) 139 require.NoError(t, err) 140 assert.Len(t, orders, len(stopOrders)) 141 142 var results []entities.StopOrder 143 err = pgxscan.Select(ctx, connectionSource, &results, "select * from stop_orders") 144 require.NoError(t, err) 145 assert.Len(t, results, len(stopOrders)) 146 assert.ElementsMatch(t, results, orders) 147 }) 148 }) 149 } 150 151 func TestStopOrders_Get(t *testing.T) { 152 so := sqlstore.NewStopOrders(connectionSource) 153 bs := sqlstore.NewBlocks(connectionSource) 154 ps := sqlstore.NewParties(connectionSource) 155 ms := sqlstore.NewMarkets(connectionSource) 156 157 ctx := tempTransaction(t) 158 159 block := addTestBlock(t, ctx, bs) 160 block2 := addTestBlock(t, ctx, bs) 161 162 party := addTestParty(t, ctx, ps, block) 163 164 markets := helpers.GenerateMarkets(t, ctx, 1, block, ms) 165 166 orderID := GenerateID() 167 stopOrders := generateTestStopOrders(t, []testStopOrderInputs{ 168 { 169 orderID: orderID, 170 vegaTime: block.VegaTime, 171 createdAt: block.VegaTime, 172 triggerPrice: "100", 173 partyID: party.ID, 174 marketID: markets[0].ID, 175 status: entities.StopOrderStatusPending, 176 expiryStrategy: entities.StopOrderExpiryStrategyUnspecified, 177 }, 178 { 179 orderID: orderID, 180 vegaTime: block2.VegaTime, 181 createdAt: block.VegaTime, 182 triggerPrice: "100", 183 partyID: party.ID, 184 marketID: markets[0].ID, 185 status: entities.StopOrderStatusTriggered, 186 expiryStrategy: entities.StopOrderExpiryStrategyUnspecified, 187 }, 188 }) 189 190 for i := range stopOrders { 191 err := so.Add(stopOrders[i]) 192 require.NoError(t, err) 193 } 194 195 want, err := so.Flush(ctx) 196 require.NoError(t, err) 197 198 t.Run("Get should return an error if the order ID does not exist", func(t *testing.T) { 199 got, err := so.GetStopOrder(ctx, "deadbeef") 200 require.Error(t, err) 201 assert.Equal(t, entities.StopOrder{}, got) 202 }) 203 204 t.Run("Get should return the order if the order ID does exist", func(t *testing.T) { 205 got, err := so.GetStopOrder(ctx, orderID) 206 require.NoError(t, err) 207 assert.Equal(t, want[1], got) 208 }) 209 } 210 211 func TestStopOrders_ListStopOrders(t *testing.T) { 212 so := sqlstore.NewStopOrders(connectionSource) 213 bs := sqlstore.NewBlocks(connectionSource) 214 ps := sqlstore.NewParties(connectionSource) 215 ms := sqlstore.NewMarkets(connectionSource) 216 217 ctx := tempTransaction(t) 218 219 blocks := []entities.Block{ 220 addTestBlock(t, ctx, bs), 221 addTestBlock(t, ctx, bs), 222 addTestBlock(t, ctx, bs), 223 addTestBlock(t, ctx, bs), 224 addTestBlock(t, ctx, bs), 225 addTestBlock(t, ctx, bs), 226 } 227 228 parties := []entities.Party{ 229 addTestParty(t, ctx, ps, blocks[0]), 230 addTestParty(t, ctx, ps, blocks[0]), 231 addTestParty(t, ctx, ps, blocks[0]), 232 addTestParty(t, ctx, ps, blocks[0]), 233 } 234 235 markets := helpers.GenerateMarkets(t, ctx, 10, blocks[0], ms) 236 orderIDs := []string{ 237 "deadbeef01", 238 "deadbeef02", 239 "deadbeef03", 240 "deadbeef04", 241 "deadbeef05", 242 "deadbeef06", 243 "deadbeef07", 244 "deadbeef08", 245 "deadbeef09", 246 "deadbeef10", 247 "deadbeef11", 248 "deadbeef12", 249 } 250 251 stopOrders := generateTestStopOrders(t, []testStopOrderInputs{ 252 { // 0 253 orderID: orderIDs[0], 254 vegaTime: blocks[0].VegaTime, 255 createdAt: blocks[0].VegaTime, 256 triggerPrice: "100", 257 partyID: parties[0].ID, 258 marketID: markets[0].ID, 259 status: entities.StopOrderStatusUnspecified, 260 expiryStrategy: entities.StopOrderExpiryStrategyUnspecified, 261 }, 262 { // 1 263 orderID: orderIDs[1], 264 vegaTime: blocks[0].VegaTime, 265 createdAt: blocks[0].VegaTime, 266 triggerPrice: "100", 267 partyID: parties[1].ID, 268 marketID: markets[1].ID, 269 status: entities.StopOrderStatusUnspecified, 270 expiryStrategy: entities.StopOrderExpiryStrategySubmit, 271 }, 272 { // 2 273 orderID: orderIDs[2], 274 vegaTime: blocks[0].VegaTime, 275 createdAt: blocks[0].VegaTime, 276 triggerPrice: "100", 277 partyID: parties[2].ID, 278 marketID: markets[2].ID, 279 status: entities.StopOrderStatusUnspecified, 280 expiryStrategy: entities.StopOrderExpiryStrategyCancels, 281 }, 282 { // 3 283 orderID: orderIDs[3], 284 vegaTime: blocks[0].VegaTime, 285 createdAt: blocks[0].VegaTime, 286 triggerPrice: "100", 287 partyID: parties[1].ID, 288 marketID: markets[3].ID, 289 status: entities.StopOrderStatusUnspecified, 290 expiryStrategy: entities.StopOrderExpiryStrategySubmit, 291 }, 292 { // 4 293 orderID: orderIDs[4], 294 vegaTime: blocks[0].VegaTime, 295 createdAt: blocks[0].VegaTime, 296 triggerPrice: "100", 297 partyID: parties[2].ID, 298 marketID: markets[4].ID, 299 status: entities.StopOrderStatusUnspecified, 300 expiryStrategy: entities.StopOrderExpiryStrategyCancels, 301 }, 302 { // 5 303 orderID: orderIDs[5], 304 vegaTime: blocks[0].VegaTime, 305 createdAt: blocks[0].VegaTime, 306 triggerPrice: "100", 307 partyID: parties[1].ID, 308 marketID: markets[5].ID, 309 status: entities.StopOrderStatusUnspecified, 310 expiryStrategy: entities.StopOrderExpiryStrategySubmit, 311 }, 312 { // 6 313 orderID: orderIDs[6], 314 vegaTime: blocks[0].VegaTime, 315 createdAt: blocks[0].VegaTime, 316 triggerPrice: "100", 317 partyID: parties[2].ID, 318 marketID: markets[6].ID, 319 status: entities.StopOrderStatusUnspecified, 320 expiryStrategy: entities.StopOrderExpiryStrategyCancels, 321 }, 322 { // 7 323 orderID: orderIDs[7], 324 vegaTime: blocks[0].VegaTime, 325 createdAt: blocks[0].VegaTime, 326 triggerPrice: "100", 327 partyID: parties[1].ID, 328 marketID: markets[7].ID, 329 status: entities.StopOrderStatusUnspecified, 330 expiryStrategy: entities.StopOrderExpiryStrategySubmit, 331 }, 332 { // 8 333 orderID: orderIDs[8], 334 vegaTime: blocks[0].VegaTime, 335 createdAt: blocks[0].VegaTime, 336 triggerPrice: "100", 337 partyID: parties[2].ID, 338 marketID: markets[8].ID, 339 status: entities.StopOrderStatusUnspecified, 340 expiryStrategy: entities.StopOrderExpiryStrategyCancels, 341 }, 342 { // 9 343 orderID: orderIDs[9], 344 vegaTime: blocks[0].VegaTime, 345 createdAt: blocks[0].VegaTime, 346 triggerPrice: "100", 347 partyID: parties[1].ID, 348 marketID: markets[9].ID, 349 status: entities.StopOrderStatusUnspecified, 350 expiryStrategy: entities.StopOrderExpiryStrategySubmit, 351 }, 352 // block 2 353 { // 10 354 orderID: orderIDs[0], 355 vegaTime: blocks[1].VegaTime, 356 createdAt: blocks[0].VegaTime, 357 triggerPrice: "100", 358 partyID: parties[0].ID, 359 marketID: markets[0].ID, 360 status: entities.StopOrderStatusPending, 361 expiryStrategy: entities.StopOrderExpiryStrategyUnspecified, 362 }, 363 { // 11 364 orderID: orderIDs[2], 365 vegaTime: blocks[1].VegaTime, 366 createdAt: blocks[0].VegaTime, 367 triggerPrice: "100", 368 partyID: parties[2].ID, 369 marketID: markets[2].ID, 370 status: entities.StopOrderStatusPending, 371 expiryStrategy: entities.StopOrderExpiryStrategyCancels, 372 }, 373 { // 12 374 orderID: orderIDs[3], 375 vegaTime: blocks[1].VegaTime, 376 createdAt: blocks[0].VegaTime, 377 triggerPrice: "100", 378 partyID: parties[1].ID, 379 marketID: markets[3].ID, 380 status: entities.StopOrderStatusPending, 381 expiryStrategy: entities.StopOrderExpiryStrategySubmit, 382 }, 383 { // 13 384 orderID: orderIDs[4], 385 vegaTime: blocks[1].VegaTime, 386 createdAt: blocks[0].VegaTime, 387 triggerPrice: "100", 388 partyID: parties[2].ID, 389 marketID: markets[4].ID, 390 status: entities.StopOrderStatusPending, 391 expiryStrategy: entities.StopOrderExpiryStrategyCancels, 392 }, 393 { // 14 394 orderID: orderIDs[6], 395 vegaTime: blocks[1].VegaTime, 396 createdAt: blocks[0].VegaTime, 397 triggerPrice: "100", 398 partyID: parties[2].ID, 399 marketID: markets[6].ID, 400 status: entities.StopOrderStatusPending, 401 expiryStrategy: entities.StopOrderExpiryStrategyCancels, 402 }, 403 { // 15 404 orderID: orderIDs[7], 405 vegaTime: blocks[1].VegaTime, 406 createdAt: blocks[0].VegaTime, 407 triggerPrice: "100", 408 partyID: parties[1].ID, 409 marketID: markets[7].ID, 410 status: entities.StopOrderStatusPending, 411 expiryStrategy: entities.StopOrderExpiryStrategySubmit, 412 }, 413 // block 3 414 { // 16 415 orderID: orderIDs[0], 416 vegaTime: blocks[2].VegaTime, 417 createdAt: blocks[0].VegaTime, 418 triggerPrice: "100", 419 partyID: parties[0].ID, 420 marketID: markets[0].ID, 421 status: entities.StopOrderStatusCancelled, 422 expiryStrategy: entities.StopOrderExpiryStrategyUnspecified, 423 }, 424 { // 17 425 orderID: orderIDs[1], 426 vegaTime: blocks[2].VegaTime, 427 createdAt: blocks[0].VegaTime, 428 triggerPrice: "100", 429 partyID: parties[1].ID, 430 marketID: markets[1].ID, 431 status: entities.StopOrderStatusPending, 432 expiryStrategy: entities.StopOrderExpiryStrategySubmit, 433 }, 434 { // 18 435 orderID: orderIDs[5], 436 vegaTime: blocks[2].VegaTime, 437 createdAt: blocks[0].VegaTime, 438 triggerPrice: "100", 439 partyID: parties[1].ID, 440 marketID: markets[5].ID, 441 status: entities.StopOrderStatusPending, 442 expiryStrategy: entities.StopOrderExpiryStrategySubmit, 443 }, 444 { // 19 445 orderID: orderIDs[8], 446 vegaTime: blocks[2].VegaTime, 447 createdAt: blocks[0].VegaTime, 448 triggerPrice: "100", 449 partyID: parties[2].ID, 450 marketID: markets[8].ID, 451 status: entities.StopOrderStatusPending, 452 expiryStrategy: entities.StopOrderExpiryStrategyCancels, 453 }, 454 { // 20 455 orderID: orderIDs[9], 456 vegaTime: blocks[2].VegaTime, 457 createdAt: blocks[0].VegaTime, 458 triggerPrice: "100", 459 partyID: parties[1].ID, 460 marketID: markets[9].ID, 461 status: entities.StopOrderStatusPending, 462 expiryStrategy: entities.StopOrderExpiryStrategySubmit, 463 }, 464 // block 4 465 { // 21 466 orderID: orderIDs[1], 467 vegaTime: blocks[3].VegaTime, 468 createdAt: blocks[0].VegaTime, 469 triggerPrice: "100", 470 partyID: parties[1].ID, 471 marketID: markets[1].ID, 472 status: entities.StopOrderStatusExpired, 473 expiryStrategy: entities.StopOrderExpiryStrategySubmit, 474 }, 475 { // 22 476 orderID: orderIDs[2], 477 vegaTime: blocks[3].VegaTime, 478 createdAt: blocks[0].VegaTime, 479 triggerPrice: "100", 480 partyID: parties[2].ID, 481 marketID: markets[2].ID, 482 status: entities.StopOrderStatusExpired, 483 expiryStrategy: entities.StopOrderExpiryStrategyCancels, 484 }, 485 { // 23 486 orderID: orderIDs[3], 487 vegaTime: blocks[3].VegaTime, 488 createdAt: blocks[0].VegaTime, 489 triggerPrice: "100", 490 partyID: parties[1].ID, 491 marketID: markets[3].ID, 492 status: entities.StopOrderStatusTriggered, 493 expiryStrategy: entities.StopOrderExpiryStrategySubmit, 494 }, 495 { // 24 496 orderID: orderIDs[6], 497 vegaTime: blocks[3].VegaTime, 498 createdAt: blocks[0].VegaTime, 499 triggerPrice: "100", 500 partyID: parties[2].ID, 501 marketID: markets[6].ID, 502 status: entities.StopOrderStatusCancelled, 503 expiryStrategy: entities.StopOrderExpiryStrategyCancels, 504 }, 505 { // 25 506 orderID: orderIDs[7], 507 vegaTime: blocks[3].VegaTime, 508 createdAt: blocks[0].VegaTime, 509 triggerPrice: "100", 510 partyID: parties[1].ID, 511 marketID: markets[7].ID, 512 status: entities.StopOrderStatusTriggered, 513 expiryStrategy: entities.StopOrderExpiryStrategySubmit, 514 }, 515 // block 5 516 { // 26 517 orderID: orderIDs[4], 518 vegaTime: blocks[4].VegaTime, 519 createdAt: blocks[0].VegaTime, 520 triggerPrice: "100", 521 partyID: parties[2].ID, 522 marketID: markets[4].ID, 523 status: entities.StopOrderStatusTriggered, 524 expiryStrategy: entities.StopOrderExpiryStrategyCancels, 525 }, 526 { // 27 527 orderID: orderIDs[5], 528 vegaTime: blocks[4].VegaTime, 529 createdAt: blocks[0].VegaTime, 530 triggerPrice: "100", 531 partyID: parties[1].ID, 532 marketID: markets[5].ID, 533 status: entities.StopOrderStatusCancelled, 534 expiryStrategy: entities.StopOrderExpiryStrategySubmit, 535 }, 536 { // 28 537 orderID: orderIDs[8], 538 vegaTime: blocks[4].VegaTime, 539 createdAt: blocks[0].VegaTime, 540 triggerPrice: "100", 541 partyID: parties[2].ID, 542 marketID: markets[8].ID, 543 status: entities.StopOrderStatusStopped, 544 expiryStrategy: entities.StopOrderExpiryStrategyCancels, 545 }, 546 { // 29 547 orderID: orderIDs[9], 548 vegaTime: blocks[4].VegaTime, 549 createdAt: blocks[0].VegaTime, 550 triggerPrice: "100", 551 partyID: parties[1].ID, 552 marketID: markets[9].ID, 553 status: entities.StopOrderStatusStopped, 554 expiryStrategy: entities.StopOrderExpiryStrategySubmit, 555 }, 556 { // 30 557 orderID: orderIDs[10], 558 vegaTime: blocks[4].VegaTime, 559 createdAt: blocks[0].VegaTime, 560 triggerPrice: "100", 561 partyID: parties[3].ID, 562 marketID: markets[8].ID, 563 status: entities.StopOrderStatusPending, 564 expiryStrategy: entities.StopOrderExpiryStrategyCancels, 565 }, 566 { // 31 567 orderID: orderIDs[11], 568 vegaTime: blocks[5].VegaTime, 569 createdAt: blocks[0].VegaTime, 570 triggerPrice: "100", 571 partyID: parties[3].ID, 572 marketID: markets[9].ID, 573 status: entities.StopOrderStatusPending, 574 expiryStrategy: entities.StopOrderExpiryStrategyCancels, 575 }, 576 }) 577 578 for _, o := range stopOrders { 579 require.NoError(t, so.Add(o)) 580 } 581 582 saved, err := so.Flush(ctx) 583 require.NoError(t, err) 584 require.ElementsMatch(t, stopOrders, saved) 585 586 t.Run("should return an error if oldest first order is requested in pagination", func(t *testing.T) { 587 filter := entities.StopOrderFilter{} 588 p := entities.CursorPagination{} 589 590 _, _, err := so.ListStopOrders(ctx, filter, p) 591 require.Error(t, err) 592 assert.EqualError(t, err, "oldest first order query is not currently supported") 593 }) 594 595 t.Run("should list the latest version of each stop order", func(t *testing.T) { 596 want := []entities.StopOrder{ 597 stopOrders[29], 598 stopOrders[28], 599 stopOrders[25], 600 stopOrders[24], 601 stopOrders[27], 602 stopOrders[26], 603 stopOrders[23], 604 stopOrders[22], 605 stopOrders[21], 606 stopOrders[16], 607 stopOrders[30], 608 stopOrders[31], 609 } 610 611 sort.Slice(want, func(i, j int) bool { 612 return want[i].ID < want[j].ID 613 }) 614 615 filter := entities.StopOrderFilter{} 616 p := entities.CursorPagination{ 617 NewestFirst: true, 618 } 619 620 got, pageInfo, err := so.ListStopOrders(ctx, filter, p) 621 require.NoError(t, err) 622 assert.Equal(t, want, got) 623 assert.Equal(t, entities.PageInfo{ 624 HasNextPage: false, 625 HasPreviousPage: false, 626 StartCursor: want[0].Cursor().Encode(), 627 EndCursor: want[len(want)-1].Cursor().Encode(), 628 }, pageInfo) 629 }) 630 631 t.Run("should paginate the data correctly", func(t *testing.T) { 632 current := []entities.StopOrder{ 633 stopOrders[31], 634 stopOrders[30], 635 stopOrders[29], 636 stopOrders[28], 637 stopOrders[25], 638 stopOrders[24], 639 stopOrders[27], 640 stopOrders[26], 641 stopOrders[23], 642 stopOrders[22], 643 stopOrders[21], 644 stopOrders[16], 645 } 646 647 sort.Slice(current, func(i, j int) bool { 648 return current[i].ID < current[j].ID 649 }) 650 651 filter := entities.StopOrderFilter{} 652 first := int32(3) 653 currentIndex := -1 654 655 for { 656 var ( 657 p entities.CursorPagination 658 err error 659 hasNext, hasPrevious bool 660 want []entities.StopOrder 661 ) 662 663 if currentIndex > -1 { 664 p, err = entities.NewCursorPagination(&first, ptr.From(current[currentIndex].Cursor().Encode()), nil, nil, true) 665 require.NoError(t, err) 666 hasNext = (currentIndex + int(first)) < len(current)-1 667 hasPrevious = true 668 } else { 669 p, err = entities.NewCursorPagination(&first, nil, nil, nil, true) 670 require.NoError(t, err) 671 hasNext = true 672 hasPrevious = false 673 } 674 675 wantStart := currentIndex + 1 676 wantEnd := wantStart + int(first) 677 678 if wantEnd > len(current) { 679 wantEnd = len(current) 680 } 681 682 want = current[wantStart:wantEnd] 683 684 got, pageInfo, err := so.ListStopOrders(ctx, filter, p) 685 require.NoError(t, err) 686 assert.Equal(t, want, got) 687 assert.Equal(t, entities.PageInfo{ 688 HasNextPage: hasNext, 689 HasPreviousPage: hasPrevious, 690 StartCursor: want[0].Cursor().Encode(), 691 EndCursor: want[len(want)-1].Cursor().Encode(), 692 }, pageInfo) 693 694 currentIndex += len(got) 695 if currentIndex >= len(current)-1 { 696 break 697 } 698 } 699 }) 700 701 t.Run("should filter orders", func(tt *testing.T) { 702 tt.Run("by party", func(ts *testing.T) { 703 filter := entities.StopOrderFilter{ 704 PartyIDs: []string{ 705 parties[0].ID.String(), 706 parties[1].ID.String(), 707 }, 708 } 709 710 want := []entities.StopOrder{ 711 stopOrders[29], 712 stopOrders[25], 713 stopOrders[27], 714 stopOrders[23], 715 stopOrders[21], 716 stopOrders[16], 717 } 718 sort.Slice(want, func(i, j int) bool { 719 if want[i].PartyID == want[j].PartyID { 720 return want[i].ID < want[j].ID 721 } 722 return want[i].PartyID < want[j].PartyID 723 }) 724 725 p, err := entities.NewCursorPagination(nil, nil, nil, nil, true) 726 require.NoError(ts, err) 727 728 got, pageInfo, err := so.ListStopOrders(ctx, filter, p) 729 require.NoError(ts, err) 730 assert.Equal(ts, want, got) 731 assert.Equal(ts, entities.PageInfo{ 732 HasNextPage: false, 733 HasPreviousPage: false, 734 StartCursor: want[0].Cursor().Encode(), 735 EndCursor: want[len(want)-1].Cursor().Encode(), 736 }, pageInfo) 737 }) 738 739 tt.Run("by market", func(ts *testing.T) { 740 filter := entities.StopOrderFilter{ 741 MarketIDs: []string{ 742 markets[0].ID.String(), 743 markets[1].ID.String(), 744 markets[3].ID.String(), 745 markets[6].ID.String(), 746 markets[7].ID.String(), 747 markets[8].ID.String(), 748 }, 749 } 750 751 want := []entities.StopOrder{ 752 stopOrders[30], 753 stopOrders[28], 754 stopOrders[25], 755 stopOrders[24], 756 stopOrders[23], 757 stopOrders[21], 758 stopOrders[16], 759 } 760 sort.Slice(want, func(i, j int) bool { 761 if want[i].MarketID == want[j].MarketID { 762 return want[i].ID < want[j].ID 763 } 764 return want[i].MarketID < want[j].MarketID 765 }) 766 767 p, err := entities.NewCursorPagination(nil, nil, nil, nil, true) 768 require.NoError(ts, err) 769 770 got, pageInfo, err := so.ListStopOrders(ctx, filter, p) 771 require.NoError(ts, err) 772 assert.Equal(ts, want, got) 773 assert.Equal(ts, entities.PageInfo{ 774 HasNextPage: false, 775 HasPreviousPage: false, 776 StartCursor: want[0].Cursor().Encode(), 777 EndCursor: want[len(want)-1].Cursor().Encode(), 778 }, pageInfo) 779 }) 780 781 tt.Run("by party and market", func(ts *testing.T) { 782 filter := entities.StopOrderFilter{ 783 PartyIDs: []string{ 784 parties[1].ID.String(), 785 parties[2].ID.String(), 786 }, 787 MarketIDs: []string{ 788 markets[1].ID.String(), 789 markets[3].ID.String(), 790 markets[6].ID.String(), 791 markets[7].ID.String(), 792 markets[8].ID.String(), 793 }, 794 } 795 796 want := []entities.StopOrder{ 797 stopOrders[28], 798 stopOrders[25], 799 stopOrders[24], 800 stopOrders[23], 801 stopOrders[21], 802 } 803 sort.Slice(want, func(i, j int) bool { 804 if want[i].PartyID == want[j].PartyID { 805 return want[i].ID < want[j].ID 806 } 807 return want[i].PartyID < want[j].PartyID 808 }) 809 810 p, err := entities.NewCursorPagination(nil, nil, nil, nil, true) 811 require.NoError(ts, err) 812 813 got, pageInfo, err := so.ListStopOrders(ctx, filter, p) 814 require.NoError(ts, err) 815 assert.Equal(ts, want, got) 816 assert.Equal(ts, entities.PageInfo{ 817 HasNextPage: false, 818 HasPreviousPage: false, 819 StartCursor: want[0].Cursor().Encode(), 820 EndCursor: want[len(want)-1].Cursor().Encode(), 821 }, pageInfo) 822 }) 823 824 tt.Run("by status", func(ts *testing.T) { 825 filter := entities.StopOrderFilter{ 826 Statuses: []entities.StopOrderStatus{ 827 entities.StopOrderStatusCancelled, 828 entities.StopOrderStatusTriggered, 829 entities.StopOrderStatusExpired, 830 }, 831 } 832 833 want := []entities.StopOrder{ 834 stopOrders[27], 835 stopOrders[26], 836 stopOrders[25], 837 stopOrders[24], 838 stopOrders[23], 839 stopOrders[22], 840 stopOrders[21], 841 stopOrders[16], 842 } 843 sort.Slice(want, func(i, j int) bool { 844 return want[i].ID < want[j].ID 845 }) 846 847 p, err := entities.NewCursorPagination(nil, nil, nil, nil, true) 848 require.NoError(ts, err) 849 850 got, pageInfo, err := so.ListStopOrders(ctx, filter, p) 851 require.NoError(ts, err) 852 assert.Equal(ts, want, got) 853 assert.Equal(ts, entities.PageInfo{ 854 HasNextPage: false, 855 HasPreviousPage: false, 856 StartCursor: want[0].Cursor().Encode(), 857 EndCursor: want[len(want)-1].Cursor().Encode(), 858 }, pageInfo) 859 }) 860 861 tt.Run("by trigger strategy", func(ts *testing.T) { 862 filter := entities.StopOrderFilter{ 863 ExpiryStrategy: []entities.StopOrderExpiryStrategy{ 864 entities.StopOrderExpiryStrategyUnspecified, 865 entities.StopOrderExpiryStrategyCancels, 866 }, 867 } 868 869 want := []entities.StopOrder{ 870 stopOrders[31], 871 stopOrders[30], 872 stopOrders[28], 873 stopOrders[26], 874 stopOrders[24], 875 stopOrders[22], 876 stopOrders[16], 877 } 878 879 sort.Slice(want, func(i, j int) bool { 880 return want[i].ID < want[j].ID 881 }) 882 883 p, err := entities.NewCursorPagination(nil, nil, nil, nil, true) 884 require.NoError(ts, err) 885 886 got, pageInfo, err := so.ListStopOrders(ctx, filter, p) 887 require.NoError(ts, err) 888 assert.Equal(ts, want, got) 889 assert.Equal(ts, entities.PageInfo{ 890 HasNextPage: false, 891 HasPreviousPage: false, 892 StartCursor: want[0].Cursor().Encode(), 893 EndCursor: want[len(want)-1].Cursor().Encode(), 894 }, pageInfo) 895 }) 896 897 tt.Run("by party and status", func(ts *testing.T) { 898 filter := entities.StopOrderFilter{ 899 PartyIDs: []string{ 900 parties[1].ID.String(), 901 }, 902 Statuses: []entities.StopOrderStatus{ 903 entities.StopOrderStatusTriggered, 904 entities.StopOrderStatusExpired, 905 }, 906 } 907 908 want := []entities.StopOrder{ 909 stopOrders[25], 910 stopOrders[23], 911 stopOrders[21], 912 } 913 sort.Slice(want, func(i, j int) bool { 914 if want[i].PartyID == want[j].PartyID { 915 return want[i].ID < want[j].ID 916 } 917 return want[i].PartyID < want[j].PartyID 918 }) 919 p, err := entities.NewCursorPagination(nil, nil, nil, nil, true) 920 require.NoError(ts, err) 921 922 got, pageInfo, err := so.ListStopOrders(ctx, filter, p) 923 require.NoError(ts, err) 924 assert.Equal(ts, want, got) 925 assert.Equal(ts, entities.PageInfo{ 926 HasNextPage: false, 927 HasPreviousPage: false, 928 StartCursor: want[0].Cursor().Encode(), 929 EndCursor: want[len(want)-1].Cursor().Encode(), 930 }, pageInfo) 931 }) 932 933 tt.Run("by market and trigger strategy", func(ts *testing.T) { 934 filter := entities.StopOrderFilter{ 935 MarketIDs: []string{ 936 markets[0].ID.String(), 937 markets[1].ID.String(), 938 markets[3].ID.String(), 939 markets[6].ID.String(), 940 markets[7].ID.String(), 941 markets[8].ID.String(), 942 }, 943 ExpiryStrategy: []entities.StopOrderExpiryStrategy{ 944 entities.StopOrderExpiryStrategyCancels, 945 }, 946 } 947 948 want := []entities.StopOrder{ 949 stopOrders[30], 950 stopOrders[28], 951 stopOrders[24], 952 } 953 sort.Slice(want, func(i, j int) bool { 954 if want[i].MarketID == want[j].MarketID { 955 return want[i].ID < want[j].ID 956 } 957 return want[i].MarketID < want[j].MarketID 958 }) 959 960 p, err := entities.NewCursorPagination(nil, nil, nil, nil, true) 961 require.NoError(ts, err) 962 963 got, pageInfo, err := so.ListStopOrders(ctx, filter, p) 964 require.NoError(ts, err) 965 assert.Equal(ts, want, got) 966 assert.Equal(ts, entities.PageInfo{ 967 HasNextPage: false, 968 HasPreviousPage: false, 969 StartCursor: want[0].Cursor().Encode(), 970 EndCursor: want[len(want)-1].Cursor().Encode(), 971 }, pageInfo) 972 }) 973 974 tt.Run("live only", func(ts *testing.T) { 975 filter := entities.StopOrderFilter{ 976 LiveOnly: true, 977 } 978 979 want := []entities.StopOrder{ 980 stopOrders[31], 981 stopOrders[30], 982 } 983 sort.Slice(want, func(i, j int) bool { 984 return want[i].ID < want[j].ID 985 }) 986 p, err := entities.NewCursorPagination(nil, nil, nil, nil, true) 987 require.NoError(ts, err) 988 got, pageInfo, err := so.ListStopOrders(ctx, filter, p) 989 require.NoError(ts, err) 990 991 assert.Equal(ts, want, got) 992 assert.Equal(ts, entities.PageInfo{ 993 HasNextPage: false, 994 HasPreviousPage: false, 995 StartCursor: want[0].Cursor().Encode(), 996 EndCursor: want[1].Cursor().Encode(), 997 }, pageInfo) 998 }) 999 }) 1000 } 1001 1002 func TestStopOrderEnums(t *testing.T) { 1003 t.Run("Should write and retrieve all values of expiry strategy", testStopOrderExpiryStrategyEnum) 1004 t.Run("Should write and retrieve all values of rejection reason", testStopOrderExpiryRejectionReason) 1005 t.Run("Should write and retrieve all values of status", testStopOrderExpiryStatus) 1006 t.Run("Should write and retrieve all values of trigger direction", testStopOrderTriggerDirection) 1007 } 1008 1009 func testStopOrderTriggerDirection(t *testing.T) { 1010 var stopOrderTriggerDirection vegapb.StopOrder_TriggerDirection 1011 directions := getEnums(t, stopOrderTriggerDirection) 1012 assert.Len(t, directions, 3) 1013 for d, direction := range directions { 1014 t.Run(direction, func(t *testing.T) { 1015 ctx := tempTransaction(t) 1016 so := sqlstore.NewStopOrders(connectionSource) 1017 bs := sqlstore.NewBlocks(connectionSource) 1018 ps := sqlstore.NewParties(connectionSource) 1019 ms := sqlstore.NewMarkets(connectionSource) 1020 1021 block := addTestBlock(t, ctx, bs) 1022 party := addTestParty(t, ctx, ps, block) 1023 markets := helpers.GenerateMarkets(t, ctx, 1, block, ms) 1024 1025 o := entities.StopOrder{ 1026 ID: entities.StopOrderID(GenerateID()), 1027 ExpiryStrategy: entities.StopOrderExpiryStrategySubmit, 1028 TriggerDirection: entities.StopOrderTriggerDirection(d), 1029 Status: entities.StopOrderStatusPending, 1030 CreatedAt: block.VegaTime, 1031 UpdatedAt: nil, 1032 OrderID: "", 1033 TriggerPrice: ptr.From("100"), 1034 PartyID: party.ID, 1035 MarketID: markets[0].ID, 1036 VegaTime: block.VegaTime, 1037 SeqNum: 1, 1038 TxHash: generateTxHash(), 1039 Submission: &commandspb.OrderSubmission{ 1040 MarketId: markets[0].ID.String(), 1041 Price: "100", 1042 Size: uint64(100), 1043 Side: entities.SideBuy, 1044 TimeInForce: entities.OrderTimeInForceUnspecified, 1045 ExpiresAt: 0, 1046 Type: entities.OrderTypeMarket, 1047 Reference: GenerateID(), 1048 }, 1049 RejectionReason: entities.StopOrderRejectionReasonUnspecified, 1050 } 1051 1052 require.NoError(t, so.Add(o)) 1053 _, err := so.Flush(ctx) 1054 require.NoError(t, err) 1055 got, err := so.GetStopOrder(ctx, o.ID.String()) 1056 require.NoError(t, err) 1057 assert.Equal(t, o.TriggerDirection, got.TriggerDirection) 1058 }) 1059 } 1060 } 1061 1062 func testStopOrderExpiryStatus(t *testing.T) { 1063 var stopOrderStatus vegapb.StopOrder_Status 1064 states := getEnums(t, stopOrderStatus) 1065 assert.Len(t, states, 7) 1066 for e, state := range states { 1067 t.Run(state, func(t *testing.T) { 1068 ctx := tempTransaction(t) 1069 so := sqlstore.NewStopOrders(connectionSource) 1070 bs := sqlstore.NewBlocks(connectionSource) 1071 ps := sqlstore.NewParties(connectionSource) 1072 ms := sqlstore.NewMarkets(connectionSource) 1073 1074 block := addTestBlock(t, ctx, bs) 1075 party := addTestParty(t, ctx, ps, block) 1076 markets := helpers.GenerateMarkets(t, ctx, 1, block, ms) 1077 1078 o := entities.StopOrder{ 1079 ID: entities.StopOrderID(GenerateID()), 1080 ExpiryStrategy: entities.StopOrderExpiryStrategySubmit, 1081 TriggerDirection: entities.StopOrderTriggerDirectionRisesAbove, 1082 Status: entities.StopOrderStatus(e), 1083 CreatedAt: block.VegaTime, 1084 UpdatedAt: nil, 1085 OrderID: "", 1086 TriggerPrice: ptr.From("100"), 1087 PartyID: party.ID, 1088 MarketID: markets[0].ID, 1089 VegaTime: block.VegaTime, 1090 SeqNum: 1, 1091 TxHash: generateTxHash(), 1092 Submission: &commandspb.OrderSubmission{ 1093 MarketId: markets[0].ID.String(), 1094 Price: "100", 1095 Size: uint64(100), 1096 Side: entities.SideBuy, 1097 TimeInForce: entities.OrderTimeInForceUnspecified, 1098 ExpiresAt: 0, 1099 Type: entities.OrderTypeMarket, 1100 Reference: GenerateID(), 1101 }, 1102 RejectionReason: entities.StopOrderRejectionReasonUnspecified, 1103 } 1104 1105 require.NoError(t, so.Add(o)) 1106 _, err := so.Flush(ctx) 1107 require.NoError(t, err) 1108 got, err := so.GetStopOrder(ctx, o.ID.String()) 1109 require.NoError(t, err) 1110 assert.Equal(t, o.Status, got.Status) 1111 }) 1112 } 1113 } 1114 1115 func testStopOrderExpiryRejectionReason(t *testing.T) { 1116 var stopOrderRejectionReason vegapb.StopOrder_RejectionReason 1117 reasons := getEnums(t, stopOrderRejectionReason) 1118 assert.Len(t, reasons, 12) 1119 for r, reason := range reasons { 1120 t.Run(reason, func(t *testing.T) { 1121 ctx := tempTransaction(t) 1122 so := sqlstore.NewStopOrders(connectionSource) 1123 bs := sqlstore.NewBlocks(connectionSource) 1124 ps := sqlstore.NewParties(connectionSource) 1125 ms := sqlstore.NewMarkets(connectionSource) 1126 1127 block := addTestBlock(t, ctx, bs) 1128 party := addTestParty(t, ctx, ps, block) 1129 markets := helpers.GenerateMarkets(t, ctx, 1, block, ms) 1130 1131 o := entities.StopOrder{ 1132 ID: entities.StopOrderID(GenerateID()), 1133 ExpiryStrategy: entities.StopOrderExpiryStrategySubmit, 1134 TriggerDirection: entities.StopOrderTriggerDirectionRisesAbove, 1135 Status: entities.StopOrderStatusPending, 1136 CreatedAt: block.VegaTime, 1137 UpdatedAt: nil, 1138 OrderID: "", 1139 TriggerPrice: ptr.From("100"), 1140 PartyID: party.ID, 1141 MarketID: markets[0].ID, 1142 VegaTime: block.VegaTime, 1143 SeqNum: 1, 1144 TxHash: generateTxHash(), 1145 Submission: &commandspb.OrderSubmission{ 1146 MarketId: markets[0].ID.String(), 1147 Price: "100", 1148 Size: uint64(100), 1149 Side: entities.SideBuy, 1150 TimeInForce: entities.OrderTimeInForceUnspecified, 1151 ExpiresAt: 0, 1152 Type: entities.OrderTypeMarket, 1153 Reference: GenerateID(), 1154 }, 1155 RejectionReason: entities.StopOrderRejectionReason(r), 1156 } 1157 1158 require.NoError(t, so.Add(o)) 1159 _, err := so.Flush(ctx) 1160 require.NoError(t, err) 1161 got, err := so.GetStopOrder(ctx, o.ID.String()) 1162 require.NoError(t, err) 1163 assert.Equal(t, o.RejectionReason, got.RejectionReason) 1164 }) 1165 } 1166 } 1167 1168 func testStopOrderExpiryStrategyEnum(t *testing.T) { 1169 var stopOrderExpiryStrategy vegapb.StopOrder_ExpiryStrategy 1170 strategies := getEnums(t, stopOrderExpiryStrategy) 1171 assert.Len(t, strategies, 3) 1172 for s, strategy := range strategies { 1173 t.Run(strategy, func(t *testing.T) { 1174 ctx := tempTransaction(t) 1175 so := sqlstore.NewStopOrders(connectionSource) 1176 bs := sqlstore.NewBlocks(connectionSource) 1177 ps := sqlstore.NewParties(connectionSource) 1178 ms := sqlstore.NewMarkets(connectionSource) 1179 1180 block := addTestBlock(t, ctx, bs) 1181 party := addTestParty(t, ctx, ps, block) 1182 markets := helpers.GenerateMarkets(t, ctx, 1, block, ms) 1183 1184 o := entities.StopOrder{ 1185 ID: entities.StopOrderID(GenerateID()), 1186 ExpiryStrategy: entities.StopOrderExpiryStrategy(s), 1187 TriggerDirection: entities.StopOrderTriggerDirectionRisesAbove, 1188 Status: entities.StopOrderStatusPending, 1189 CreatedAt: block.VegaTime, 1190 UpdatedAt: nil, 1191 OrderID: "", 1192 TriggerPrice: ptr.From("100"), 1193 PartyID: party.ID, 1194 MarketID: markets[0].ID, 1195 VegaTime: block.VegaTime, 1196 SeqNum: 1, 1197 TxHash: generateTxHash(), 1198 Submission: &commandspb.OrderSubmission{ 1199 MarketId: markets[0].ID.String(), 1200 Price: "100", 1201 Size: uint64(100), 1202 Side: entities.SideBuy, 1203 TimeInForce: entities.OrderTimeInForceUnspecified, 1204 ExpiresAt: 0, 1205 Type: entities.OrderTypeMarket, 1206 Reference: GenerateID(), 1207 }, 1208 RejectionReason: entities.StopOrderRejectionReasonUnspecified, 1209 } 1210 1211 require.NoError(t, so.Add(o)) 1212 _, err := so.Flush(ctx) 1213 require.NoError(t, err) 1214 got, err := so.GetStopOrder(ctx, o.ID.String()) 1215 require.NoError(t, err) 1216 assert.Equal(t, o.ExpiryStrategy, got.ExpiryStrategy) 1217 }) 1218 } 1219 }