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  }