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 }