code.vegaprotocol.io/vega@v0.79.0/commands/order_submission.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
    17  
    18  import (
    19  	"errors"
    20  	"math"
    21  	"math/big"
    22  
    23  	types "code.vegaprotocol.io/vega/protos/vega"
    24  	commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1"
    25  )
    26  
    27  func CheckOrderSubmission(cmd *commandspb.OrderSubmission) error {
    28  	return checkOrderSubmission(cmd).ErrorOrNil()
    29  }
    30  
    31  func checkOrderSubmission(cmd *commandspb.OrderSubmission) Errors {
    32  	errs := NewErrors()
    33  
    34  	if cmd == nil {
    35  		return errs.FinalAddForProperty("order_submission", ErrIsRequired)
    36  	}
    37  
    38  	if len(cmd.Reference) > ReferenceMaxLen {
    39  		errs.AddForProperty("order_submission.reference", ErrReferenceTooLong)
    40  	}
    41  
    42  	if len(cmd.MarketId) == 0 {
    43  		errs.AddForProperty("order_submission.market_id", ErrIsRequired)
    44  	} else if !IsVegaID(cmd.MarketId) {
    45  		errs.AddForProperty("order_submission.market_id", ErrShouldBeAValidVegaID)
    46  	}
    47  
    48  	if cmd.Side == types.Side_SIDE_UNSPECIFIED {
    49  		errs.AddForProperty("order_submission.side", ErrIsRequired)
    50  	}
    51  	if _, ok := types.Side_name[int32(cmd.Side)]; !ok {
    52  		errs.AddForProperty("order_submission.side", ErrIsNotValid)
    53  	}
    54  
    55  	if cmd.Type == types.Order_TYPE_UNSPECIFIED {
    56  		errs.AddForProperty("order_submission.type", ErrIsRequired)
    57  	}
    58  	if _, ok := types.Order_Type_name[int32(cmd.Type)]; !ok {
    59  		errs.AddForProperty("order_submission.type", ErrIsNotValid)
    60  	}
    61  	if cmd.Type == types.Order_TYPE_NETWORK {
    62  		errs.AddForProperty("order_submission.type", ErrIsUnauthorised)
    63  	}
    64  
    65  	if cmd.TimeInForce == types.Order_TIME_IN_FORCE_UNSPECIFIED {
    66  		errs.AddForProperty("order_submission.time_in_force", ErrIsRequired)
    67  	}
    68  	if _, ok := types.Order_TimeInForce_name[int32(cmd.TimeInForce)]; !ok {
    69  		errs.AddForProperty("order_submission.time_in_force", ErrIsNotValid)
    70  	}
    71  
    72  	if cmd.Size <= 0 {
    73  		errs.AddForProperty("order_submission.size", ErrMustBePositive)
    74  	}
    75  
    76  	// just make sure its not some silly big number because we do sometimes cast to int64s
    77  	if cmd.Size > math.MaxInt64/2 {
    78  		errs.AddForProperty("order_submission.size", ErrSizeIsTooLarge)
    79  	}
    80  
    81  	if cmd.TimeInForce == types.Order_TIME_IN_FORCE_GTT {
    82  		if cmd.ExpiresAt <= 0 {
    83  			errs.AddForProperty("order_submission.expires_at", ErrMustBePositive)
    84  		}
    85  	} else if cmd.ExpiresAt != 0 {
    86  		errs.AddForProperty("order_submission.expires_at",
    87  			errors.New("is only available when the time in force is of type GTT"),
    88  		)
    89  	}
    90  
    91  	if cmd.PostOnly && cmd.ReduceOnly {
    92  		errs.AddForProperty("order_submission.post_only",
    93  			errors.New("cannot be true at the same time as order_submission.reduce_only"))
    94  	} else {
    95  		if cmd.PostOnly {
    96  			if cmd.Type != types.Order_TYPE_LIMIT {
    97  				errs.AddForProperty("order_submission.post_only",
    98  					errors.New("only valid for limit orders"))
    99  			}
   100  			if cmd.TimeInForce == types.Order_TIME_IN_FORCE_FOK ||
   101  				cmd.TimeInForce == types.Order_TIME_IN_FORCE_IOC {
   102  				errs.AddForProperty("order_submission.post_only",
   103  					errors.New("only valid for persistent orders"))
   104  			}
   105  		}
   106  
   107  		if cmd.ReduceOnly {
   108  			if cmd.TimeInForce != types.Order_TIME_IN_FORCE_FOK &&
   109  				cmd.TimeInForce != types.Order_TIME_IN_FORCE_IOC {
   110  				errs.AddForProperty("order_submission.reduce_only",
   111  					errors.New("only valid for non-persistent orders"))
   112  			}
   113  			if cmd.PeggedOrder != nil {
   114  				errs.AddForProperty("order_submission.reduce_only",
   115  					errors.New("cannot be pegged"))
   116  			}
   117  		}
   118  	}
   119  
   120  	// iceberg checks
   121  	if cmd.IcebergOpts != nil {
   122  		iceberg := cmd.IcebergOpts
   123  		if iceberg.PeakSize < iceberg.MinimumVisibleSize {
   124  			errs.AddForProperty("order_submission.iceberg_opts.peak_size", errors.New("must be >= order_submission.iceberg_opts.minimum_visible_size"))
   125  		}
   126  
   127  		if iceberg.MinimumVisibleSize <= 0 {
   128  			errs.AddForProperty("order_submission.iceberg_opts.minimum_visible_size", ErrMustBePositive)
   129  		}
   130  
   131  		if iceberg.PeakSize > cmd.Size {
   132  			errs.AddForProperty("order_submission.iceberg_opts.peak_size", errors.New("must be <= order_submission.size"))
   133  		}
   134  
   135  		if cmd.Type != types.Order_TYPE_LIMIT {
   136  			errs.AddForProperty("order_submission.type", errors.New("iceberg order must be of type LIMIT"))
   137  		}
   138  
   139  		if cmd.TimeInForce == types.Order_TIME_IN_FORCE_FOK ||
   140  			cmd.TimeInForce == types.Order_TIME_IN_FORCE_IOC {
   141  			errs.AddForProperty("order_submission.time_in_force", errors.New("iceberg order must be a persistent order"))
   142  		}
   143  
   144  		if cmd.ReduceOnly {
   145  			errs.AddForProperty("order_submission.reduce_only", errors.New("iceberg order must not be reduce-only"))
   146  		}
   147  	}
   148  
   149  	if cmd.PeggedOrder != nil {
   150  		if cmd.PeggedOrder.Reference == types.PeggedReference_PEGGED_REFERENCE_UNSPECIFIED {
   151  			errs.AddForProperty("order_submission.pegged_order.reference", ErrIsRequired)
   152  		}
   153  		if _, ok := types.PeggedReference_name[int32(cmd.PeggedOrder.Reference)]; !ok {
   154  			errs.AddForProperty("order_submission.pegged_order.reference", ErrIsNotValid)
   155  		}
   156  
   157  		if cmd.Type != types.Order_TYPE_LIMIT {
   158  			errs.AddForProperty("order_submission.type",
   159  				errors.New("is expected to be an order of type LIMIT when the order is pegged"),
   160  			)
   161  		}
   162  
   163  		if cmd.TimeInForce != types.Order_TIME_IN_FORCE_GTT &&
   164  			cmd.TimeInForce != types.Order_TIME_IN_FORCE_GTC &&
   165  			cmd.TimeInForce != types.Order_TIME_IN_FORCE_GFN {
   166  			errs.AddForProperty("order_submission.time_in_force",
   167  				errors.New("is expected to have a time in force of type GTT, GTC or GFN when the order is pegged"),
   168  			)
   169  		}
   170  
   171  		if cmd.Side == types.Side_SIDE_BUY {
   172  			switch cmd.PeggedOrder.Reference {
   173  			case types.PeggedReference_PEGGED_REFERENCE_BEST_ASK:
   174  				errs.AddForProperty("order_submission.pegged_order.reference",
   175  					errors.New("cannot have a reference of type BEST_ASK when on BUY side"),
   176  				)
   177  			case types.PeggedReference_PEGGED_REFERENCE_BEST_BID:
   178  				if offset, ok := big.NewInt(0).SetString(cmd.PeggedOrder.Offset, 10); !ok {
   179  					errs.AddForProperty(
   180  						"order_submission.pegged_order.offset",
   181  						ErrNotAValidInteger,
   182  					)
   183  				} else if offset.Cmp(big.NewInt(0)) == -1 {
   184  					errs.AddForProperty("order_submission.pegged_order.offset", ErrMustBePositiveOrZero)
   185  				}
   186  			case types.PeggedReference_PEGGED_REFERENCE_MID:
   187  				if offset, ok := big.NewInt(0).SetString(cmd.PeggedOrder.Offset, 10); !ok {
   188  					errs.AddForProperty(
   189  						"order_submission.pegged_order.offset",
   190  						ErrNotAValidInteger,
   191  					)
   192  				} else if offset.Cmp(big.NewInt(0)) == -1 || offset.Cmp(big.NewInt(0)) == 0 {
   193  					errs.AddForProperty("order_submission.pegged_order.offset", ErrMustBePositive)
   194  				}
   195  			}
   196  			return errs
   197  		}
   198  
   199  		switch cmd.PeggedOrder.Reference {
   200  		case types.PeggedReference_PEGGED_REFERENCE_BEST_BID:
   201  			errs.AddForProperty("order_submission.pegged_order.reference",
   202  				errors.New("cannot have a reference of type BEST_BID when on SELL side"),
   203  			)
   204  		case types.PeggedReference_PEGGED_REFERENCE_BEST_ASK:
   205  			if offset, ok := big.NewInt(0).SetString(cmd.PeggedOrder.Offset, 10); !ok {
   206  				errs.AddForProperty(
   207  					"order_submission.pegged_order.offset",
   208  					ErrNotAValidInteger,
   209  				)
   210  			} else if offset.Cmp(big.NewInt(0)) == -1 {
   211  				errs.AddForProperty("order_submission.pegged_order.offset", ErrMustBePositiveOrZero)
   212  			}
   213  		case types.PeggedReference_PEGGED_REFERENCE_MID:
   214  			if offset, ok := big.NewInt(0).SetString(cmd.PeggedOrder.Offset, 10); !ok {
   215  				errs.AddForProperty(
   216  					"order_submission.pegged_order.offset",
   217  					ErrNotAValidInteger,
   218  				)
   219  			} else if offset.Cmp(big.NewInt(0)) == -1 || offset.Cmp(big.NewInt(0)) == 0 {
   220  				errs.AddForProperty("order_submission.pegged_order.offset", ErrMustBePositive)
   221  			}
   222  		}
   223  
   224  		return errs
   225  	}
   226  
   227  	switch cmd.Type {
   228  	case types.Order_TYPE_MARKET:
   229  		if len(cmd.Price) > 0 {
   230  			errs.AddForProperty("order_submission.price",
   231  				errors.New("is unavailable when the order is of type MARKET"),
   232  			)
   233  		}
   234  		if cmd.TimeInForce != types.Order_TIME_IN_FORCE_FOK &&
   235  			cmd.TimeInForce != types.Order_TIME_IN_FORCE_IOC {
   236  			errs.AddForProperty("order_submission.time_in_force",
   237  				errors.New("is expected to be of type FOK or IOC when order is of type MARKET"),
   238  			)
   239  		}
   240  	case types.Order_TYPE_LIMIT:
   241  		if len(cmd.Price) <= 0 {
   242  			errs.AddForProperty("order_submission.price",
   243  				errors.New("is required when the order is of type LIMIT"),
   244  			)
   245  		} else {
   246  			if price, ok := big.NewInt(0).SetString(cmd.Price, 10); !ok {
   247  				errs.AddForProperty("order_submission.price", ErrNotAValidInteger)
   248  			} else if price.Cmp(big.NewInt(0)) != 1 {
   249  				errs.AddForProperty("order_submission.price",
   250  					errors.New("must be positive when the order is of type LIMIT"),
   251  				)
   252  			}
   253  		}
   254  	}
   255  
   256  	return errs
   257  }