fix(dex): resolve audit findings for NaN guards, side totality, and persistence symmetry

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-09-03 13:16:52 -07:00
co-authored by Hanzo Dev
parent c084b00a3d
commit c1de892454
3 changed files with 28 additions and 19 deletions
+24 -15
View File
@@ -23,11 +23,8 @@ const MaxSafePrice = float64(math.MaxInt64) / PriceMultiplier
// safePriceToInt converts a float64 price to PriceInt with overflow protection
func safePriceToInt(price float64) (PriceInt, error) {
if price < 0 {
return 0, fmt.Errorf("negative price: %f", price)
}
if price > MaxSafePrice {
return 0, fmt.Errorf("price overflow: %f exceeds max safe price %f", price, MaxSafePrice)
if math.IsNaN(price) || math.IsInf(price, 0) || !(price >= 0 && price <= MaxSafePrice) {
return 0, fmt.Errorf("invalid price: %f", price)
}
return PriceInt(price * PriceMultiplier), nil
}
@@ -741,8 +738,10 @@ func (ob *OrderBook) tryMatchImmediateLocked(order *Order) []Trade {
var oppositeTree *OrderTree
if order.Side == Buy {
oppositeTree = (*OrderTree)(atomic.LoadPointer(&ob.asks))
} else {
} else if order.Side == Sell {
oppositeTree = (*OrderTree)(atomic.LoadPointer(&ob.bids))
} else {
return nil
}
for order.RemainingSize > 0 {
@@ -756,18 +755,20 @@ func (ob *OrderBook) tryMatchImmediateLocked(order *Order) []Trade {
// tie where two distinct prices collapse to one float64. Legacy float-API
// orders (PriceUnits==0) keep the float comparison.
if order.Type == Limit {
if order.PriceUnits != 0 && bestOrder.PriceUnits != 0 {
if order.Side == Buy && order.PriceUnits < bestOrder.PriceUnits {
break
}
if order.Side == Sell && order.PriceUnits > bestOrder.PriceUnits {
if order.Side == Buy {
if order.PriceUnits != 0 && bestOrder.PriceUnits != 0 {
if order.PriceUnits < bestOrder.PriceUnits {
break
}
} else if order.Price < bestOrder.Price {
break
}
} else {
if order.Side == Buy && order.Price < bestOrder.Price {
break
}
if order.Side == Sell && order.Price > bestOrder.Price {
if order.PriceUnits != 0 && bestOrder.PriceUnits != 0 {
if order.PriceUnits > bestOrder.PriceUnits {
break
}
} else if order.Price > bestOrder.Price {
break
}
}
@@ -1076,6 +1077,14 @@ func (ob *OrderBook) validateOrder(order *Order) error {
if order == nil {
return ErrInvalidOrder
}
if order.Side != Buy && order.Side != Sell {
return ErrInvalidOrder
}
if math.IsNaN(order.Price) || math.IsInf(order.Price, 0) ||
math.IsNaN(order.StopPrice) || math.IsInf(order.StopPrice, 0) ||
math.IsNaN(order.Size) || math.IsInf(order.Size, 0) {
return ErrInvalidOrder
}
// Market and Stop orders don't require a price
if order.Type != Market && order.Type != Stop && order.Price <= 0 {
return ErrInvalidPrice
+3 -3
View File
@@ -102,10 +102,10 @@ func uint64ToUser(id uint64) string {
// sideToDEX maps the book Side to the DEXOrder side byte.
func sideToDEX(s Side) uint8 {
if s == Sell {
return DEXSideAsk
if s == Buy {
return DEXSideBid
}
return DEXSideBid
return DEXSideAsk
}
// dexToSide maps a DEXOrder side byte back to the book Side.
+1 -1
View File
@@ -94,7 +94,7 @@ func (o *SignedOrder) SigningHash() ([32]byte, error) {
if err != nil {
return [32]byte{}, err
}
if o.Size < 0 || o.Size > MaxSafePrice {
if math.IsNaN(o.Size) || math.IsInf(o.Size, 0) || !(o.Size >= 0 && o.Size <= MaxSafePrice) {
return [32]byte{}, errors.New("size out of range for SigningHash")
}
// The digest must cover the size the matcher ACTS on, not a rounded stand-in