Files
dex/pkg/api/zap_server_test.go
zeekayandHanzo Dev 10a084c0b2 refactor(dex): fold pkg/lx into pkg/dex — one matcher, one package
The matching engine (pkg/lx, 45.5K LOC incl. tests) and the deterministic
core layered on it (pkg/dex) are one matcher spread across two packages
(dex imported lx in 12 files; lx imported neither). Fold lx into dex and
drop the "lx" name entirely — pkg/dex is now the single matcher package.

Pure rename/fold, ZERO logic change. Behavior proven identical: the merged
pkg/dex runs 457 top-level tests (431 former-lx + 26 former-dex) and 489
test funcs (468 + 21), all green — matching, batch (BatchMatchEqualsSequential,
run-to-run determinism, tile boundary), GPU/CPU parity, truncation, and CL
swap math all pass unchanged.

What moved:
- git mv all 101 pkg/lx/*.go + 1 .c into pkg/dex/ (package lx -> package dex).
- lx's swap_test.go -> swap_math_test.go (only filename collision with dex's
  existing swap_test.go).
- Rewrote imports repo-wide: 54 pkg/lx import lines removed; 42 external
  importers (api, client, consensus, dchain, dpdk, lxgpu, marketdata, test/,
  tests/) repointed to pkg/dex with lx.X -> dex.X; the 12 in-package dex files
  had lx.X -> bare X. 717 selector lines rewritten. Stale pkg/lx comment refs
  updated (pkg/lxgpu left intact).

Collisions resolved (2, both package-level vars):
- ErrNoLiquidity: lx's pool-level error (dexError, used only in swap.go, never
  referenced via qualifier) renamed ErrPoolNoLiquidity to match the existing
  ErrPool* convention; dex's router-level ErrNoLiquidity (public, has errors.Is
  tests) kept.
- priceMultiplierBig: identical after fold (both big.NewInt(PriceMultiplier));
  deduped by deleting dex's copy.

Not folded — pkg/orderbook: it is NOT the GPU seam (that is orderbook_gpu.go,
which lives in the engine and folded in here). pkg/orderbook is a zero-importer,
placeholder-cgo island whose 13 core domain identifiers (Order, OrderBook, Trade,
OrderType, OrderStatus, PriceLevel, Buy, Sell, Market, Limit, Filled,
PartiallyFilled, NewOrderBook) collide incompatibly with the engine's. Folding
it verbatim breaks the build; folding via rename reintroduces the exact parallel
duplication this fold removes. Left for owner decision (recommend deletion as
dead code) — see refactor notes.

Follow-on (separate repos, not touched): 19 files under luxfi/dex import
pkg/lx externally — lux/precompile/dex, lux/precompile-dex/dex,
lux/consensus/examples/01-simple-bridge, lux/_consensus-v12537/examples,
lux/cex/pkg/engine — repoint to pkg/dex after this lands.

Verified: CGO_ENABLED=0 GOWORK=off go build ./... clean; go vet ./... clean;
go test ./... -count=1 all green (22 ok / 0 fail).

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 00:57:08 -07:00

335 lines
8.2 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package api
import (
"context"
"encoding/binary"
"testing"
"time"
"github.com/luxfi/dex/pkg/dex"
"github.com/luxfi/dex/pkg/log"
"github.com/luxfi/rpc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// nopLogger is a no-op logger for testing
type nopLogger struct{}
func (nopLogger) Info(msg string, args ...interface{}) {}
func (nopLogger) Error(msg string, args ...interface{}) {}
func (nopLogger) Warn(msg string, args ...interface{}) {}
func (nopLogger) Debug(msg string, args ...interface{}) {}
func (nopLogger) Fatal(msg string, args ...interface{}) {}
func (nopLogger) WithField(key string, value interface{}) log.Logger { return nopLogger{} }
func TestZAPServerBasic(t *testing.T) {
// Create orderbook
ob := dex.NewOrderBook("BTC-USD")
// Create and start ZAP server
logger := nopLogger{}
server := NewZAPServer(ob, ":0", logger)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := server.Start(ctx)
require.NoError(t, err)
defer server.Stop()
// Give server time to start
time.Sleep(10 * time.Millisecond)
// Connect client
client, err := rpc.Dial(ctx, server.Addr())
require.NoError(t, err)
defer client.Close()
// Test place order
orderPayload := encodeTestOrder(1, 50000.0, 1.0, dex.Buy, dex.Limit, "user1")
resp, err := client.CallRaw(ctx, "place_order", orderPayload)
require.NoError(t, err)
require.NotNil(t, resp)
// Verify ack
assert.GreaterOrEqual(t, len(resp), 8)
orderID := binary.BigEndian.Uint64(resp[0:8])
assert.Greater(t, orderID, uint64(0))
// Check stats
orders, trades, cancels := server.Stats()
assert.Equal(t, uint64(1), orders)
assert.Equal(t, uint64(0), trades)
assert.Equal(t, uint64(0), cancels)
}
func TestZAPServerOrderBook(t *testing.T) {
ob := dex.NewOrderBook("BTC-USD")
logger := nopLogger{}
server := NewZAPServer(ob, ":0", logger)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := server.Start(ctx)
require.NoError(t, err)
defer server.Stop()
time.Sleep(10 * time.Millisecond)
client, err := rpc.Dial(ctx, server.Addr())
require.NoError(t, err)
defer client.Close()
// Place some orders
for i := 0; i < 5; i++ {
price := 50000.0 - float64(i)*100
orderPayload := encodeTestOrder(uint64(i+1), price, 1.0, dex.Buy, dex.Limit, "user1")
_, err := client.CallRaw(ctx, "place_order", orderPayload)
require.NoError(t, err)
}
for i := 0; i < 5; i++ {
price := 50100.0 + float64(i)*100
orderPayload := encodeTestOrder(uint64(i+100), price, 1.0, dex.Sell, dex.Limit, "user2")
_, err := client.CallRaw(ctx, "place_order", orderPayload)
require.NoError(t, err)
}
// Get best bid
resp, err := client.CallRaw(ctx, "best_bid", nil)
require.NoError(t, err)
assert.GreaterOrEqual(t, len(resp), QuoteWireSize)
price := decodeFloat64(resp[0:8])
assert.Equal(t, 50000.0, price)
// Get best ask
resp, err = client.CallRaw(ctx, "best_ask", nil)
require.NoError(t, err)
assert.GreaterOrEqual(t, len(resp), QuoteWireSize)
price = decodeFloat64(resp[0:8])
assert.Equal(t, 50100.0, price)
// Get orderbook
depthReq := make([]byte, 4)
binary.BigEndian.PutUint32(depthReq, 10)
resp, err = client.CallRaw(ctx, "orderbook", depthReq)
require.NoError(t, err)
assert.GreaterOrEqual(t, len(resp), 8)
bidCount := binary.BigEndian.Uint32(resp[0:4])
askCount := binary.BigEndian.Uint32(resp[4:8])
assert.Equal(t, uint32(5), bidCount)
assert.Equal(t, uint32(5), askCount)
}
func TestZAPServerCancelOrder(t *testing.T) {
ob := dex.NewOrderBook("BTC-USD")
logger := nopLogger{}
server := NewZAPServer(ob, ":0", logger)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := server.Start(ctx)
require.NoError(t, err)
defer server.Stop()
time.Sleep(10 * time.Millisecond)
client, err := rpc.Dial(ctx, server.Addr())
require.NoError(t, err)
defer client.Close()
// Place order
orderPayload := encodeTestOrder(1, 50000.0, 1.0, dex.Buy, dex.Limit, "user1")
resp, err := client.CallRaw(ctx, "place_order", orderPayload)
require.NoError(t, err)
orderID := binary.BigEndian.Uint64(resp[0:8])
// Cancel order
cancelPayload := make([]byte, CancelWireSize)
binary.BigEndian.PutUint64(cancelPayload[0:8], orderID)
copy(cancelPayload[8:24], padNull([]byte("user1"), 16))
resp, err = client.CallRaw(ctx, "cancel_order", cancelPayload)
require.NoError(t, err)
// Verify ack with cancel status
assert.GreaterOrEqual(t, len(resp), 9)
respOrderID := binary.BigEndian.Uint64(resp[0:8])
assert.Equal(t, orderID, respOrderID)
assert.Equal(t, uint8(1), resp[8]) // Cancel status
// Check stats
_, _, cancels := server.Stats()
assert.Equal(t, uint64(1), cancels)
}
func TestZAPServerGetOrder(t *testing.T) {
ob := dex.NewOrderBook("BTC-USD")
logger := nopLogger{}
server := NewZAPServer(ob, ":0", logger)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := server.Start(ctx)
require.NoError(t, err)
defer server.Stop()
time.Sleep(10 * time.Millisecond)
client, err := rpc.Dial(ctx, server.Addr())
require.NoError(t, err)
defer client.Close()
// Place order
orderPayload := encodeTestOrder(1, 50000.0, 2.5, dex.Buy, dex.Limit, "user1")
resp, err := client.CallRaw(ctx, "place_order", orderPayload)
require.NoError(t, err)
orderID := binary.BigEndian.Uint64(resp[0:8])
// Get order
getReq := make([]byte, 8)
binary.BigEndian.PutUint64(getReq, orderID)
resp, err = client.CallRaw(ctx, "order", getReq)
require.NoError(t, err)
assert.GreaterOrEqual(t, len(resp), OrderWireSize)
// Decode order response
respOrderID := binary.BigEndian.Uint64(resp[8:16])
respPrice := decodeFloat64(resp[16:24])
respSize := decodeFloat64(resp[24:32])
respSide := dex.Side(resp[32])
assert.Equal(t, orderID, respOrderID)
assert.Equal(t, 50000.0, respPrice)
assert.Equal(t, 2.5, respSize)
assert.Equal(t, dex.Buy, respSide)
}
func BenchmarkZAPOrderPlacement(b *testing.B) {
ob := dex.NewOrderBook("BTC-USD")
logger := nopLogger{}
server := NewZAPServer(ob, ":0", logger)
ctx := context.Background()
err := server.Start(ctx)
if err != nil {
b.Fatal(err)
}
defer server.Stop()
time.Sleep(10 * time.Millisecond)
client, err := rpc.Dial(ctx, server.Addr())
if err != nil {
b.Fatal(err)
}
defer client.Close()
orderPayload := encodeTestOrder(0, 50000.0, 1.0, dex.Buy, dex.Limit, "user1")
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
// Update order ID
binary.BigEndian.PutUint64(orderPayload[8:16], uint64(i+1))
_, err := client.CallRaw(ctx, "place_order", orderPayload)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkZAPBestBid(b *testing.B) {
ob := dex.NewOrderBook("BTC-USD")
// Add some orders
for i := 0; i < 100; i++ {
order := &dex.Order{
ID: uint64(i + 1),
Price: 50000.0 - float64(i),
Size: 1.0,
Side: dex.Buy,
Type: dex.Limit,
UserID: "user1",
}
ob.AddOrder(order)
}
logger := nopLogger{}
server := NewZAPServer(ob, ":0", logger)
ctx := context.Background()
err := server.Start(ctx)
if err != nil {
b.Fatal(err)
}
defer server.Stop()
time.Sleep(10 * time.Millisecond)
client, err := rpc.Dial(ctx, server.Addr())
if err != nil {
b.Fatal(err)
}
defer client.Close()
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := client.CallRaw(ctx, "best_bid", nil)
if err != nil {
b.Fatal(err)
}
}
}
// Helper to encode a test order
func encodeTestOrder(id uint64, price, size float64, side dex.Side, orderType dex.OrderType, userID string) []byte {
buf := make([]byte, OrderWireSize)
// Symbol (8 bytes)
copy(buf[0:8], padNull([]byte("BTC-USD"), 8))
// Order ID
binary.BigEndian.PutUint64(buf[8:16], id)
// Price
encodeFloat64(buf[16:24], price)
// Size
encodeFloat64(buf[24:32], size)
// Side
buf[32] = byte(side)
// Type
buf[33] = byte(orderType)
// Flags (none)
binary.BigEndian.PutUint16(buf[34:36], 0)
// Timestamp
binary.BigEndian.PutUint64(buf[36:44], uint64(time.Now().UnixNano()))
// User ID
copy(buf[44:60], padNull([]byte(userID), 16))
return buf
}