mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
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:
+24
-15
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user