mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
fix(dexcore): widen AccountID 16->32 bytes — injective for 20-byte EVM addresses
The cEVM home's taker is a 20-byte EVM address; the prior 16-byte AccountID would fold a 20-byte address lossily, so two distinct addresses sharing a leading 16-byte prefix could collide as one dexcore account and drain each other's resting-order proceeds (cross-user drain on the account axis). Widen AccountID to the full 32-byte injective settlement identity both homes map into without a lossy fold: the cEVM left-pads the 20-byte address (accountFromAddress), dchain left-pads its 16-byte wire user. 32 bytes is birthday-safe at 2^128 and injective for every real identity. matcherHint renders the full 32 account bytes as the matcher's User/UserID string — the hint is ONLY a matching collision handle, never authoritative for value (settlement resolves the taker from the explicit AccountID and the maker from the orderuser: row). Replaces the trimTrailingZeros round-trip (no longer needed: settlement never reads the hint back).
This commit is contained in:
+31
-14
@@ -33,7 +33,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/database"
|
||||
"github.com/luxfi/dex/pkg/zapwire"
|
||||
)
|
||||
|
||||
// Store is the key/value surface dexcore reads and writes its state over. It is
|
||||
@@ -54,24 +53,42 @@ type Store interface {
|
||||
// drain fix on the asset axis).
|
||||
type AssetID = [32]byte
|
||||
|
||||
// AccountID is the FULL 16-byte injective wire identity the ledger keys an
|
||||
// account by — NEVER a folded 8-byte handle. Keeping the account at full width
|
||||
// makes two addresses sharing a leading 8-byte prefix DISTINCT accounts so
|
||||
// neither can withdraw the other's balance (the cross-user native-drain fix on
|
||||
// the account axis). It is byte-identical to the wire user[16] field.
|
||||
type AccountID = [zapwire.UserSize]byte
|
||||
// AccountID is the FULL 32-byte injective settlement identity the ledger keys an
|
||||
// account by — the AccountID32 width both homes map their native identity into
|
||||
// WITHOUT a lossy fold:
|
||||
//
|
||||
// - the cEVM home left-pads the 20-byte EVM address into 32 bytes (12 zero bytes
|
||||
// + the 20 address bytes), so two distinct addresses are ALWAYS distinct
|
||||
// accounts — no address pair can collide and drain each other's resting-order
|
||||
// proceeds (the cross-user-drain guard, made injective for 20-byte addresses
|
||||
// that a 16-byte identity could alias);
|
||||
// - the dchain home maps its 16-byte wire user into the same 32-byte space.
|
||||
//
|
||||
// 32 bytes is birthday-safe at 2^128 and injective for every real identity. It is
|
||||
// NEVER the matcher's 8-byte STP handle (which may collide and is purely a matching
|
||||
// hint); settlement resolves the taker from the explicit AccountID and the maker
|
||||
// from the orderuser: row, both full-width.
|
||||
type AccountID = [32]byte
|
||||
|
||||
// AccountFromString folds a user-identity string to the canonical 16-byte ledger
|
||||
// identity: the leading 16 bytes, zero-padded — byte-identical to the wire's
|
||||
// user[16] null-padding so the account the ledger credits is the SAME account the
|
||||
// orders/deposits name. This is the ONE fold from a user string to the settlement
|
||||
// identity; there is no 8-byte fold anywhere on the value path.
|
||||
func AccountFromString(user string) AccountID {
|
||||
// AccountFromBytes maps a raw identity (an EVM address, a wire user) to the
|
||||
// canonical 32-byte account by left-padding into the low bytes — injective for any
|
||||
// identity up to 32 bytes. This is the ONE map from a native identity to the
|
||||
// settlement identity; there is no lossy fold anywhere on the value path.
|
||||
func AccountFromBytes(id []byte) AccountID {
|
||||
var k AccountID
|
||||
copy(k[:], user)
|
||||
if len(id) > 32 {
|
||||
id = id[len(id)-32:]
|
||||
}
|
||||
copy(k[32-len(id):], id)
|
||||
return k
|
||||
}
|
||||
|
||||
// AccountFromString maps a user-identity string to the canonical 32-byte account
|
||||
// (left-padded). Used by the dchain home where the identity arrives as a string.
|
||||
func AccountFromString(user string) AccountID {
|
||||
return AccountFromBytes([]byte(user))
|
||||
}
|
||||
|
||||
// KEY SCHEMA — the durable ledger rows. Identical in both homes; the precompile
|
||||
// maps each []byte key to a keccak-derived 0x9999 storage slot, dchain writes it
|
||||
// to versiondb directly. BOTH halves of a balance key are full injective width.
|
||||
|
||||
@@ -77,8 +77,8 @@ func PlaceOrder(db Store, poolID [32]byte, maker AccountID, side lx.Side, price,
|
||||
Price: price,
|
||||
Size: size,
|
||||
SizeUnits: new(big.Int).SetUint64(SizeToUnits(size)),
|
||||
User: string(trimTrailingZeros(maker[:])),
|
||||
UserID: string(trimTrailingZeros(maker[:])),
|
||||
User: matcherHint(maker),
|
||||
UserID: matcherHint(maker),
|
||||
Symbol: symbol,
|
||||
Timestamp: ts,
|
||||
}
|
||||
|
||||
+10
-13
@@ -164,8 +164,8 @@ func buildMarketableOrder(req SwapRequest, remainingIn uint64) *lx.Order {
|
||||
order := &lx.Order{
|
||||
ID: req.OrderID,
|
||||
Side: req.Side,
|
||||
User: string(trimTrailingZeros(req.TakerUser[:])),
|
||||
UserID: string(trimTrailingZeros(req.TakerUser[:])),
|
||||
User: matcherHint(req.TakerUser),
|
||||
UserID: matcherHint(req.TakerUser),
|
||||
Symbol: PoolSymbol(req.PoolID),
|
||||
Timestamp: time.Unix(0, req.TimestampN),
|
||||
}
|
||||
@@ -256,14 +256,11 @@ func persistTouchedMakers(db Store, poolID [32]byte, book *lx.OrderBook, fills [
|
||||
return nil
|
||||
}
|
||||
|
||||
// trimTrailingZeros returns b with trailing NUL bytes removed (the inverse of the
|
||||
// wire's user[16] null-padding), so the matcher's user string round-trips to the
|
||||
// same 16-byte AccountID via AccountFromString.
|
||||
func trimTrailingZeros(b []byte) []byte {
|
||||
for i := len(b) - 1; i >= 0; i-- {
|
||||
if b[i] != 0 {
|
||||
return b[:i+1]
|
||||
}
|
||||
}
|
||||
return b[:0]
|
||||
}
|
||||
// matcherHint renders an AccountID to the matcher's User/UserID string. This string
|
||||
// is ONLY a matching hint: the matcher derives its 8-byte STP collision handle from
|
||||
// it, and that handle is NEVER authoritative for value (settlement resolves the taker
|
||||
// from the explicit AccountID and the maker from the orderuser: row). So the hint
|
||||
// only needs to be a deterministic, injective-enough function of the account — the
|
||||
// raw 32 account bytes as a string serve exactly that, with no lossy round-trip
|
||||
// concern because settlement never reads it back.
|
||||
func matcherHint(a AccountID) string { return string(a[:]) }
|
||||
|
||||
Reference in New Issue
Block a user