2020-11-09 10:09:50 -08:00
|
|
|
// 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.
|
2020-11-05 14:54:08 -08:00
|
|
|
package serum
|
|
|
|
|
2020-11-06 08:47:26 -08:00
|
|
|
import (
|
2020-11-17 05:24:59 -08:00
|
|
|
"encoding/base64"
|
2020-11-23 08:12:02 -08:00
|
|
|
"encoding/binary"
|
2020-11-06 08:47:26 -08:00
|
|
|
"fmt"
|
2020-11-09 07:02:28 -08:00
|
|
|
"math/big"
|
|
|
|
|
2020-11-12 14:57:43 -08:00
|
|
|
bin "github.com/dfuse-io/binary"
|
2020-11-06 08:47:26 -08:00
|
|
|
"github.com/dfuse-io/solana-go"
|
2020-11-17 04:14:55 -08:00
|
|
|
"go.uber.org/zap"
|
2020-11-06 08:47:26 -08:00
|
|
|
)
|
2020-11-05 14:54:08 -08:00
|
|
|
|
|
|
|
type MarketV2 struct {
|
2020-11-13 06:52:02 -08:00
|
|
|
SerumPadding [5]byte `json:"-"`
|
|
|
|
AccountFlags bin.Uint64
|
|
|
|
OwnAddress solana.PublicKey
|
|
|
|
VaultSignerNonce bin.Uint64
|
|
|
|
BaseMint solana.PublicKey
|
|
|
|
QuoteMint solana.PublicKey
|
|
|
|
BaseVault solana.PublicKey
|
|
|
|
BaseDepositsTotal bin.Uint64
|
|
|
|
BaseFeesAccrued bin.Uint64
|
|
|
|
QuoteVault solana.PublicKey
|
|
|
|
QuoteDepositsTotal bin.Uint64
|
|
|
|
QuoteFeesAccrued bin.Uint64
|
|
|
|
QuoteDustThreshold bin.Uint64
|
|
|
|
RequestQueue solana.PublicKey
|
|
|
|
EventQueue solana.PublicKey
|
|
|
|
Bids solana.PublicKey
|
|
|
|
Asks solana.PublicKey
|
|
|
|
BaseLotSize bin.Uint64
|
|
|
|
QuoteLotSize bin.Uint64
|
|
|
|
FeeRateBPS bin.Uint64
|
|
|
|
ReferrerRebatesAccrued bin.Uint64
|
|
|
|
EndPadding [7]byte `json:"-"`
|
2020-11-06 08:47:26 -08:00
|
|
|
}
|
2020-11-05 14:54:08 -08:00
|
|
|
|
2020-11-09 09:59:24 -08:00
|
|
|
func (m *MarketV2) Decode(in []byte) error {
|
2020-11-12 14:57:43 -08:00
|
|
|
decoder := bin.NewDecoder(in)
|
|
|
|
err := decoder.Decode(&m)
|
2020-11-09 09:59:24 -08:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unpack: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-06 08:47:26 -08:00
|
|
|
type Orderbook struct {
|
2020-11-12 14:57:43 -08:00
|
|
|
SerumPadding [5]byte `json:"-"`
|
|
|
|
AccountFlags uint64
|
|
|
|
BumpIndex uint32 `bin:"sizeof=Nodes"`
|
|
|
|
ZeroPaddingA [4]byte `json:"-"`
|
|
|
|
FreeListLen uint32
|
|
|
|
ZeroPaddingB [4]byte `json:"-"`
|
|
|
|
FreeListHead uint32
|
|
|
|
Root uint32
|
|
|
|
LeafCount uint32
|
|
|
|
ZeroPaddingC [4]byte `json:"-"`
|
2020-11-13 06:52:02 -08:00
|
|
|
Nodes []*Slab
|
2020-11-11 05:39:35 -08:00
|
|
|
}
|
|
|
|
|
2020-11-06 13:31:18 -08:00
|
|
|
func (o *Orderbook) Items(descending bool, f func(node *SlabLeafNode) error) error {
|
2020-11-06 10:18:04 -08:00
|
|
|
if o.LeafCount == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-07 18:30:28 -08:00
|
|
|
index := uint32(0)
|
2020-11-06 10:18:04 -08:00
|
|
|
stack := []uint32{o.Root}
|
2020-11-07 18:30:28 -08:00
|
|
|
for len(stack) > 0 {
|
|
|
|
index, stack = stack[len(stack)-1], stack[:len(stack)-1]
|
2020-11-09 07:02:28 -08:00
|
|
|
if traceEnabled {
|
|
|
|
zlog.Debug("looking at slab index", zap.Int("index", int(index)))
|
|
|
|
}
|
2020-11-06 10:18:04 -08:00
|
|
|
slab := o.Nodes[index]
|
2020-11-12 14:57:43 -08:00
|
|
|
impl := slab.Impl
|
2020-11-06 13:14:55 -08:00
|
|
|
switch s := impl.(type) {
|
2020-11-06 13:31:18 -08:00
|
|
|
case *SlabInnerNode:
|
2020-11-06 10:18:04 -08:00
|
|
|
if descending {
|
|
|
|
stack = append(stack, s.Children[0], s.Children[1])
|
|
|
|
} else {
|
|
|
|
stack = append(stack, s.Children[1], s.Children[0])
|
|
|
|
}
|
2020-11-06 13:31:18 -08:00
|
|
|
case *SlabLeafNode:
|
2020-11-09 07:02:28 -08:00
|
|
|
if traceEnabled {
|
|
|
|
zlog.Debug("found leaf", zap.Int("leaf", int(index)))
|
|
|
|
}
|
2020-11-06 10:18:04 -08:00
|
|
|
f(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2020-11-13 06:52:02 -08:00
|
|
|
|
|
|
|
var SlabFactoryImplDef = bin.NewVariantDefinition(bin.Uint32TypeIDEncoding, []bin.VariantType{
|
2020-12-15 15:07:11 -08:00
|
|
|
{Name: "uninitialized", Type: (*SlabUninitialized)(nil)},
|
|
|
|
{Name: "inner_node", Type: (*SlabInnerNode)(nil)},
|
|
|
|
{Name: "leaf_node", Type: (*SlabLeafNode)(nil)},
|
|
|
|
{Name: "free_node", Type: (*SlabFreeNode)(nil)},
|
|
|
|
{Name: "last_free_node", Type: (*SlabLastFreeNode)(nil)},
|
2020-11-13 06:52:02 -08:00
|
|
|
})
|
|
|
|
|
|
|
|
type Slab struct {
|
|
|
|
bin.BaseVariant
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Slab) UnmarshalBinary(decoder *bin.Decoder) error {
|
|
|
|
return s.BaseVariant.UnmarshalBinaryVariant(decoder, SlabFactoryImplDef)
|
|
|
|
}
|
|
|
|
func (s *Slab) MarshalBinary(encoder *bin.Encoder) error {
|
2020-11-23 08:12:02 -08:00
|
|
|
err := encoder.WriteUint32(s.TypeID, binary.LittleEndian)
|
2020-11-13 06:52:02 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return encoder.Encode(s.Impl)
|
|
|
|
}
|
|
|
|
|
|
|
|
type SlabUninitialized struct {
|
|
|
|
Padding [4]byte `json:"-"`
|
|
|
|
PaddingA [64]byte `json:"-"` // ensure variant is 68 bytes
|
|
|
|
}
|
|
|
|
|
|
|
|
type SlabInnerNode struct {
|
|
|
|
PrefixLen uint32
|
|
|
|
Key bin.Uint128
|
|
|
|
Children [2]uint32
|
|
|
|
Padding [40]byte `json:"-"` // ensure variant is 68 bytes
|
|
|
|
}
|
|
|
|
|
|
|
|
type SlabFreeNode struct {
|
|
|
|
Next uint32
|
|
|
|
Padding [64]byte `json:"-"` // ensure variant is 68 bytes
|
|
|
|
}
|
|
|
|
|
|
|
|
type SlabLastFreeNode struct {
|
|
|
|
Padding [4]byte `json:"-"`
|
|
|
|
PaddingA [64]byte `json:"-"` // ensure variant is 68 bytes
|
|
|
|
}
|
|
|
|
|
|
|
|
type SlabLeafNode struct {
|
|
|
|
OwnerSlot uint8
|
|
|
|
FeeTier uint8
|
|
|
|
Padding [2]byte `json:"-"`
|
|
|
|
Key bin.Uint128
|
|
|
|
Owner solana.PublicKey
|
|
|
|
Quantity bin.Uint64
|
|
|
|
ClientOrderId bin.Uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SlabLeafNode) GetPrice() *big.Int {
|
|
|
|
raw := s.Key.BigInt().Bytes()
|
|
|
|
if len(raw) <= 8 {
|
|
|
|
return big.NewInt(0)
|
|
|
|
}
|
|
|
|
v := new(big.Int).SetBytes(raw[0 : len(raw)-8])
|
|
|
|
return v
|
|
|
|
}
|
2020-11-17 04:14:55 -08:00
|
|
|
|
|
|
|
type EventQueueHeader struct {
|
|
|
|
Serum [5]byte
|
|
|
|
AccountFlags uint64
|
|
|
|
Head uint64
|
|
|
|
Count uint64
|
|
|
|
SeqNum uint64
|
|
|
|
}
|
|
|
|
|
2020-11-17 04:54:55 -08:00
|
|
|
type EventFlag uint8
|
|
|
|
|
|
|
|
const (
|
|
|
|
EventFlagFill EventFlag = 0x1
|
|
|
|
EventFlagOut EventFlag = 0x2
|
|
|
|
EventFlagBid EventFlag = 0x4
|
|
|
|
EventFlagMaker EventFlag = 0x8
|
|
|
|
)
|
|
|
|
|
|
|
|
type EventSide string
|
|
|
|
|
|
|
|
const (
|
|
|
|
EventSideAsk EventSide = "ASK"
|
|
|
|
EventSideBid EventSide = "BID"
|
|
|
|
)
|
|
|
|
|
2020-11-17 05:24:59 -08:00
|
|
|
type Event struct {
|
|
|
|
Flag EventFlag
|
|
|
|
OwnerSlot uint8
|
|
|
|
FeeTier uint8
|
|
|
|
Padding [5]uint8
|
|
|
|
NativeQtyReleased uint64
|
|
|
|
NativeQtyPaid uint64
|
|
|
|
NativeFeeOrRebate uint64
|
|
|
|
OrderID bin.Uint128
|
|
|
|
Owner solana.PublicKey
|
|
|
|
ClientOrderID uint64
|
|
|
|
}
|
|
|
|
|
2020-11-17 04:54:55 -08:00
|
|
|
func (e *Event) Side() EventSide {
|
|
|
|
if Has(uint8(e.Flag), uint8(EventFlagBid)) {
|
|
|
|
return EventSideBid
|
|
|
|
}
|
|
|
|
return EventSideAsk
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Event) Filled() bool {
|
|
|
|
return Has(uint8(e.Flag), uint8(EventFlagFill))
|
2020-11-17 04:14:55 -08:00
|
|
|
}
|
2020-11-17 04:54:55 -08:00
|
|
|
|
2020-12-16 11:44:27 -08:00
|
|
|
func (e *Event) Equal(other *Event) bool {
|
|
|
|
return e.OrderID.Hi == other.OrderID.Hi && e.OrderID.Lo == other.OrderID.Lo
|
2020-12-15 15:07:11 -08:00
|
|
|
}
|
|
|
|
|
2020-11-17 04:54:55 -08:00
|
|
|
func Has(b, flag uint8) bool { return b&flag != 0 }
|
2020-11-17 05:24:59 -08:00
|
|
|
|
|
|
|
type EventQueue struct {
|
|
|
|
Header *EventQueueHeader
|
|
|
|
Events []*Event
|
|
|
|
}
|
|
|
|
|
|
|
|
func (q *EventQueue) DecodeFromBase64(b64 string) error {
|
|
|
|
data, err := base64.StdEncoding.DecodeString(b64)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("event queue: from base64: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return q.Decode(data)
|
|
|
|
}
|
|
|
|
|
2020-12-16 11:44:27 -08:00
|
|
|
var EventDataLength = int(bin.MustByteCount(&Event{}))
|
2020-11-17 05:24:59 -08:00
|
|
|
|
|
|
|
func (q *EventQueue) Decode(data []byte) error {
|
|
|
|
decoder := bin.NewDecoder(data)
|
|
|
|
err := decoder.Decode(&q.Header)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("event queue: decode header: %w", err)
|
|
|
|
}
|
2020-12-16 11:44:27 -08:00
|
|
|
for decoder.Remaining() >= EventDataLength {
|
2020-11-17 05:24:59 -08:00
|
|
|
var e *Event
|
|
|
|
err = decoder.Decode(&e)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("event queue: decode events: %w", err)
|
|
|
|
}
|
|
|
|
q.Events = append(q.Events, e)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2020-11-20 10:15:27 -08:00
|
|
|
|
|
|
|
type OpenOrdersV2 struct {
|
|
|
|
SerumPadding [5]byte `json:"-"`
|
|
|
|
AccountFlags bin.Uint64
|
|
|
|
Market solana.PublicKey
|
|
|
|
Owner solana.PublicKey
|
|
|
|
BaseTokenFree bin.Uint64
|
|
|
|
BaseTokenTotal bin.Uint64
|
|
|
|
QuoteTokenFree bin.Uint64
|
|
|
|
QuoteTokenTotal bin.Uint64
|
|
|
|
FreeSlotBits bin.Uint128
|
|
|
|
IsBidBits bin.Uint128
|
|
|
|
Orders [128]bin.Uint128
|
|
|
|
ClientIDs [128]bin.Uint64
|
|
|
|
ReferrerRebatesAccrued bin.Uint64
|
|
|
|
EndPadding [7]byte `json:"-"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *OpenOrdersV2) Decode(in []byte) error {
|
|
|
|
decoder := bin.NewDecoder(in)
|
|
|
|
err := decoder.Decode(&m)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unpack: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|