mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
wip: drop order/slippage code the funding rail no longer reaches
Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
+2
-94
@@ -5,7 +5,6 @@ package dex
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"math/big"
|
||||
|
||||
"github.com/luxfi/geth/common"
|
||||
@@ -101,26 +100,7 @@ const (
|
||||
GasNativeSettlement uint64 = 50_000 // decode + SM get/bind + replay slot + credit + SM remove + DEXFill log (Phase B).
|
||||
)
|
||||
|
||||
// maxOrderTTL is the FINITE reclaim horizon applied to any value-path swap order that
|
||||
// did NOT name an explicit deadline (a plain swap(), or a DI01 body with deadline 0). It
|
||||
// bounds how long a taker's locked principal can sit unsettled before reclaimIntent can
|
||||
// refund it. Seven days (in seconds) is generous enough for any honest cross-domain
|
||||
// settlement to land yet guarantees the principal is never permanently stranded — the
|
||||
// structural fix for the deadline-0 fund-lock. A taker who wants a tighter window passes an
|
||||
// explicit DI01 deadline; this is only the floor for those who pass none.
|
||||
const maxOrderTTL uint64 = 7 * 24 * 60 * 60 // 604800s = 7 days
|
||||
|
||||
// defaultedReclaimDeadline returns the finite deadline to stamp on an order that supplied
|
||||
// none: blockTimestamp + maxOrderTTL, saturating at the uint64 max so the addition can
|
||||
// never wrap to a SMALL (already-past) value (which would let reclaim fire immediately, or —
|
||||
// worse — a wrapped tiny deadline could let a Phase-B settlement be rejected as "late" right
|
||||
// away). Saturation keeps the horizon in the future for every realistic block time.
|
||||
func defaultedReclaimDeadline(blockTimestamp uint64) uint64 {
|
||||
if blockTimestamp > math.MaxUint64-maxOrderTTL {
|
||||
return math.MaxUint64
|
||||
}
|
||||
return blockTimestamp + maxOrderTTL
|
||||
}
|
||||
|
||||
// isNativeAsset reports whether an injective AssetID is native LUX (all-zero).
|
||||
func isNativeAsset(id [32]byte) bool { return id == ([32]byte{}) }
|
||||
@@ -290,73 +270,10 @@ func buildTransfer(key PoolKey, params SwapParams, caller common.Address) (Trans
|
||||
}, nil
|
||||
}
|
||||
|
||||
// priceScale is the fixed-point scale of the CLOB price wire (price × 1e8) — the
|
||||
// matcher's PriceInt grid, byte-identical with dex/pkg/zapwire.PriceScale (==
|
||||
// lx.PriceMultiplier) and the chains/dexvm proxy's PriceScale. Quote-per-base limits
|
||||
// and Fill prices cross the C<->D seam as EXACT integers on this grid; there is NO
|
||||
// float64 on the value path (its out-of-range int conversion is architecture-
|
||||
// dependent and forks — the cross-arch consensus bug the integer wire closes).
|
||||
const priceScale = 100000000
|
||||
|
||||
// priceLimitToCLOB converts the V4 SqrtPriceLimitX96 into the CLOB price domain the
|
||||
// dexvm settles in — quote-per-base as a uint64 FIXED-POINT ×priceScale value (the
|
||||
// PriceInt grid), computed as an EXACT integer so no float→int conversion ever
|
||||
// touches the value the settle path compares against. It is the slippage floor the
|
||||
// matcher / settle path enforces: a fill WORSE than this is refused (bounded MEV).
|
||||
//
|
||||
// price (quote/base) = (sqrtPriceX96 / 2^96)^2 = currency1 per currency0,
|
||||
// grid value = round(price × priceScale) = round(s^2 × priceScale / 2^192).
|
||||
//
|
||||
// Round-to-NEAREST (not floor): the client's sqrtPriceLimitX96 is a floored sqrt, so its
|
||||
// exact square sits an epsilon below the intended price; flooring the grid value would
|
||||
// quantize a "50" limit to 49.99999999 and fail to cross a maker resting AT 50. Rounding
|
||||
// to the nearest PriceInt quantizes to the price the taker meant, deterministically.
|
||||
//
|
||||
// DIRECTION. limitIsUpper reports which side the limit bounds, matching the dexvm's
|
||||
// fill-side convention:
|
||||
// - zeroForOne (sell currency0/base for currency1/quote => CLOB SELL): the V4 limit is
|
||||
// the MINIMUM sqrt price, i.e. a price FLOOR — reject fills BELOW it (limitIsUpper=false).
|
||||
// - !zeroForOne (buy currency0/base with currency1/quote => CLOB BUY): the V4 limit is
|
||||
// the MAXIMUM sqrt price, i.e. a price CEILING — reject fills ABOVE it (limitIsUpper=true).
|
||||
//
|
||||
// A SqrtPriceLimitX96 at/beyond the V4 sentinel bounds (MinSqrtRatio for zeroForOne,
|
||||
// MaxSqrtRatio for !zeroForOne) means "no limit" — returns 0 so the settle path imposes
|
||||
// no floor (the pre-existing unbounded behavior, preserved).
|
||||
func priceLimitToCLOB(params SwapParams) (priceLimitUnits uint64, limitIsUpper bool) {
|
||||
limitIsUpper = !params.ZeroForOne
|
||||
s := params.SqrtPriceLimitX96
|
||||
if s == nil || s.Sign() <= 0 {
|
||||
return 0, limitIsUpper // unset => no limit
|
||||
}
|
||||
// Treat the V4 "no limit" sentinels as unbounded: a zeroForOne floor at/below
|
||||
// MinSqrtRatio, or a !zeroForOne ceiling at/above MaxSqrtRatio, imposes nothing.
|
||||
if params.ZeroForOne {
|
||||
if s.Cmp(MinSqrtRatio) <= 0 {
|
||||
return 0, limitIsUpper
|
||||
}
|
||||
} else {
|
||||
if s.Cmp(MaxSqrtRatio) >= 0 {
|
||||
return 0, limitIsUpper
|
||||
}
|
||||
}
|
||||
// grid = round(s^2 × priceScale / 2^192), EXACT big.Int arithmetic (add 2^191 before
|
||||
// the >>192 floor-divide = round-half-up). The matcher's crossing loop may project this
|
||||
// back to a float for display, but the CONSENSUS value the settle floor compares against
|
||||
// is this exact PriceInt grid integer.
|
||||
units := new(big.Int).Mul(s, s)
|
||||
units.Mul(units, big.NewInt(priceScale))
|
||||
units.Add(units, new(big.Int).Lsh(big.NewInt(1), 191)) // + 2^191 for round-to-nearest
|
||||
units.Rsh(units, 192)
|
||||
if units.Sign() <= 0 || !units.IsUint64() {
|
||||
return 0, limitIsUpper // degenerate / out of range => no limit (unbounded)
|
||||
}
|
||||
return units.Uint64(), limitIsUpper
|
||||
}
|
||||
|
||||
|
||||
// balanceDeltaForOutput maps a credited output amount to the V4 BalanceDelta
|
||||
// convention (output paid out to taker = negative on the output side; the input
|
||||
// side already moved at Phase A so it is zero on the settlement leg).
|
||||
// convention (output paid out to caller = negative on the output side; the input
|
||||
// side already moved when the value crossed, so it is zero on the import leg).
|
||||
func balanceDeltaForOutput(params SwapParams, amountOut *big.Int) BalanceDelta {
|
||||
out := new(big.Int).Neg(amountOut)
|
||||
if params.ZeroForOne {
|
||||
@@ -366,15 +283,6 @@ func balanceDeltaForOutput(params SwapParams, amountOut *big.Int) BalanceDelta {
|
||||
return BalanceDelta{Amount0: out, Amount1: big.NewInt(0)}
|
||||
}
|
||||
|
||||
// minAmountOut derives the swap's slippage floor from V4 SwapParams (exact-output
|
||||
// names the floor; exact-input leaves it to the D price limit). Routing only.
|
||||
func minAmountOut(params SwapParams) *big.Int {
|
||||
if params.AmountSpecified != nil && params.AmountSpecified.Sign() > 0 {
|
||||
return new(big.Int).Set(params.AmountSpecified)
|
||||
}
|
||||
return big.NewInt(0)
|
||||
}
|
||||
|
||||
// swapAssetDirection returns the (tokenIn, tokenOut) injective AssetIDs implied by
|
||||
// the pool key and swap direction. ZeroForOne true => in=currency0, out=currency1.
|
||||
func swapAssetDirection(key PoolKey, params SwapParams) (in, out [32]byte) {
|
||||
|
||||
@@ -5,9 +5,7 @@ package dex
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/luxfi/geth/common"
|
||||
"github.com/luxfi/ids"
|
||||
@@ -180,68 +178,4 @@ func EncodeSettlementHookData(claimID ids.ID, object []byte) []byte {
|
||||
return out
|
||||
}
|
||||
|
||||
// orderBodyLen is the Phase-A body width with a deadline only: deadline(32).
|
||||
// orderBodyLenWithNonce adds the nonce word: deadline(32) | nonce(32). An empty body
|
||||
// (the common plain-swap case) means deadline 0, nonce 0.
|
||||
const (
|
||||
orderBodyLen = 32
|
||||
orderBodyLenWithNonce = 64
|
||||
)
|
||||
|
||||
// decodeOrderBody parses the OPTIONAL Phase-A order body into (deadline, nonce). An
|
||||
// empty body => (0, 0) — the plain-swap default. A 32-byte body carries only a deadline
|
||||
// (nonce 0). A 64-byte body carries deadline + nonce. Any other width, or a value that
|
||||
// does not fit uint64, is malformed (a swap that carries garbage where a deadline/nonce
|
||||
// should be reverts rather than silently dropping it and deriving a mismatched id /
|
||||
// stranding funds with no reclaim horizon).
|
||||
func decodeOrderBody(body []byte) (deadline, nonce uint64, err error) {
|
||||
switch len(body) {
|
||||
case 0:
|
||||
return 0, 0, nil
|
||||
case orderBodyLen:
|
||||
d := new(big.Int).SetBytes(body[0:32])
|
||||
if !d.IsUint64() {
|
||||
return 0, 0, ErrOrderBadDeadline
|
||||
}
|
||||
return d.Uint64(), 0, nil
|
||||
case orderBodyLenWithNonce:
|
||||
d := new(big.Int).SetBytes(body[0:32])
|
||||
n := new(big.Int).SetBytes(body[32:64])
|
||||
if !d.IsUint64() || !n.IsUint64() {
|
||||
return 0, 0, ErrOrderBadDeadline
|
||||
}
|
||||
return d.Uint64(), n.Uint64(), nil
|
||||
default:
|
||||
return 0, 0, ErrOrderBodyMalformed
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeOrderHookData builds an explicit Phase-A hookData: the tag, plus deadline and
|
||||
// nonce words. A plain empty hookData also selects Phase A with deadline 0, nonce 0.
|
||||
// The encoding is MINIMAL-WIDTH so the off-chain (zap/dexsession) and on-chain encoders
|
||||
// produce byte-identical calldata for the same (deadline, nonce):
|
||||
// - deadline==0 && nonce==0 -> tag only (4 bytes)
|
||||
// - nonce==0 -> tag | deadline[32]
|
||||
// - else -> tag | deadline[32] | nonce[32]
|
||||
func EncodeOrderHookData(deadline, nonce uint64) []byte {
|
||||
if deadline == 0 && nonce == 0 {
|
||||
out := make([]byte, 4)
|
||||
copy(out, orderPhaseTag[:])
|
||||
return out
|
||||
}
|
||||
var d [32]byte
|
||||
binary.BigEndian.PutUint64(d[24:32], deadline)
|
||||
if nonce == 0 {
|
||||
out := make([]byte, 0, 4+orderBodyLen)
|
||||
out = append(out, orderPhaseTag[:]...)
|
||||
out = append(out, d[:]...)
|
||||
return out
|
||||
}
|
||||
var n [32]byte
|
||||
binary.BigEndian.PutUint64(n[24:32], nonce)
|
||||
out := make([]byte, 0, 4+orderBodyLenWithNonce)
|
||||
out = append(out, orderPhaseTag[:]...)
|
||||
out = append(out, d[:]...)
|
||||
out = append(out, n[:]...)
|
||||
return out
|
||||
}
|
||||
|
||||
+8
-10
@@ -95,7 +95,7 @@ func (s *SettleContract) runSeedSeamReserve(
|
||||
defer exitCustodyKV(stateDB)
|
||||
|
||||
aid := assetID(asset)
|
||||
// PHASE A (record) credits seamReserve BEFORE the terminal ERC-20 pull, so the seed's
|
||||
// PHASE A (record) credits custody BEFORE the terminal ERC-20 pull, so the seed's
|
||||
// accounting is never dropped by the geth nested-call host bug.
|
||||
delivered, derr := receiveOperatorValue(stateDB, caller, asset, aid, amount, func(amt *big.Int) error {
|
||||
storeCustody(stateDB, aid, new(big.Int).Add(loadCustody(stateDB, aid), amt))
|
||||
@@ -110,8 +110,8 @@ func (s *SettleContract) runSeedSeamReserve(
|
||||
}
|
||||
|
||||
// runCreditPositionFee credits an LP position's earned fees: it receives operator
|
||||
// value into the vault, adds it to committedPositions[asset] (the LP-rail pot backing
|
||||
// the collect), raises the NAMED position record's LockedAmt and the owner's per-asset
|
||||
// value into the vault, adds it to custody[asset] (the pot backing the collect),
|
||||
// raises the NAMED position record's LockedAmt and the owner's per-asset
|
||||
// committed reserve by the same amount. So a maker's withdrawable rises with the fees
|
||||
// the D-Chain credited them, the per-owner committed bound stays exact (a collect can
|
||||
// still only pull up to THIS owner's record), and conservation holds (the fee credit
|
||||
@@ -160,8 +160,7 @@ func (s *SettleContract) runCreditPositionFee(
|
||||
return nil, gasLeft, ErrFeeNoPosition // fees credit the position's own locked asset.
|
||||
}
|
||||
// PHASE A (record): back the collect BEFORE the terminal ERC-20 pull — the pot, the
|
||||
// record's withdrawable, and the owner reserve all rise by the credited fee, keeping
|
||||
// committedPositions == Σ live records' LockedAmt and the per-owner bound exact. Recording
|
||||
// record's withdrawable, and the owner reserve all rise by the credited fee. Recording
|
||||
// before the transfer is the geth nested-call fund-loss fix (the prior order dropped these
|
||||
// writes for an ERC-20 fee credit).
|
||||
delivered, derr := receiveOperatorValue(stateDB, caller, asset, aid, amount, func(amt *big.Int) error {
|
||||
@@ -184,7 +183,7 @@ func (s *SettleContract) runCreditPositionFee(
|
||||
// the target pot via `record` using the NATIVE-ZAP two-phase discipline (the geth nested-call
|
||||
// fund-loss fix): the pot write (Phase A, via `record`) happens BEFORE the ERC-20 transferFrom
|
||||
// (Phase B, terminal), so the host bug cannot drop the seed's accounting (which it did — the
|
||||
// ERC-20 seed moved the token but lost the seamReserve/committedPositions credit => a stranded,
|
||||
// ERC-20 seed moved the token but lost the custody credit => a stranded,
|
||||
// liveness-ineffective seed).
|
||||
//
|
||||
// - NATIVE: observed-delta read (the host frame already moved msg.value into 0x9999; delivered
|
||||
@@ -200,13 +199,12 @@ func receiveOperatorValue(stateDB StateDB, caller common.Address, asset Currency
|
||||
if _, of := uint256.FromBig(amount); of {
|
||||
return nil, ErrSeedBadAmount
|
||||
}
|
||||
// delivered = realBal − (settleVault + seamReserve + committedPositions): the native
|
||||
// this call carried, since every native-moving path keeps the three pots in lock-step
|
||||
// with the vault's real native balance.
|
||||
// delivered = realBal − (settleVault + custody): the native this call carried,
|
||||
// since every native-moving path keeps the pots in lock-step with the vault's
|
||||
// real native balance.
|
||||
realBal := stateDB.GetBalance(poolManagerAddr9999).ToBig()
|
||||
tracked := new(big.Int).Set(loadSettleVault(stateDB, aid))
|
||||
tracked.Add(tracked, loadCustody(stateDB, aid))
|
||||
tracked.Add(tracked, loadCustody(stateDB, aid))
|
||||
delivered := new(big.Int).Sub(realBal, tracked)
|
||||
if delivered.Sign() < 0 || delivered.Cmp(amount) < 0 {
|
||||
return nil, ErrSeedUndelivered
|
||||
|
||||
Reference in New Issue
Block a user