mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
dex: bound the GPU book indices, and let a market sell actually cross
The C matcher takes the book pointer and the indices but no book LENGTH, so it cannot bounds-check, and the Go wrapper did not either. MatchOrderCPU guards the same field because indices genuinely can be out of range — one field, two readers, one guard. On a real Metal device that wrote past the end of a Go slice and answered 420 where the oracle answered 490. An empty book is the same hole from the other side: &book[0] panicked where the CPU path returned cleanly, so one build was total and the other fatal on identical input. Both cases hand off to the oracle rather than restate its bounds rule here; a second copy of a bounds check is a second chance to get it wrong. The shadow gate gave every market order the MAXIMAL price, "so it crosses every resting level". That is true for a market BUY, which sits on the bid and crosses any ask at or below it. A market SELL sits on the ask and crosses any bid at or above it, and no bid is ever above +inf — so every market sell matched nothing, compared empty against empty, and was counted VERIFIED. The mechanism the GPU path is assured by was measuring nothing for an entire order type, while an operator watched Verified climb and Divergences stay at zero. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
+15
-1
@@ -247,7 +247,21 @@ func orderToFlatTaker(order *Order) DEXOrder {
|
||||
}
|
||||
t.Remaining = t.Quantity
|
||||
if order.Type == Market {
|
||||
t.Price = DEXPrice{Integer: ^uint64(0), Fraction: ^uint64(0)}
|
||||
// A market order crosses every resting level, and WHICH price does that
|
||||
// depends on the side. A market BUY sits on the bid and crosses any ask at
|
||||
// or below it, so the maximal price sweeps the book. A market SELL sits on
|
||||
// the ask and crosses any bid at or above it — the maximal price crosses
|
||||
// NOTHING, because no bid is ever above +inf.
|
||||
//
|
||||
// Given the maximum for both, every market sell matched nothing in the
|
||||
// shadow, compared empty against empty, and was counted VERIFIED. An
|
||||
// operator watching Verified climb with Divergences at zero could not see
|
||||
// that an entire order type was being waved through unchecked.
|
||||
if t.Side == sideToDEX(Sell) {
|
||||
t.Price = DEXPrice{Integer: 0, Fraction: 0}
|
||||
} else {
|
||||
t.Price = DEXPrice{Integer: ^uint64(0), Fraction: ^uint64(0)}
|
||||
}
|
||||
} else {
|
||||
t.Price = priceRowFor(orderPriceUnits(order))
|
||||
}
|
||||
|
||||
@@ -112,6 +112,28 @@ func gpuMatchOrder(
|
||||
if len(bookIndices) == 0 {
|
||||
return nil, incoming.Quantity, nil
|
||||
}
|
||||
// The C matcher takes the indices and the book pointer but NO book length,
|
||||
// so it cannot bounds-check and neither did this wrapper. MatchOrderCPU
|
||||
// guards the same field (orderbook_match_cpu.go: `if int(idx) >= len(book)`)
|
||||
// because indices genuinely can be out of range — one field, two readers, and
|
||||
// only one of them checked. On a real device that wrote past the end of a Go
|
||||
// slice and answered 420 where the CPU answered 490.
|
||||
//
|
||||
// An empty book is the same hole seen from the other side: &book[0] panics
|
||||
// where the CPU path returns cleanly, so one build is total and the other is
|
||||
// fatal on identical input. Hand both to the oracle rather than reproduce its
|
||||
// guard here — a second copy of a bounds rule is a second chance to get it
|
||||
// wrong.
|
||||
if len(book) == 0 {
|
||||
t, r := MatchOrderCPU(incoming, book, bookIndices, tradeIDBase, timestamp)
|
||||
return t, r, nil
|
||||
}
|
||||
for _, idx := range bookIndices {
|
||||
if uint64(idx) >= uint64(len(book)) {
|
||||
t, r := MatchOrderCPU(incoming, book, bookIndices, tradeIDBase, timestamp)
|
||||
return t, r, nil
|
||||
}
|
||||
}
|
||||
|
||||
tradesOut := make([]DEXTrade, len(bookIndices))
|
||||
var (
|
||||
|
||||
Reference in New Issue
Block a user