decred.org/dcrdex@v1.0.5/dex/order/order.go (about) 1 // This code is available on the terms of the project LICENSE.md file, 2 // also available online at https://blueoakcouncil.org/license/1.0.0. 3 4 // Package order defines the Order and Match types used throughout the DEX. 5 package order 6 7 import ( 8 "database/sql" 9 "database/sql/driver" 10 "encoding/binary" 11 "encoding/hex" 12 "encoding/json" 13 "fmt" 14 "sync" 15 "time" 16 17 "decred.org/dcrdex/server/account" 18 "github.com/decred/dcrd/crypto/blake256" 19 ) 20 21 // Several types including OrderID and Commitment are defined as a Blake256 22 // hash. Define an internal hash type and hashSize for convenience. 23 const hashSize = blake256.Size // 32 24 type hash = [hashSize]byte 25 26 // OrderIDSize defines the length in bytes of an OrderID. 27 const OrderIDSize = hashSize 28 29 // OrderID is the unique identifier for each order. It is defined as the 30 // Blake256 hash of the serialized order. 31 type OrderID hash 32 33 // IDFromHex decodes an OrderID from a hexadecimal string. 34 func IDFromHex(sid string) (OrderID, error) { 35 if len(sid) > OrderIDSize*2 { 36 return OrderID{}, fmt.Errorf("invalid order ID. too long %d > %d", len(sid), OrderIDSize*2) 37 } 38 oidB, err := hex.DecodeString(sid) 39 if err != nil { 40 return OrderID{}, fmt.Errorf("order ID decode error: %w", err) 41 } 42 var oid OrderID 43 copy(oid[OrderIDSize-len(oidB):], oidB) 44 return oid, nil 45 } 46 47 // IDFromBytes converts a byte slice to an OrderID. 48 func IDFromBytes(b []byte) (OrderID, error) { 49 if len(b) != OrderIDSize { 50 return OrderID{}, fmt.Errorf("invalid order ID. wanted length %d but got %d", OrderIDSize, len(b)) 51 } 52 var oid OrderID 53 copy(oid[:], b) 54 return oid, nil 55 } 56 57 // String returns a hexadecimal representation of the OrderID. String implements 58 // fmt.Stringer. 59 func (oid OrderID) String() string { 60 return hex.EncodeToString(oid[:]) 61 } 62 63 // MarshalJSON satisfies the json.Marshaller interface, and will marshal the 64 // id to a hex string. 65 func (oid OrderID) MarshalJSON() ([]byte, error) { 66 return json.Marshal(oid.String()) 67 } 68 69 // Bytes returns the order ID as a []byte. 70 func (oid OrderID) Bytes() []byte { 71 return oid[:] 72 } 73 74 // Value implements the sql/driver.Valuer interface. 75 func (oid OrderID) Value() (driver.Value, error) { 76 return oid[:], nil // []byte 77 } 78 79 // Scan implements the sql.Scanner interface. 80 func (oid *OrderID) Scan(src any) error { 81 switch src := src.(type) { 82 case []byte: 83 copy(oid[:], src) 84 return nil 85 //case string: 86 // case nil: 87 // *oid = nil 88 // return nil 89 } 90 91 return fmt.Errorf("cannot convert %T to OrderID", src) 92 } 93 94 var zeroOrderID OrderID 95 96 // IsZero returns true if the order ID is zeros. 97 func (oid OrderID) IsZero() bool { 98 return oid == zeroOrderID 99 } 100 101 // OrderType distinguishes the different kinds of orders (e.g. limit, market, 102 // cancel). 103 type OrderType uint8 104 105 // The different OrderType values. 106 const ( 107 UnknownOrderType OrderType = iota 108 LimitOrderType 109 MarketOrderType 110 CancelOrderType 111 ) 112 113 // Value implements the sql/driver.Valuer interface. 114 func (ot OrderType) Value() (driver.Value, error) { 115 return int64(ot), nil 116 } 117 118 // Scan implements the sql.Scanner interface. 119 func (ot *OrderType) Scan(src any) error { 120 // Use sql.(NullInt32).Scan because it uses the unexported 121 // sql.convertAssignRows to coerce compatible types. 122 v := new(sql.NullInt32) 123 if err := v.Scan(src); err != nil { 124 return err 125 } 126 *ot = OrderType(v.Int32) 127 return nil 128 } 129 130 // String returns a string representation of the OrderType. 131 func (ot OrderType) String() string { 132 switch ot { 133 case LimitOrderType: 134 return "limit" 135 case MarketOrderType: 136 return "market" 137 case CancelOrderType: 138 return "cancel" 139 default: 140 return "unknown" 141 } 142 } 143 144 // TimeInForce indicates how limit order execution is to be handled. That is, 145 // when the order is not immediately matched during processing of the order's 146 // epoch, the order may become a standing order or be revoked without a fill. 147 type TimeInForce uint8 148 149 // The TimeInForce is either ImmediateTiF, which prevents the order from 150 // becoming a standing order if there is no match during epoch processing, or 151 // StandingTiF, which allows limit orders to enter the order book if not 152 // immediately matched during epoch processing. 153 const ( 154 ImmediateTiF TimeInForce = iota 155 StandingTiF 156 ) 157 158 // String satisfies the Stringer interface. 159 func (t TimeInForce) String() string { 160 switch t { 161 case ImmediateTiF: 162 return "immediate" 163 case StandingTiF: 164 return "standing" 165 } 166 return fmt.Sprintf("unknown (%d)", t) 167 } 168 169 // Order specifies the methods required for a type to function as a DEX order. 170 // See the concrete implementations of MarketOrder, LimitOrder, and CancelOrder. 171 type Order interface { 172 // Prefix returns the order *Prefix. 173 Prefix() *Prefix 174 175 // Trade returns the order *Trade if a limit or market order, else nil. 176 Trade() *Trade 177 178 // ID computes the Order's ID from its serialization. Serialization is 179 // detailed in the 'Client Order Management' section of the DEX 180 // specification. 181 ID() OrderID 182 183 // UID gives the string representation of the order ID. It is named to 184 // reflect the intent of providing a unique identifier. 185 UID() string 186 187 // User gives the user's account ID. 188 User() account.AccountID 189 190 // Serialize marshals the order. Serialization is detailed in the 'Client 191 // Order Management' section of the DEX specification. 192 Serialize() []byte 193 194 // Type indicates the Order's type (e.g. LimitOrder, MarketOrder, etc.). 195 Type() OrderType 196 197 // Time returns the Order's server time in milliseconds, when it was received by the server. 198 Time() int64 199 200 // SetTime sets the ServerTime field of the prefix. 201 SetTime(time.Time) 202 203 // Base returns the unique integer identifier of the base asset as defined 204 // in the asset package. 205 Base() uint32 206 207 // Quote returns the unique integer identifier of the quote asset as defined 208 // in the asset package. 209 Quote() uint32 210 211 // Commitment returns the order's preimage commitment. 212 Commitment() Commitment 213 } 214 215 // zeroTime is the Unix time for a Time where IsZero() == true. 216 var zeroTime = time.Time{}.UnixMilli() 217 218 // An order's ID is computed as the Blake-256 hash of the serialized order. 219 func calcOrderID(order Order) OrderID { 220 sTime := order.Time() 221 if sTime == zeroTime { 222 panic("Order's ServerTime is unset") 223 } 224 return blake256.Sum256(order.Serialize()) 225 } 226 227 // CoinID should be used to wrap a []byte so that it may be used as a map key. 228 type CoinID []byte 229 230 func (c CoinID) String() string { 231 return hex.EncodeToString(c) 232 } 233 234 // CommitmentSize is the length of the Commitment, a 32-byte Blake-256 hash 235 // according to the DEX specification. 236 const CommitmentSize = hashSize 237 238 // Commitment is the Blake-256 hash of the Preimage. 239 type Commitment hash 240 241 var zeroCommit Commitment 242 243 // IsZero indicates if the Commitment is the zero-value for the type. 244 func (c *Commitment) IsZero() bool { 245 return *c == zeroCommit 246 } 247 248 // Value implements the sql/driver.Valuer interface. The zero-value Commitment 249 // returns nil rather than a byte slice of zeros. 250 func (c Commitment) Value() (driver.Value, error) { 251 if c.IsZero() { 252 return nil, nil // nil => NULL 253 } 254 return c[:], nil // []byte => BYTEA 255 } 256 257 // Scan implements the sql.Scanner interface. NULL table values are scanned as 258 // the zero-value Commitment. 259 func (c *Commitment) Scan(src any) error { 260 switch src := src.(type) { 261 case []byte: 262 copy(c[:], src) 263 return nil 264 case nil: // NULL in the table 265 *c = Commitment{} 266 return nil 267 } 268 269 return fmt.Errorf("cannot convert %T to Commitment", src) 270 } 271 272 // String returns a hexadecimal representation of the Commitment. String 273 // implements fmt.Stringer. 274 func (c Commitment) String() string { 275 return hex.EncodeToString(c[:]) 276 } 277 278 // PreimageSize defines the length of the preimage, which is a 32-byte value 279 // according to the DEX specification. 280 const PreimageSize = 32 281 282 // Preimage represents the 32-byte preimage as a byte slice. 283 type Preimage [PreimageSize]byte 284 285 // Commit computes the preimage commitment as the Blake-256 hash of the 286 // Preimage. 287 func (pi *Preimage) Commit() Commitment { 288 return blake256.Sum256(pi[:]) 289 } 290 291 // Value implements the sql/driver.Valuer interface. 292 func (pi Preimage) Value() (driver.Value, error) { 293 return pi[:], nil // []byte 294 } 295 296 // Scan implements the sql.Scanner interface. 297 func (pi *Preimage) Scan(src any) error { 298 switch src := src.(type) { 299 case []byte: 300 copy(pi[:], src) 301 return nil 302 case nil: // NULL in the table 303 *pi = Preimage{} 304 return nil 305 } 306 307 return fmt.Errorf("cannot convert %T to Preimage", src) 308 } 309 310 // IsZero checks if the Preimage is the zero Preimage. 311 func (pi *Preimage) IsZero() bool { 312 return *pi == Preimage{} 313 } 314 315 // Prefix is the order prefix containing data fields common to all orders. 316 type Prefix struct { 317 AccountID account.AccountID 318 BaseAsset uint32 319 QuoteAsset uint32 320 OrderType OrderType 321 ClientTime time.Time 322 ServerTime time.Time 323 Commit Commitment 324 325 id *OrderID // cache of the order's OrderID 326 } 327 328 // P is an alias for Prefix. Embedding with the alias allows us to define a 329 // method on the interface called Prefix that returns the *Prefix. 330 type P = Prefix 331 332 func (p *Prefix) Prefix() *Prefix { 333 return p 334 } 335 336 // PrefixLen is the length in bytes of the serialized order Prefix. 337 const PrefixLen = account.HashSize + 4 + 4 + 1 + 8 + 8 + CommitmentSize 338 339 // serializeSize returns the length of the serialized order Prefix. 340 func (p *Prefix) serializeSize() int { 341 return PrefixLen 342 } 343 344 // Time returns the order prefix's server time as a UNIX epoch time in 345 // milliseconds. 346 func (p *Prefix) Time() int64 { 347 return p.ServerTime.UnixMilli() 348 } 349 350 // SetTime sets the order prefix's server time. 351 func (p *Prefix) SetTime(t time.Time) { 352 p.ServerTime = t.UTC() 353 // SetTime should only ever be called once in practice, but in case it is 354 // necessary to restamp the ServerTime, clear any computed OrderID. 355 p.id = nil 356 } 357 358 // User gives the user's account ID. 359 func (p *Prefix) User() account.AccountID { 360 return p.AccountID 361 } 362 363 // Serialize marshals the Prefix into a []byte. 364 func (p *Prefix) Serialize() []byte { 365 b := make([]byte, PrefixLen) 366 367 // account ID 368 offset := len(p.AccountID) 369 copy(b[:offset], p.AccountID[:]) 370 371 // base asset 372 binary.BigEndian.PutUint32(b[offset:offset+4], p.BaseAsset) 373 offset += 4 374 375 // quote asset 376 binary.BigEndian.PutUint32(b[offset:offset+4], p.QuoteAsset) 377 offset += 4 378 379 // order type (e.g. market, limit, cancel) 380 b[offset] = uint8(p.OrderType) 381 offset++ 382 383 // client time 384 binary.BigEndian.PutUint64(b[offset:offset+8], uint64(p.ClientTime.UnixMilli())) 385 offset += 8 386 387 // server time 388 binary.BigEndian.PutUint64(b[offset:offset+8], uint64(p.ServerTime.UnixMilli())) 389 offset += 8 390 391 // commitment 392 copy(b[offset:offset+CommitmentSize], p.Commit[:]) 393 394 return b 395 } 396 397 // Base returns the base asset integer ID. 398 func (p *Prefix) Base() uint32 { 399 return p.BaseAsset 400 } 401 402 // Quote returns the quote asset integer ID. 403 func (p *Prefix) Quote() uint32 { 404 return p.QuoteAsset 405 } 406 407 // Type returns the order type. 408 func (p *Prefix) Type() OrderType { 409 return p.OrderType 410 } 411 412 // Commitment returns the order Commitment. 413 func (p *Prefix) Commitment() Commitment { 414 return p.Commit 415 } 416 417 // Trade is information about a trade-type order. Both limit and market orders 418 // are trade-type orders. 419 type Trade struct { 420 Coins []CoinID 421 Sell bool 422 Quantity uint64 423 Address string // TODO: provide later, maybe in the match ack 424 425 // FillAmt is not part of the order's serialization. 426 fillAmtMtx sync.RWMutex 427 FillAmt uint64 // use Filled and AddFill methods for thread-safe access 428 } 429 430 // Copy makes a shallow copy of a Trade. This is useful when attempting to 431 // assign a newly-created trade to an order's field without a linter warning 432 // about copying a mutex (e.g. MarketOrder{T: *aNewTrade.Copy()}). 433 func (t *Trade) Copy() *Trade { 434 return &Trade{ 435 Coins: t.Coins, // shallow 436 Sell: t.Sell, 437 Quantity: t.Quantity, 438 Address: t.Address, 439 FillAmt: t.FillAmt, 440 } 441 } 442 443 // T is an alias for Trade. Embedding with the alias allows us to define a 444 // method on the interface called Trade that returns the *Trade. 445 type T = Trade 446 447 // Trade returns a pointer to the orders embedded Trade. 448 func (t *Trade) Trade() *Trade { 449 return t 450 } 451 452 // Remaining returns the remaining order amount. 453 func (t *Trade) Remaining() uint64 { 454 t.fillAmtMtx.RLock() 455 defer t.fillAmtMtx.RUnlock() 456 return t.Quantity - t.FillAmt 457 } 458 459 // Filled returns the filled amount. 460 func (t *Trade) Filled() uint64 { 461 t.fillAmtMtx.RLock() 462 defer t.fillAmtMtx.RUnlock() 463 return t.FillAmt 464 } 465 466 // AddFill increases the filled amount. 467 func (t *Trade) AddFill(amt uint64) { 468 t.fillAmtMtx.Lock() 469 t.FillAmt += amt 470 t.fillAmtMtx.Unlock() 471 } 472 473 // SetFill sets the filled amount. 474 func (t *Trade) SetFill(amt uint64) { 475 t.fillAmtMtx.Lock() 476 t.FillAmt = amt 477 t.fillAmtMtx.Unlock() 478 } 479 480 // SwapAddress returns the order's payment address. 481 func (t *Trade) SwapAddress() string { 482 return t.Address 483 } 484 485 // FromAccount is the account that the order originates from. Only useful for 486 // account-based assets. Use of this method assumes that account coin has 487 // already been added. 488 func (t *Trade) FromAccount() string { 489 if len(t.Coins) == 0 { 490 return "no coins?" 491 } 492 // The coin ID should be the UTF-8 encoded address string, not the address 493 // byte-array. t.Coins[0] = []byte(addrStr). 494 return string(t.Coins[0]) 495 } 496 497 // ToAccount is the account that the order pays to. Only useful for 498 // account-based assets. 499 func (t *Trade) ToAccount() string { 500 return t.Address 501 } 502 503 // BaseAccount is the account address associated with the base asset for the 504 // order. Only useful for account-based assets. Use of this method assumes that 505 // account coin has already been added (when sell = true). 506 func (t *Trade) BaseAccount() string { 507 if t.Sell { 508 return t.FromAccount() 509 } 510 return t.ToAccount() 511 } 512 513 // QuoteAccount is the account address associated with the quote asset for the 514 // order. Only useful for account-based assets. Use of this method assumes that 515 // account coin has already been added (when sell = false). 516 func (t *Trade) QuoteAccount() string { 517 if t.Sell { 518 return t.ToAccount() 519 } 520 return t.FromAccount() 521 } 522 523 // serializeSize returns the length of the serialized Trade. 524 func (t *Trade) serializeSize() int { 525 // Compute the size of the serialized Coin IDs. 526 var coinSz int 527 for _, coinID := range t.Coins { 528 coinSz += len(coinID) 529 // TODO: ensure all Coin IDs have the same size, indicating the same asset? 530 } 531 // The serialized order includes a byte for coin count, but this is implicit 532 // in coin slice length. 533 return 1 + coinSz + 1 + 8 + len(t.Address) 534 } 535 536 // Serialize marshals the Trade into a []byte. 537 func (t *Trade) Serialize() []byte { 538 b := make([]byte, t.serializeSize()) 539 offset := 0 540 541 // Coin count 542 b[offset] = uint8(len(t.Coins)) 543 offset++ 544 545 // Coins 546 for _, coinID := range t.Coins { 547 coinSz := len(coinID) 548 copy(b[offset:offset+coinSz], coinID) 549 offset += coinSz 550 } 551 552 // order side 553 var side uint8 554 if t.Sell { 555 side = 1 556 } 557 b[offset] = side 558 offset++ 559 560 // order quantity 561 binary.BigEndian.PutUint64(b[offset:offset+8], t.Quantity) 562 offset += 8 563 564 // client address for received funds 565 copy(b[offset:offset+len(t.Address)], []byte(t.Address)) 566 return b 567 } 568 569 // MarketOrder defines a market order in terms of a Prefix and the order 570 // details, including the backing Coins, the order direction/side, order 571 // quantity, and the address where the matched client will send funds. The order 572 // quantity is in atoms of the base asset, and must be an integral multiple of 573 // the asset's lot size, except for Market buy orders when it is in units of the 574 // quote asset and is not bound by integral lot size multiple constraints. 575 type MarketOrder struct { 576 P 577 T 578 } 579 580 // ID computes the order ID. 581 func (o *MarketOrder) ID() OrderID { 582 if o.id != nil { 583 return *o.id 584 } 585 id := calcOrderID(o) 586 o.id = &id 587 return id 588 } 589 590 // UID computes the order ID, returning the string representation. 591 func (o *MarketOrder) UID() string { 592 return o.ID().String() 593 } 594 595 // String is the same as UID. It is defined to satisfy Stringer. 596 func (o *MarketOrder) String() string { 597 return o.UID() 598 } 599 600 // serializeSize returns the length of the serialized MarketOrder. 601 func (o *MarketOrder) serializeSize() int { 602 // The serialized order includes a byte for coin count, but this is implicit 603 // in coin slice length. 604 return o.P.serializeSize() + o.T.serializeSize() 605 } 606 607 // Serialize marshals the LimitOrder into a []byte. 608 func (o *MarketOrder) Serialize() []byte { 609 b := make([]byte, o.serializeSize()) 610 // Prefix and data common with MarketOrder 611 offset := o.P.serializeSize() 612 copy(b[:offset], o.P.Serialize()) 613 tradeLen := o.T.serializeSize() 614 copy(b[offset:offset+tradeLen], o.T.Serialize()) 615 return b 616 } 617 618 // Ensure MarketOrder is an Order. 619 var _ Order = (*MarketOrder)(nil) 620 621 // LimitOrder defines a limit order in terms of a MarketOrder and limit-specific 622 // data including rate (price) and time in force. 623 type LimitOrder struct { 624 P 625 T 626 Rate uint64 // price as atoms of quote asset, applied per 1e8 units of the base asset 627 Force TimeInForce 628 } 629 630 // ID computes the order ID. 631 func (o *LimitOrder) ID() OrderID { 632 if o.id != nil { 633 return *o.id 634 } 635 id := calcOrderID(o) 636 o.id = &id 637 return id 638 } 639 640 // UID computes the order ID, returning the string representation. 641 func (o *LimitOrder) UID() string { 642 return o.ID().String() 643 } 644 645 // String is the same as UID. It is defined to satisfy Stringer. 646 func (o *LimitOrder) String() string { 647 return o.UID() 648 } 649 650 // serializeSize returns the length of the serialized LimitOrder. 651 func (o *LimitOrder) serializeSize() int { 652 return o.P.serializeSize() + o.T.serializeSize() + 8 + 1 653 } 654 655 // Serialize marshals the LimitOrder into a []byte. 656 func (o *LimitOrder) Serialize() []byte { 657 b := make([]byte, o.serializeSize()) 658 // Prefix and data common with MarketOrder 659 offset := o.P.serializeSize() 660 copy(b[:offset], o.P.Serialize()) 661 tradeLen := o.T.serializeSize() 662 copy(b[offset:offset+tradeLen], o.T.Serialize()) 663 offset += tradeLen 664 665 // Price rate in atoms of quote asset 666 binary.BigEndian.PutUint64(b[offset:offset+8], o.Rate) 667 offset += 8 668 669 // Time in force 670 b[offset] = uint8(o.Force) 671 return b 672 } 673 674 // Ensure LimitOrder is an Order. 675 var _ Order = (*LimitOrder)(nil) 676 677 // Price returns the limit order's price rate. 678 func (o *LimitOrder) Price() uint64 { 679 return o.Rate 680 } 681 682 // CancelOrder defines a cancel order in terms of an order Prefix and the ID of 683 // the order to be canceled. 684 type CancelOrder struct { 685 P 686 TargetOrderID OrderID 687 } 688 689 // ID computes the order ID. 690 func (o *CancelOrder) ID() OrderID { 691 if o.id != nil { 692 return *o.id 693 } 694 id := calcOrderID(o) 695 o.id = &id 696 return id 697 } 698 699 // UID computes the order ID, returning the string representation. 700 func (o *CancelOrder) UID() string { 701 return o.ID().String() 702 } 703 704 // Trade returns a pointer to the orders embedded Trade. 705 func (o *CancelOrder) Trade() *Trade { 706 return nil 707 } 708 709 // String is the same as UID. It is defined to satisfy Stringer. 710 func (o *CancelOrder) String() string { 711 return o.UID() 712 } 713 714 // serializeSize returns the length of the serialized CancelOrder. 715 func (o *CancelOrder) serializeSize() int { 716 return o.P.serializeSize() + OrderIDSize 717 } 718 719 // Serialize marshals the CancelOrder into a []byte. 720 func (o *CancelOrder) Serialize() []byte { 721 return append(o.P.Serialize(), o.TargetOrderID[:]...) 722 } 723 724 // Ensure CancelOrder is an Order. 725 var _ Order = (*CancelOrder)(nil) 726 727 // ValidateOrder ensures that the order with the given status for the specified 728 // market is sensible. The ServerTime may not be set yet, so the OrderID cannot 729 // be computed. 730 func ValidateOrder(ord Order, status OrderStatus, lotSize uint64) error { 731 if ord.Base() == ord.Quote() { 732 return fmt.Errorf("same asset specified for base and quote") 733 } 734 735 // Each order type has different rules about status and lot size. 736 switch ot := ord.(type) { 737 case *MarketOrder: 738 // Market orders OK statuses: epoch and executed (NOT booked or 739 // canceled). 740 switch status { 741 case OrderStatusEpoch, OrderStatusExecuted, OrderStatusRevoked: 742 default: 743 return fmt.Errorf("invalid market order status %d -> %s", status, status) 744 } 745 746 if ot.OrderType != MarketOrderType { 747 return fmt.Errorf("market order has wrong order type %d -> %s", ot.OrderType, ot.OrderType) 748 } 749 750 // Market sell orders must respect lot size. Market buy orders must be 751 // of an amount sufficiently buffered beyond the minimum standing sell 752 // order's lot cost, but that is enforced by the order router. 753 if ot.Sell && (ot.Quantity%lotSize != 0 || ot.Remaining()%lotSize != 0) { 754 return fmt.Errorf("market sell order fails lot size requirement %d %% %d = %d", ot.Quantity, lotSize, ot.Quantity%lotSize) 755 } 756 757 case *CancelOrder: 758 // Cancel order OK statuses: epoch, executed (NOT booked or canceled), 759 // and revoked. Revoked status indicates the cancel order is 760 // server-generated and corresponds to a revoked trade order. 761 switch status { 762 case OrderStatusEpoch, OrderStatusExecuted, OrderStatusRevoked: // orderStatusFailed if we decide to export that 763 default: 764 return fmt.Errorf("invalid cancel order status %d -> %s", status, status) 765 } 766 767 if ot.OrderType != CancelOrderType { 768 return fmt.Errorf("cancel order has wrong order type %d -> %s", ot.OrderType, ot.OrderType) 769 } 770 771 case *LimitOrder: 772 // Limit order OK statuses: epoch, booked, executed, and canceled (same 773 // as market plus booked). 774 switch status { 775 case OrderStatusEpoch, OrderStatusExecuted, OrderStatusRevoked: 776 case OrderStatusBooked, OrderStatusCanceled: 777 // Immediate time in force limit orders may not be canceled, and may 778 // not be in the order book. 779 if ot.Force == ImmediateTiF { 780 return fmt.Errorf("invalid immediate limit order status %d -> %s", status, status) 781 } 782 default: 783 return fmt.Errorf("invalid limit order status %d -> %s", status, status) 784 } 785 786 if ot.OrderType != LimitOrderType { 787 return fmt.Errorf("limit order has wrong order type %d -> %s", ot.OrderType, ot.OrderType) 788 } 789 790 // All limit orders must respect lot size. 791 if ot.Quantity%lotSize != 0 || ot.Remaining()%lotSize != 0 { 792 return fmt.Errorf("limit order fails lot size requirement %d %% %d = %d", ot.Quantity, lotSize, ot.Quantity%lotSize) 793 } 794 default: 795 // cannot validate an unknown order type 796 return fmt.Errorf("unknown order type") 797 } 798 799 return nil 800 } 801 802 // ExtractAddress extracts the address from the order. If the order is a cancel 803 // order, an empty string is returned. 804 func ExtractAddress(ord Order) string { 805 trade := ord.Trade() 806 if trade == nil { 807 return "" 808 } 809 return trade.Address 810 }