Files
dex/pkg/dchain/fourpath_e2e_test.go
zeekay baa275e80f feat(dex): FIX 4.4 acceptor + unified 4-path trading e2e
Adds the missing SERVER side of the FIX trading path and a unified e2e
proving REAL fills over all four DEX access paths against ONE book.

The decomplection: the D-Chain CLOB (pkg/dchain) is the single matcher,
reached over the single frozen clob_* ZAP surface (pkg/zapwire). Every
access path is a thin wire dialect onto that surface — none owns a
matcher. This commit adds the two missing dialects (FIX, a focused WS
venue handler) and the shared seam they translate through.

New:
- pkg/venue: ONE Venue interface (ensure/place/cancel/submit = the
  clob_* ops) + ONE production ZAP-backed impl. The seam FIX, WS, and
  the 0x9010 precompile all route through. Dials the SAME rpc.Listen
  socket dvenue serves.
- pkg/fix: dependency-free FIX 4.4 codec (BodyLength tag 9 + CheckSum
  tag 10 computed AND validated; spec-correct emit/parse) + a TCP
  acceptor (Logon/Heartbeat/TestRequest/Logout, monotonic seq,
  NewOrderSingle->clob_submit->ExecutionReport, OrderCancelRequest->
  clob_cancel). Pure protocol front-end onto Venue; no matcher.
- pkg/api/venue_ws.go: minimal gorilla/websocket /ws handler that
  translates a JSON order to Venue.Submit and streams fills back —
  the WS dialect onto the SAME venue (kept orthogonal to the existing
  TradingEngine product WS server).
- cmd/fix-gateway: runs the acceptor in front of a live venue
  (-venue clob_* endpoint), the production form of the FIX path.
- pkg/dchain/fourpath_e2e_test.go: stands up ONE venue + ONE LUX/LUSD
  book and drives a real limit-order -> match -> FILL over EACH path
  (V4 precompile engine_zap frame | ZAP native | FIX | WS) against the
  SAME resting asks, asserting real consensus fills, per-path custody
  changes, and GLOBAL conservation (available+locked invariant).

V4 leg: exercised at the engine_zap WIRE boundary — the exact 66-byte
clob_submit frame engine_zap.Swap emits (asserted byte-identical to
zapwire.EncodeSubmit) + the V4 BalanceDelta sign convention derived from
the consensus fills. A live luxd EVM (StateDB replay-idempotency, gas,
PoolManager) is what the full on-chain precompile call additionally
needs; the matcher boundary is proven in-process here.

No deps added; no existing files modified (go.mod/go.sum untouched).
Builds CGO_ENABLED=0; race-clean.
2026-06-19 13:05:58 -07:00

533 lines
20 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build !cgo
// +build !cgo
// fourpath_e2e_test.go is the UNIFIED four-path trading proof: it stands up ONE
// D-Chain venue with ONE seeded LUX/LUSD market and drives a real limit order ->
// match -> FILL over EACH of the four DEX access paths against the SAME book,
// asserting each path produces a real CONSENSUS-COMPUTED fill and the correct
// custody balance change, with GLOBAL value conservation (I = available + locked)
// intact across all four crosses.
//
// THE FOUR PATHS — each is a thin wire dialect onto the SAME clob_* matcher
// surface (pkg/zapwire), never its own matcher:
//
// 1. V4 precompile (0x9010): the engine_zap boundary — the EXACT 66-byte
// clob_submit frame lux/precompile/dex/engine_zap.go Swap() builds (the V4
// zeroForOne/AmountSpecified/SqrtPriceLimit -> CLOB side/size/limit mapping),
// submitted to the venue, with the V4 BalanceDelta derived from the
// consensus fills exactly as fillsToDelta does. See v4SubmitFrame /
// v4BalanceDelta below and the NOTE on what a live luxd EVM adds.
// 2. ZAP native: pkg/zapwire clob_* frames over the venue's rpc.Listen socket —
// the existing e2eClient.submit path.
// 3. FIX: the new pkg/fix acceptor — a real TCP FIX 4.4 session (Logon,
// NewOrderSingle, ExecutionReport) whose order routes to the SAME venue via
// the venue.Venue (clob_*) seam.
// 4. WS: the new pkg/api VenueWS handler — a JSON order over a real
// gorilla/websocket /ws connection routed to the SAME venue via venue.Venue.
//
// All four BUY orders cross the SAME resting asks the maker seeded over ZAP; the
// fills every path receives are what the consensus auto-sealer re-derived at
// Verify (match -> Verify -> Accept), not a synchronous single-process mutation.
package dchain
import (
"bufio"
"context"
"encoding/binary"
"fmt"
"io"
"math"
"math/big"
"net"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
"github.com/gorilla/websocket"
dexapi "github.com/luxfi/dex/pkg/api"
"github.com/luxfi/dex/pkg/fix"
dexvenue "github.com/luxfi/dex/pkg/venue"
"github.com/luxfi/dex/pkg/zapwire"
)
// fixClient is a minimal FIX 4.4 client over TCP for the e2e — it speaks the SAME
// pkg/fix codec the acceptor uses (so this also proves client<->server interop),
// with a monotonic outbound seq and length-prefixed framed reads.
type fixClient struct {
conn net.Conn
r *bufio.Reader
sender string
target string
seq int
}
func dialFIX(t *testing.T, addr, sender, target string) *fixClient {
t.Helper()
conn, err := net.Dial("tcp", addr)
if err != nil {
t.Fatalf("dial FIX acceptor %s: %v", addr, err)
}
return &fixClient{conn: conn, r: bufio.NewReader(conn), sender: sender, target: target, seq: 1}
}
func (c *fixClient) close() { _ = c.conn.Close() }
func (c *fixClient) send(t *testing.T, m *fix.Message) {
t.Helper()
m.Set(fix.TagSenderCompID, c.sender).
Set(fix.TagTargetCompID, c.target).
SetInt(fix.TagMsgSeqNum, c.seq)
c.seq++
if _, err := c.conn.Write(m.Serialize()); err != nil {
t.Fatalf("FIX client write: %v", err)
}
}
// recv reads one framed FIX message: 8=..SOH, 9=<n>SOH, n body bytes, 7-byte 10=
// trailer — then re-validates via the codec.
func (c *fixClient) recv(t *testing.T) *fix.Message {
t.Helper()
_ = c.conn.SetReadDeadline(time.Now().Add(10 * time.Second))
begin, err := c.r.ReadBytes(fix.SOH)
if err != nil {
t.Fatalf("FIX recv begin: %v", err)
}
blField, err := c.r.ReadBytes(fix.SOH)
if err != nil {
t.Fatalf("FIX recv bodylen: %v", err)
}
n, err := strconv.Atoi(string(blField[2 : len(blField)-1]))
if err != nil {
t.Fatalf("FIX recv bad bodylen %q: %v", blField, err)
}
body := make([]byte, n)
if _, err := io.ReadFull(c.r, body); err != nil {
t.Fatalf("FIX recv body: %v", err)
}
trailer := make([]byte, 7)
if _, err := io.ReadFull(c.r, trailer); err != nil {
t.Fatalf("FIX recv trailer: %v", err)
}
frame := make([]byte, 0, len(begin)+len(blField)+len(body)+len(trailer))
frame = append(frame, begin...)
frame = append(frame, blField...)
frame = append(frame, body...)
frame = append(frame, trailer...)
m, err := fix.Parse(frame)
if err != nil {
t.Fatalf("FIX recv parse: %v (frame=%q)", err, frame)
}
return m
}
func (c *fixClient) logon(t *testing.T) {
t.Helper()
c.send(t, fix.NewMessage(fix.MsgTypeLogon).SetInt(fix.TagEncryptMethod, 0).SetInt(fix.TagHeartBtInt, 5))
reply := c.recv(t)
if reply.MsgType() != fix.MsgTypeLogon {
t.Fatalf("FIX: expected Logon reply, got %q", reply.MsgType())
}
}
// custody seeds a deposit over the venue's ZAP socket (the SAME clob_deposit wire
// path the chains/dexvm proxy uses). Uses contentRef so a retry would dedup.
func (c *e2eClient) deposit(t *testing.T, user string, asset [32]byte, amount uint64) {
t.Helper()
ref := contentRef(byte(TxDeposit), user, asset, amount)
resp, err := c.conn.Call(c.ctx, zapwire.MethodDeposit, zapwire.EncodeDeposit(user, asset, amount, ref))
if err != nil {
t.Fatalf("clob_deposit %s %d: %v", user, amount, err)
}
status, credited, derr := zapwire.DecodeBalanceResp(resp)
if derr != nil || status != zapwire.StatusPlaced || credited != amount {
t.Fatalf("clob_deposit %s %d: status=%d credited=%d err=%v", user, amount, status, credited, derr)
}
}
// openMarket binds a market's (base, quote) asset ids over ZAP (clob_open_market)
// so the venue knows which asset each order locks/settles.
func (c *e2eClient) openMarket(t *testing.T, pool, base, quote [32]byte) {
t.Helper()
resp, err := c.conn.Call(c.ctx, zapwire.MethodOpenMarket, zapwire.EncodeOpenMarket(pool, base, quote))
if err != nil {
t.Fatalf("clob_open_market: %v", err)
}
if len(resp) < 9 || resp[8] != zapwire.StatusPlaced {
t.Fatalf("clob_open_market not placed: resp=%x", resp)
}
}
// v4SubmitFrame builds the EXACT clob_submit payload lux/precompile/dex
// engine_zap.go Swap() emits for a marketable order, so the V4 leg exercises the
// real engine_zap WIRE CONTRACT (the frozen 66-byte frame), not a re-derivation:
//
// [0:32] poolID
// [32] side (V4 zeroForOne -> 1=sell, else 0=buy)
// [33] isMarket (SqrtPriceLimit at the extreme => market)
// [34:42] limitPrice big-endian float64
// [42:50] size big-endian float64 = |AmountSpecified|
// [50:66] caller[:16] the EVM caller's 16-byte handle (taker account)
//
// This is byte-identical to zapwire.EncodeSubmit(pool, side, isMarket, limit,
// size, caller) — the frame is frozen and re-declared in three homes precisely so
// the precompile, the proxy, and the d-chain match. We assert that equality too.
func v4SubmitFrame(poolID [32]byte, side uint8, isMarket bool, limit, size float64, caller string) []byte {
payload := make([]byte, 66)
copy(payload[0:32], poolID[:])
payload[32] = side
if isMarket {
payload[33] = 1
}
binary.BigEndian.PutUint64(payload[34:42], math.Float64bits(limit))
binary.BigEndian.PutUint64(payload[42:50], math.Float64bits(size))
copy(payload[50:66], caller)
return payload
}
// v4BalanceDelta computes the V4 (amount0, amount1) BalanceDelta from the
// consensus fills exactly as lux/precompile/dex engine_zap.go fillsToDelta does:
// V4 taker-perspective sign convention — positive = user owes the pool, negative =
// pool owes the user. For a BUY (!zeroForOne): receives base (amount0<0), owes
// quote (amount1>0). This is the value the precompile would hand the EVM
// PoolManager; deriving it here proves the V4 leg's settlement math end to end.
func v4BalanceDelta(zeroForOne bool, fills []zapwire.Fill) (amount0, amount1 *big.Int) {
base, quote := 0.0, 0.0
for _, f := range fills {
base += f.Size
quote += f.Price * f.Size
}
ceil := func(v float64) *big.Int { return big.NewInt(int64(math.Ceil(v))) }
floor := func(v float64) *big.Int { return big.NewInt(int64(math.Floor(v))) }
if zeroForOne {
return ceil(base), new(big.Int).Neg(floor(quote))
}
return new(big.Int).Neg(floor(base)), ceil(quote)
}
// fixCross drives the FIX path: connect to the acceptor, Logon as `user`,
// NewOrderSingle(BUY size@limit), and collect the fills from the ExecutionReports
// until a terminal status (Fill/Canceled). Returns the fills as zapwire.Fill so
// the e2e asserts them uniformly with the other paths.
func fixCross(t *testing.T, addr, user, symbol string, limit, size float64) []zapwire.Fill {
t.Helper()
cli := dialFIX(t, addr, user, "LXDEX")
defer cli.close()
cli.logon(t)
cli.send(t, fix.NewMessage(fix.MsgTypeNewOrderSingle).
Set(fix.TagClOrdID, "FIX-"+user).
Set(fix.TagSymbol, symbol).
Set(fix.TagSide, fix.SideBuy).
Set(fix.TagOrderQty, formatFloat(size)).
Set(fix.TagOrdType, fix.OrdTypeLimit).
Set(fix.TagPrice, formatFloat(limit)))
var fills []zapwire.Fill
for {
m := cli.recv(t)
if m.MsgType() != fix.MsgTypeExecutionReport {
t.Fatalf("FIX: expected ExecutionReport, got %q", m.MsgType())
}
execType, _ := m.Get(fix.TagExecType)
switch execType {
case fix.ExecTypePartialFill, fix.ExecTypeFill:
lastQty, _ := m.GetFloat(fix.TagLastQty)
lastPx, _ := m.GetFloat(fix.TagLastPx)
fills = append(fills, zapwire.Fill{Price: lastPx, Size: lastQty, TakerSide: zapwire.SideBuy})
if execType == fix.ExecTypeFill {
return fills
}
case fix.ExecTypeNew:
// ack before fills (or before an IOC cancel) — keep reading.
case fix.ExecTypeCanceled:
return fills // IOC remainder / no-liquidity terminal
case fix.ExecTypeRejected:
text, _ := m.Get(fix.TagText)
t.Fatalf("FIX order rejected: %s", text)
default:
t.Fatalf("FIX: unexpected ExecType %q", execType)
}
}
}
// wsCross drives the WS path: open a /ws connection to the VenueWS test server,
// send a JSON marketable BUY, and read back the fills.
func wsCross(t *testing.T, wsURL, user, symbol string, limit, size float64) []zapwire.Fill {
t.Helper()
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
if err != nil {
t.Fatalf("WS dial: %v", err)
}
defer conn.Close()
if err := conn.WriteJSON(map[string]interface{}{
"type": "submit",
"symbol": symbol,
"side": "buy",
"orderQty": size,
"price": limit,
"isMarket": false,
"user": user,
"clOrdId": "WS-" + user,
}); err != nil {
t.Fatalf("WS write: %v", err)
}
_ = conn.SetReadDeadline(time.Now().Add(10 * time.Second))
var resp struct {
Type string `json:"type"`
Error string `json:"error"`
Fills []struct {
Price float64 `json:"price"`
Size float64 `json:"size"`
TakerSide string `json:"takerSide"`
} `json:"fills"`
}
if err := conn.ReadJSON(&resp); err != nil {
t.Fatalf("WS read: %v", err)
}
if resp.Type == "error" {
t.Fatalf("WS order error: %s", resp.Error)
}
fills := make([]zapwire.Fill, 0, len(resp.Fills))
for _, f := range resp.Fills {
side := zapwire.SideBuy
if f.TakerSide == "sell" {
side = zapwire.SideSell
}
fills = append(fills, zapwire.Fill{Price: f.Price, Size: f.Size, TakerSide: side})
}
return fills
}
func formatFloat(v float64) string { return fmt.Sprintf("%g", v) }
// TestFourPathTradingE2E is the unified four-path proof.
func TestFourPathTradingE2E(t *testing.T) {
dir := t.TempDir()
v := bootVenue(t, dir)
defer v.stop()
t.Logf("venue d-chain up: ZAP endpoint=%s", v.addr)
// One ZAP control client for seeding (deposits, open-market, resting asks) +
// the native ZAP cross.
cli, closeCli := dialVenue(t, v.addr)
defer closeCli()
const symbol = "LUX/LUSD"
pool := poolIDForSymbol(symbol)
const askSize = 1.0 // each resting ask is 1 LUX; each path buys exactly 1
// One leg PER path, in execution order. Each path crosses a DISTINCT resting
// ask at its own price, lowest-first (price-time priority): the maker rests
// 1 LUX @ {10,20,30,40}; V4 buys @10 (the then-lowest), ZAP @20, FIX @30, WS
// @40. Distinct prices ALSO make the four place/submit frames distinct, so the
// d-chain's content-addressed dedup (seen: keyed on the whole frame) treats
// them as four genuinely different orders rather than one idempotent retry.
const maker = "maker"
type leg struct {
path string
user string
price float64
}
legs := []leg{
{"V4", "v4taker", 10.0},
{"ZAP", "zaptaker", 20.0},
{"FIX", "fixtaker", 30.0},
{"WS", "wstaker", 40.0},
}
// --- seed custody + market (over ZAP, the canonical custody wire path) ---
cli.ensureMarket(t, pool)
cli.openMarket(t, pool, assetLUX, assetLUSD)
// Maker funds len(legs) LUX (to rest one 1-LUX ask per path).
cli.deposit(t, maker, assetLUX, uint64(len(legs)))
// Each taker funds exactly its leg price in LUSD (to buy 1 LUX at that price).
var totalQuoteIn uint64
for _, l := range legs {
cli.deposit(t, dexvenue.UserHandle(l.user), assetLUSD, uint64(l.price))
totalQuoteIn += uint64(l.price)
}
assertBalance(t, v.vm, maker, assetLUX, uint64(len(legs)), 0)
t.Logf("seeded: maker=%d LUX; takers fund %d LUSD total (10+20+30+40)", len(legs), totalQuoteIn)
// --- maker rests one SELL ask of 1 LUX per leg price (locks len(legs) LUX). ---
for _, l := range legs {
cli.place(t, pool, zapwire.SideSell, l.price, askSize, maker)
}
assertBalance(t, v.vm, maker, assetLUX, 0, uint64(len(legs))) // all LUX locked in resting asks
wantRem := float64(len(legs)) * askSize
if n, rem, _, _, ok := v.vm.BookDepth(pool); !ok || n != len(legs) || rem != wantRem {
t.Fatalf("seeded asks: orders=%d remaining=%v ok=%v, want %d / %v", n, rem, ok, len(legs), wantRem)
}
t.Logf("maker rested %d asks of %g LUX @ {10,20,30,40} LUSD (%d LUX locked)", len(legs), askSize, len(legs))
// --- stand up the FIX acceptor and the WS venue front-end, BOTH onto the SAME
// venue via the venue.Venue (clob_*) seam — one matcher, many protocols. ---
fixVenue, err := dexvenue.DialVenue(context.Background(), v.addr)
if err != nil {
t.Fatalf("FIX venue dial: %v", err)
}
acc, err := fix.NewAcceptor(fix.Config{Addr: "127.0.0.1:0", Venue: fixVenue, TargetCompID: "LXDEX", HeartBtInt: 5})
if err != nil {
t.Fatalf("NewAcceptor: %v", err)
}
fixCtx, fixCancel := context.WithCancel(context.Background())
defer fixCancel()
go func() { _ = acc.Serve(fixCtx) }()
defer acc.Close()
wsVenueConn, err := dexvenue.DialVenue(context.Background(), v.addr)
if err != nil {
t.Fatalf("WS venue dial: %v", err)
}
wsHandler := dexapi.NewVenueWS(wsVenueConn)
httpSrv := httptest.NewServer(http.HandlerFunc(wsHandler.HandleConnection))
defer httpSrv.Close()
wsURL := "ws" + strings.TrimPrefix(httpSrv.URL, "http")
t.Logf("FIX acceptor=%s WS endpoint=%s/ws (both -> same venue %s)", acc.Addr(), wsURL, v.addr)
// expectedRemaining tracks the book as each path consumes one ask.
expectedRemaining := float64(len(legs)) * askSize
crossOne := func(l leg, fills []zapwire.Fill) {
t.Helper()
if len(fills) != 1 {
t.Fatalf("%s: got %d fills, want exactly 1", l.path, len(fills))
}
f := fills[0]
if f.Size != askSize || f.Price != l.price {
t.Fatalf("%s: fill %g@%g, want %g@%g", l.path, f.Size, f.Price, askSize, l.price)
}
expectedRemaining -= askSize
if _, rem, _, _, ok := v.vm.BookDepth(pool); !ok || rem != expectedRemaining {
t.Fatalf("%s: book remaining=%v after cross, want %v", l.path, rem, expectedRemaining)
}
// Per-path custody change: the taker received 1 LUX, spent its price in LUSD
// (limit*size == lock exactly, so 0 LUSD refunded, 0 left).
u := dexvenue.UserHandle(l.user)
assertBalance(t, v.vm, u, assetLUX, 1, 0)
assertBalance(t, v.vm, u, assetLUSD, 0, 0)
t.Logf(" [%s] FILL %g LUX @ %g LUSD (consensus-settled); book now %g; taker %s = 1 LUX / 0 LUSD",
l.path, f.Size, f.Price, expectedRemaining, u)
}
// legByPath looks a leg up by its path label (the four blocks below address
// their own leg by name for readability).
legByPath := func(name string) leg {
for _, l := range legs {
if l.path == name {
return l
}
}
t.Fatalf("no leg %q", name)
return leg{}
}
// ===== PATH 1: V4 precompile (engine_zap boundary) =====
// Build the EXACT engine_zap clob_submit frame and submit it to the venue. A
// BUY of 1 LUX is !zeroForOne, AmountSpecified magnitude 1, a bounded
// (non-market) IOC limited at the leg price. Assert the frame is byte-identical
// to zapwire.EncodeSubmit (the frozen-frame three-homes guarantee), then derive
// the V4 BalanceDelta from the consensus fills.
{
l := legByPath("V4")
caller := dexvenue.UserHandle(l.user)
frame := v4SubmitFrame(pool, zapwire.SideBuy, false, l.price, askSize, caller)
want := zapwire.EncodeSubmit(pool, zapwire.SideBuy, false, l.price, askSize, caller)
if string(frame) != string(want) {
t.Fatalf("V4 engine_zap frame != zapwire.EncodeSubmit (frozen-frame parity broken)")
}
resp, err := cli.conn.Call(cli.ctx, zapwire.MethodSubmit, frame)
if err != nil {
t.Fatalf("V4 clob_submit: %v", err)
}
fills, err := zapwire.DecodeFills(resp)
if err != nil {
t.Fatalf("V4 fills decode: %v", err)
}
crossOne(l, fills)
// V4 BalanceDelta (taker perspective): buy => receives base (amount0<0),
// owes quote (amount1>0). With 1 LUX @ price: amount0=-1, amount1=+price.
a0, a1 := v4BalanceDelta(false, fills)
if a0.Int64() != -1 || a1.Int64() != int64(l.price) {
t.Fatalf("V4 BalanceDelta = (%d,%d), want (-1,+%d)", a0.Int64(), a1.Int64(), int64(l.price))
}
t.Logf(" [V4] BalanceDelta amount0=%d (base in) amount1=%d (quote owed) — V4 sign convention OK", a0.Int64(), a1.Int64())
}
// ===== PATH 2: ZAP native =====
{
l := legByPath("ZAP")
fills := cli.submit(t, pool, zapwire.SideBuy, l.price, askSize, dexvenue.UserHandle(l.user))
crossOne(l, fills)
}
// ===== PATH 3: FIX =====
{
l := legByPath("FIX")
fills := fixCross(t, acc.Addr(), l.user, symbol, l.price, askSize)
crossOne(l, fills)
}
// ===== PATH 4: WS =====
{
l := legByPath("WS")
fills := wsCross(t, wsURL+"/ws", l.user, symbol, l.price, askSize)
crossOne(l, fills)
}
// --- all asks consumed: book empty, maker fully settled ---
if _, rem, _, _, ok := v.vm.BookDepth(pool); !ok || rem != 0.0 {
t.Fatalf("after %d crosses, book remaining=%v, want 0", len(legs), rem)
}
// Maker sold all LUX, received the sum of the leg prices in LUSD.
assertBalance(t, v.vm, maker, assetLUX, 0, 0)
assertBalance(t, v.vm, maker, assetLUSD, totalQuoteIn, 0)
// --- GLOBAL CONSERVATION (I = available + locked), per asset, across every
// account that touched the book. Nothing minted, nothing burned. ---
accounts := []string{maker}
for _, l := range legs {
accounts = append(accounts, dexvenue.UserHandle(l.user))
}
totalLUX := conserved(t, v.vm, accounts, assetLUX)
totalLUSD := conserved(t, v.vm, accounts, assetLUSD)
if totalLUX != uint64(len(legs)) {
t.Fatalf("LUX conservation: total available+locked=%d, want %d (maker deposited %d)", totalLUX, len(legs), len(legs))
}
if totalLUSD != totalQuoteIn {
t.Fatalf("LUSD conservation: total available+locked=%d, want %d (sum of leg prices)", totalLUSD, totalQuoteIn)
}
fmt.Printf("\n=== FOUR-PATH TRADING E2E: PASS ===\n"+
" one venue, one LUX/LUSD book; a real consensus FILL over EACH path:\n"+
" V4 precompile (engine_zap frame) | ZAP native | FIX acceptor | WS venue\n"+
" %d asks consumed; maker 0 LUX / %d LUSD; each taker 1 LUX / 0 LUSD\n"+
" CONSERVATION: LUX=%d (=%d in), LUSD=%d (=%d in) — available+locked, nothing minted\n",
len(legs), totalQuoteIn, totalLUX, len(legs), totalLUSD, totalQuoteIn)
}
// conserved sums (available + locked) of asset across accounts — the
// conservation quantity I. Every unit a fill removes from one account's locked is
// added to a counterparty's available, so this total is invariant under matching.
func conserved(t *testing.T, vm *VM, accounts []string, asset [32]byte) uint64 {
t.Helper()
var total uint64
for _, u := range accounts {
avail, locked, err := vm.Balance(u, asset)
if err != nil {
t.Fatalf("Balance(%s): %v", u, err)
}
total += avail + locked
}
return total
}