337 lines
8.0 KiB
Go
337 lines
8.0 KiB
Go
// Copyright 2020 dfuse Platform Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package serum
|
|
|
|
import (
|
|
"strings"
|
|
|
|
bin "github.com/gagliardetto/binary"
|
|
"github.com/gagliardetto/solana-go"
|
|
)
|
|
|
|
type RequestQueue struct {
|
|
SerumPadding [5]byte `json:"-"`
|
|
|
|
AccountFlags AccountFlag
|
|
Head bin.Uint64 `bin:"sliceoffsetof=Requests,80"`
|
|
|
|
Count bin.Uint64 `bin:"sizeof=Requests"`
|
|
NextSeqNum bin.Uint64
|
|
Requests []*Request
|
|
|
|
EndPadding [7]byte `json:"-"`
|
|
}
|
|
|
|
func (r *RequestQueue) Decode(data []byte) error {
|
|
decoder := bin.NewBinDecoder(data)
|
|
return decoder.Decode(&r)
|
|
}
|
|
|
|
var _ bin.EncoderDecoder = &RequestQueue{}
|
|
|
|
func (q *RequestQueue) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) {
|
|
if err = decoder.SkipBytes(5); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = decoder.Decode(&q.AccountFlags); err != nil {
|
|
return err
|
|
}
|
|
if err = decoder.Decode(&q.Head); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = decoder.Decode(&q.Count); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = decoder.Decode(&q.NextSeqNum); err != nil {
|
|
return err
|
|
}
|
|
|
|
ringbufStartByte := decoder.Position()
|
|
ringbufByteSize := uint(decoder.Remaining() - 7)
|
|
ringbugLength := ringbufByteSize / EVENT_BYTE_SIZE
|
|
|
|
q.Requests = make([]*Request, q.Count)
|
|
|
|
for i := uint(0); i < uint(q.Count); i++ {
|
|
itemIndex := ((uint(q.Head) + i) % ringbugLength)
|
|
offset := ringbufStartByte + (itemIndex * EVENT_BYTE_SIZE)
|
|
if err = decoder.SetPosition(offset); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = decoder.Decode(&q.Requests[i]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TODO: fill up later
|
|
func (q *RequestQueue) MarshalWithEncoder(encoder *bin.Encoder) error {
|
|
return nil
|
|
}
|
|
|
|
type RequestFlag uint8
|
|
|
|
const (
|
|
RequestFlagNewOrder = RequestFlag(1 << iota)
|
|
RequestFlagCancelOrder
|
|
RequestFlagBid
|
|
RequestFlagPostOnly
|
|
RequestFlagImmediateOrCancel
|
|
RequestFlagDecrementTakeOnSelfTrade
|
|
)
|
|
|
|
func (f RequestFlag) String() string {
|
|
var flags []string
|
|
|
|
if f.IsNewOrder() {
|
|
flags = append(flags, "NEW_ORDER")
|
|
}
|
|
if f.IsCancelOrder() {
|
|
flags = append(flags, "CANCEL_ORDER")
|
|
}
|
|
if f.IsBid() {
|
|
flags = append(flags, "BID")
|
|
} else {
|
|
flags = append(flags, "ASK")
|
|
}
|
|
if f.IsPostOnly() {
|
|
flags = append(flags, "POST_ONLY")
|
|
}
|
|
if f.IsImmediateOrCancel() {
|
|
flags = append(flags, "IMMEDIATE_OR_CANCEL")
|
|
}
|
|
if f.IsDecrementTakeOnSelfTrade() {
|
|
flags = append(flags, "DECR_TAKE_ON_SELF")
|
|
}
|
|
return strings.Join(flags, " | ")
|
|
}
|
|
|
|
func (r RequestFlag) IsNewOrder() bool {
|
|
return Has(uint8(r), uint8(RequestFlagNewOrder))
|
|
}
|
|
|
|
func (r RequestFlag) IsCancelOrder() bool {
|
|
return Has(uint8(r), uint8(RequestFlagCancelOrder))
|
|
}
|
|
|
|
func (r RequestFlag) IsBid() bool {
|
|
return Has(uint8(r), uint8(RequestFlagBid))
|
|
}
|
|
|
|
func (r RequestFlag) IsPostOnly() bool {
|
|
return Has(uint8(r), uint8(RequestFlagPostOnly))
|
|
}
|
|
|
|
func (r RequestFlag) IsImmediateOrCancel() bool {
|
|
return Has(uint8(r), uint8(RequestFlagImmediateOrCancel))
|
|
}
|
|
|
|
func (r RequestFlag) IsDecrementTakeOnSelfTrade() bool {
|
|
return Has(uint8(r), uint8(RequestFlagDecrementTakeOnSelfTrade))
|
|
}
|
|
|
|
// Size 80 byte
|
|
type Request struct {
|
|
RequestFlags RequestFlag
|
|
OwnerSlot uint8
|
|
FeeTier uint8
|
|
SelfTradeBehavior uint8
|
|
Padding [4]byte `json:"-"`
|
|
MaxCoinQtyOrCancelId bin.Uint64 //the max amount you wish to buy or sell
|
|
NativePCQtyLocked bin.Uint64
|
|
OrderID bin.Uint128
|
|
OpenOrders [4]bin.Uint64 // this is the openOrder address
|
|
ClientOrderID bin.Uint64
|
|
}
|
|
|
|
func (r *Request) Equal(other *Request) bool {
|
|
//return (r.OrderID.Hi == other.OrderID.Hi && r.OrderID.Lo == other.OrderID.Lo) &&
|
|
// (r.MaxCoinQtyOrCancelId == other.MaxCoinQtyOrCancelId) &&
|
|
// (r.NativePCQtyLocked == other.NativePCQtyLocked)
|
|
return (r.OrderID.Hi == other.OrderID.Hi && r.OrderID.Lo == other.OrderID.Lo) &&
|
|
(r.MaxCoinQtyOrCancelId == other.MaxCoinQtyOrCancelId) &&
|
|
(r.NativePCQtyLocked == other.NativePCQtyLocked)
|
|
}
|
|
|
|
// -> 262144 + 12 bytes
|
|
// -> 12 bytes of serum padding -> 262144
|
|
// -> 262144 = HEADER + RING-BUFFER
|
|
// -> HEADER = 32 bytes
|
|
// -> RING BUFF = (262144 - 32) 262112
|
|
// -> ring buf (262144 - 32) -> 262112 -> max number of event
|
|
type EventQueue struct {
|
|
SerumPadding [5]byte `json:"-"`
|
|
|
|
AccountFlags AccountFlag
|
|
Head bin.Uint64 `bin:"sliceoffsetof=Events,88"`
|
|
Count bin.Uint64 `bin:"sizeof=Events"`
|
|
SeqNum bin.Uint64
|
|
Events []*Event
|
|
|
|
EndPadding [7]byte `json:"-"`
|
|
}
|
|
|
|
var _ bin.EncoderDecoder = &EventQueue{}
|
|
|
|
func (q *EventQueue) Decode(data []byte) error {
|
|
decoder := bin.NewBinDecoder(data)
|
|
return decoder.Decode(&q)
|
|
}
|
|
|
|
func (q *EventQueue) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) {
|
|
if err = decoder.SkipBytes(5); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = decoder.Decode(&q.AccountFlags); err != nil {
|
|
return err
|
|
}
|
|
if err = decoder.Decode(&q.Head); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = decoder.Decode(&q.Count); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = decoder.Decode(&q.SeqNum); err != nil {
|
|
return err
|
|
}
|
|
|
|
ringbufStartByte := decoder.Position()
|
|
ringbufByteSize := uint(decoder.Remaining() - 7)
|
|
ringbugLength := ringbufByteSize / EVENT_BYTE_SIZE
|
|
|
|
q.Events = make([]*Event, q.Count)
|
|
|
|
for i := uint(0); i < uint(q.Count); i++ {
|
|
itemIndex := ((uint(q.Head) + i) % ringbugLength)
|
|
offset := ringbufStartByte + (itemIndex * EVENT_BYTE_SIZE)
|
|
if err = decoder.SetPosition(offset); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = decoder.Decode(&q.Events[i]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TODO: fill up later
|
|
func (q *EventQueue) MarshalWithEncoder(encoder *bin.Encoder) error {
|
|
return nil
|
|
}
|
|
|
|
type EventFlag uint8
|
|
|
|
const (
|
|
EventFlagFill = 0x1
|
|
EventFlagOut = 0x2
|
|
EventFlagBid = 0x4
|
|
EventFlagMaker = 0x8
|
|
|
|
// Added in DEX v3
|
|
|
|
EventFlagReleaseFunds = 0x10
|
|
)
|
|
|
|
func (e EventFlag) IsFill() bool {
|
|
return Has(uint8(e), uint8(EventFlagFill))
|
|
}
|
|
|
|
func (e EventFlag) IsOut() bool {
|
|
return Has(uint8(e), uint8(EventFlagOut))
|
|
}
|
|
|
|
func (e EventFlag) IsBid() bool {
|
|
return Has(uint8(e), uint8(EventFlagBid))
|
|
}
|
|
|
|
func (e EventFlag) IsMaker() bool {
|
|
return Has(uint8(e), uint8(EventFlagMaker))
|
|
}
|
|
|
|
func (e EventFlag) IsReleaseFunds() bool {
|
|
return Has(uint8(e), uint8(EventFlagReleaseFunds))
|
|
}
|
|
|
|
func (e EventFlag) String() string {
|
|
var flags []string
|
|
if e.IsFill() {
|
|
flags = append(flags, "FILL")
|
|
}
|
|
if e.IsOut() {
|
|
flags = append(flags, "OUT")
|
|
}
|
|
if e.IsBid() {
|
|
flags = append(flags, "BID")
|
|
}
|
|
if e.IsMaker() {
|
|
flags = append(flags, "MAKER")
|
|
}
|
|
if e.IsReleaseFunds() {
|
|
flags = append(flags, "RELEASE_FUNDS")
|
|
}
|
|
|
|
return strings.Join(flags, " | ")
|
|
}
|
|
|
|
const EVENT_BYTE_SIZE = uint(88)
|
|
|
|
type Event struct {
|
|
Flag EventFlag
|
|
OwnerSlot uint8
|
|
FeeTier uint8
|
|
Padding [5]uint8
|
|
NativeQtyReleased uint64 // the amount you should release (free to settle)
|
|
NativeQtyPaid uint64 // The amount out of your account
|
|
NativeFeeOrRebate uint64 // maker etc...
|
|
OrderID OrderID
|
|
Owner solana.PublicKey // OpenOrder Account address NOT trader
|
|
ClientOrderID uint64
|
|
}
|
|
|
|
/* Fill Event*/
|
|
// BID: Buying Coin paying in PC
|
|
// NativeQtyPaid will deplete PC
|
|
// NativeQtyReleased: (native_qty_received) amount of Coin you received
|
|
// will incremet natice_coin_total, native_coin_free
|
|
|
|
// ASK: Buying PC paying in COIN
|
|
func (e *Event) Equal(other *Event) bool {
|
|
return e.OrderID.Hi == other.OrderID.Hi && e.OrderID.Lo == other.OrderID.Lo
|
|
}
|
|
|
|
func (e *Event) Side() Side {
|
|
if Has(uint8(e.Flag), uint8(EventFlagBid)) {
|
|
return SideBid
|
|
}
|
|
return SideAsk
|
|
}
|
|
|
|
func (e *Event) Filled() bool {
|
|
return Has(uint8(e.Flag), uint8(EventFlagFill))
|
|
}
|
|
|
|
func Has(b, flag uint8) bool { return b&flag == flag }
|