Files
maker/dexcustody_test.go
zeekayandHanzo Dev 83b6e971c2 refactor: one price grid, one inverse pair
The scale prices cross the wire on was declared five times — priceGridMul,
gridMul, priceMultiplier, refPriceMultiplier, and again per command — and read
back three ways: priceToGrid, gridToFloat, gridToPrice. Same number every time.
dex.go said so out loud: "The maker re-declares it so it can size a deposit."

The place a value is used had been braided into the value itself. It is one
value, so it gets one name: PriceGridMul, exported because the commands need the
same scale and were each declaring their own. Conversions become one inverse
pair, GridOf and PriceOf, with PriceOfBig for a grid read off the wire as a
word — no open-coded /1e8 left anywhere.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-29 17:44:28 -07:00

231 lines
8.1 KiB
Go

package maker
import (
"bytes"
"encoding/binary"
"math/big"
"testing"
)
// dexcustody_test.go proves the maker's CUSTODY frames (DEFECT 2) and lock-sizing
// math are byte/value-identical to github.com/luxfi/dex/pkg/zapwire and
// pkg/dchain. We re-state the canonical encoders/lock math verbatim as references
// (the DEX's own pattern — re-declare the frozen frame, assert equality — rather
// than importing the whole dex module for a few pure functions) and assert the
// maker matches them. A frame or lock-math divergence fails the build.
// --- reference encoders, verbatim from dex/pkg/zapwire/zapwire.go ---
// refEncodeOpenMarket == zapwire.EncodeOpenMarket (zapwire.go:431-437).
func refEncodeOpenMarket(poolID, baseAsset, quoteAsset [32]byte) []byte {
const PoolIDSize, AssetIDSize = 32, 32
out := make([]byte, PoolIDSize+AssetIDSize+AssetIDSize)
copy(out[0:PoolIDSize], poolID[:])
copy(out[PoolIDSize:PoolIDSize+AssetIDSize], baseAsset[:])
copy(out[PoolIDSize+AssetIDSize:], quoteAsset[:])
return out
}
// refEncodeDeposit == zapwire.EncodeDeposit (zapwire.go:377-384).
func refEncodeDeposit(user string, asset [32]byte, amount uint64, ref [32]byte) []byte {
const UserSize, AssetIDSize, RefSize = 16, 32, 32
const custodyAmountOff = UserSize + AssetIDSize // 48
const custodyRefOff = UserSize + AssetIDSize + 8 // 56
out := make([]byte, UserSize+AssetIDSize+8+RefSize)
pad := make([]byte, UserSize)
copy(pad, user)
copy(out[0:UserSize], pad)
copy(out[UserSize:custodyAmountOff], asset[:])
binary.BigEndian.PutUint64(out[custodyAmountOff:custodyRefOff], amount)
copy(out[custodyRefOff:custodyRefOff+RefSize], ref[:])
return out
}
func TestEncodeOpenMarketMatchesZapwire(t *testing.T) {
pool := SymbolPoolID("LUX/LUSD")
got := encodeOpenMarket(pool, AssetLUX, AssetLUSD)
want := refEncodeOpenMarket(pool, AssetLUX, AssetLUSD)
if !bytes.Equal(got, want) {
t.Fatalf("encodeOpenMarket\n got=%x\nwant=%x", got, want)
}
if len(got) != 96 {
t.Errorf("open_market frame = %d bytes, want 96", len(got))
}
}
func TestEncodeDepositMatchesZapwire(t *testing.T) {
ref := depositRef("maker", AssetLUSD, 1250)
got := encodeDeposit("maker", AssetLUSD, 1250, ref)
want := refEncodeDeposit("maker", AssetLUSD, 1250, ref)
if !bytes.Equal(got, want) {
t.Fatalf("encodeDeposit\n got=%x\nwant=%x", got, want)
}
if len(got) != 88 {
t.Errorf("deposit frame = %d bytes, want 88", len(got))
}
}
// TestDepositRefMatchesVenueContentRef proves the maker's idempotency ref is
// byte-identical to the venue's contentRef (conservation_test.go:45-58 /
// cmd/fourpath-live main.go:88-97 with the deposit tag), so a byte-identical
// re-seed dedups to exactly-once on the venue.
func TestDepositRefMatchesVenueContentRef(t *testing.T) {
// refContentRef is verbatim venue contentRef with typ = TxDeposit(1).
refContentRef := func(typ byte, user string, asset [32]byte, amount uint64) [32]byte {
var ref [32]byte
copy(ref[:], asset[:])
ref[0] ^= typ
for i := 0; i < len(user) && i+1 < 24; i++ {
ref[1+i] ^= user[i]
}
binary.BigEndian.PutUint64(ref[24:32], binary.BigEndian.Uint64(asset[24:32])^amount)
return ref
}
for _, tc := range []struct {
user string
asset [32]byte
amount uint64
}{
{"maker", AssetLUSD, 1250},
{"maker", AssetLETH, 1},
{"seeder-x", AssetLUX, 99},
} {
got := depositRef(tc.user, tc.asset, tc.amount)
want := refContentRef(txDeposit, tc.user, tc.asset, tc.amount)
if got != want {
t.Errorf("depositRef(%q,%x,%d) = %x, want %x", tc.user, tc.asset, tc.amount, got, want)
}
}
// Idempotency: identical inputs -> identical ref.
if depositRef("maker", AssetLUSD, 1250) != depositRef("maker", AssetLUSD, 1250) {
t.Error("depositRef not deterministic")
}
// Distinctness: a different amount -> a different ref.
if depositRef("maker", AssetLUSD, 1250) == depositRef("maker", AssetLUSD, 1251) {
t.Error("depositRef collided across distinct amounts")
}
}
// --- reference lock math, verbatim from dex/pkg/dchain settle.go ---
// refOrderLock == pkg/dchain.orderLock composed with sizeToUnits/priceToInt/
// quoteUnitsCeil (settle.go:55-105). It returns the integer amount a place locks.
func refOrderLock(side uint8, price, size float64) uint64 {
if !(size > 0) {
return 0
}
units := uint64(size) // sizeToUnits
if units == 0 {
return 0
}
if side == sideSell {
return units
}
if !(price > 0) {
return 0
}
priceInt := uint64(price * PriceGridMul) // priceToInt
if priceInt == 0 {
return 0
}
q := new(big.Int).Mul(new(big.Int).SetUint64(units), new(big.Int).SetUint64(priceInt))
q.Add(q, big.NewInt(PriceGridMul-1)) // ceil
q.Quo(q, big.NewInt(PriceGridMul))
if !q.IsUint64() {
return 0
}
return q.Uint64()
}
func TestLockUnitsMatchesVenueOrderLock(t *testing.T) {
cases := []struct {
side uint8
price float64
size float64
}{
{sideSell, 12.5125, 100}, // ask: locks floor(100)=100 base
{sideBuy, 12.4875, 100}, // bid: locks ceil(100*12.4875)=1249 quote
{sideSell, 0.004, 100}, // LETH ask
{sideBuy, 0.003996, 150}, // LETH bid, fractional price
{sideSell, 1.0, 1.9}, // truncating sell size -> 1 unit
{sideBuy, 7.0, 3.0}, // exact: 21 quote
{sideBuy, 0.0000001, 1}, // sub-tick price floors to 0 lock
}
for _, c := range cases {
got := lockUnits(c.side, c.price, c.size)
want := refOrderLock(c.side, c.price, c.size)
if got != want {
t.Errorf("lockUnits(side=%d, %g, %g) = %d, venue orderLock = %d", c.side, c.price, c.size, got, want)
}
}
}
// TestDepositCoversLock is the funding-correctness invariant: the deposit a
// custody seed issues for one side must be >= the sum of that side's per-order
// locks, so no place is rejected for insufficient funds. We size with lockUnits
// (== orderLock), so equality holds exactly.
func TestDepositCoversLock(t *testing.T) {
// A representative ask ladder (size, price) for LUX/LUSD.
asks := []struct{ price, size float64 }{
{12.5125, 100}, {12.575, 150}, {12.6375, 225},
}
var deposit, totalLock uint64
for _, a := range asks {
deposit += lockUnits(sideSell, a.price, a.size) // what fundAll deposits
totalLock += refOrderLock(sideSell, a.price, a.size) // what the venue locks
}
if deposit < totalLock {
t.Fatalf("base deposit %d < total ask lock %d (would under-fund)", deposit, totalLock)
}
if deposit != totalLock {
t.Errorf("base deposit %d != exact lock %d (over/under-funding)", deposit, totalLock)
}
}
// TestDecodeBalanceRespMatchesZapwire proves the maker reads a deposit response
// the same way zapwire.DecodeBalanceResp writes it (status[1]+amount[8]).
func TestDecodeBalanceRespMatchesZapwire(t *testing.T) {
// refEncodeBalanceResp == zapwire.EncodeBalanceResp (zapwire.go:453-458).
refEncode := func(status uint8, amount uint64) []byte {
out := make([]byte, 9)
out[0] = status
binary.BigEndian.PutUint64(out[1:9], amount)
return out
}
resp := refEncode(statusPlaced, 1249)
status, amount, err := decodeBalanceResp(resp)
if err != nil {
t.Fatalf("decodeBalanceResp: %v", err)
}
if status != statusPlaced || amount != 1249 {
t.Errorf("decoded (status=%d, amount=%d), want (0, 1249)", status, amount)
}
if _, _, err := decodeBalanceResp([]byte{0x00}); err == nil {
t.Error("expected error on short balance resp")
}
}
// TestAssetIDsArePinned documents and locks the canonical devnet asset ids
// (DEFECT 3): native LUX = all-zero; LUSD/LETH = the a32 label form the live
// settlement rail (cmd/fourpath-live, conservation_test) uses on devnet.
func TestAssetIDsArePinned(t *testing.T) {
if AssetLUX != ([32]byte{}) {
t.Errorf("AssetLUX must be the all-zero native id, got %x", AssetLUX)
}
// a32(0x4c555344_000001): ASCII "LUSD" packed into the low bytes.
var wantLUSD, wantLETH [32]byte
binary.BigEndian.PutUint64(wantLUSD[24:32], 0x4c555344_000001)
binary.BigEndian.PutUint64(wantLETH[24:32], 0x4c455448_000001)
if AssetLUSD != wantLUSD {
t.Errorf("AssetLUSD = %x, want %x", AssetLUSD, wantLUSD)
}
if AssetLETH != wantLETH {
t.Errorf("AssetLETH = %x, want %x", AssetLETH, wantLETH)
}
// The two non-native ids must be distinct (injective), and distinct from native.
if AssetLUSD == AssetLETH || AssetLUSD == AssetLUX || AssetLETH == AssetLUX {
t.Error("asset ids are not injective")
}
}