Files
precompile/dex/dchain_client.go
zeekay 1362566d34 Merge github/main into the hardening branch
Both sides carried work. Ours is the security pass: the stableswap amp=0
and empty-pool halts, the wei-per-swap leak, uint256 truncation in math,
three verifiers that returned a verdict without verifying, the cgo/non-cgo
consensus split in cggmp21, and the starkfri soundness floor. Theirs is
the fhe parameter set, the mldsa crafted-length refusal, and the naming
sweep.

Resolved per hunk rather than by side. Where both fixed one defect the
better fix survives alone, never two guards for one bug.
2026-08-28 20:13:25 -07:00

149 lines
6.7 KiB
Go

// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package dex
import (
"context"
"math/big"
"github.com/luxfi/geth/common"
)
// DChainClient is the precompile's handle to the node-LOCAL D-Chain (dexvm).
//
// THE MODEL (decomplected — one concern, one name): D is a normal Lux chain
// running dexvm, validated by an authorized subset (the --dex-validator opt-in +
// NFT authz at the chain-create/validate level, NOT here). The LP-9010 precompile
// is ONLY an ON-RAMP from the C-Chain to that D-Chain: it SUBMITS DEX operations
// to the local D-Chain or VERIFIES committed D receipts. It is NOT a matcher and
// NOT a backend selector. The matcher is the dexvm the validators run.
//
// This interface is the canonical seam that surface migrates onto. It is the
// Stage-1 target (see the package design): the synchronous V4 swap path is moved
// off the live-query Engine surface and onto Export (Pattern A: a
// C tx records a D-Chain order; the local dexvm executes it under D
// consensus; the result settles asynchronously through the D->C atomic boundary)
// or GetReceipt (Pattern B: a C tx carries a committed D receipt that 0x9010
// deterministically verifies). Both are consensus-safe; a SYNCHRONOUS in-block
// fill via a live query to a separate chain's moving book is NOT (it forks: two
// validators executing the same C block at different instants observe different
// fills => divergent StateRoot).
//
// CONSENSUS-SAFETY RULE: an implementation MUST NOT make C-Chain consensus state
// depend on a live query whose result is not already committed/certified. The
// transport is loopback to the co-located dexvm (the dexvm is RPC-served; the
// precompile is a leaf library that cannot — and must not — reach up into the
// node chain manager). A remote / standalone venue is never a valid target.
type DChainClient interface {
// SubmitOrder records a resting (maker) order on the local D-Chain
// and returns its order id. The order rests under D consensus; it is not
// matched synchronously here.
SubmitOrder(ctx context.Context, o Order) (orderID uint64, err error)
// Export records a marketable (taker) swap ORDER on the local
// D-Chain (Pattern A) and returns a receipt id the caller polls / observes
// for the asynchronous settlement. It does NOT return an in-block fill.
Export(ctx context.Context, in SwapOrder) (receiptID [32]byte, err error)
// GetMarket returns the committed market (order book) metadata for marketID,
// a read of already-committed D state (safe to fold into a C read path).
GetMarket(ctx context.Context, marketID [32]byte) (Market, error)
// GetReceipt returns a committed D receipt for a prior order (Pattern B:
// the C side deterministically verifies it before applying the C effect).
GetReceipt(ctx context.Context, receiptID [32]byte) (Receipt, error)
}
// Order is a resting (maker) limit-order submitted to the local D-Chain.
// Identity (maker) is bound by the C tx caller; size/price are the V4-derived
// resting parameters. (Field set intentionally minimal for the Stage-1 seam; the
// frozen wire frame lives in engine_zap.go.)
type Order struct {
MarketID [32]byte
Maker common.Address
Side uint8
Price *big.Int
Size *big.Int
}
// SwapOrder is a marketable (taker) swap order submitted to the local D-Chain.
// The taker is the C tx caller; the D-Chain settles the realized fills through
// the atomic D->C boundary, NOT into C StateDB synchronously.
type SwapOrder struct {
MarketID [32]byte
Taker common.Address
Params SwapParams
}
// Market is committed market metadata read back from the local D-Chain.
type Market struct {
MarketID [32]byte
Base Currency
Quote Currency
Initialized bool
}
// Receipt is a committed D receipt for an order (Pattern B verification target).
// Fills are the D-Chain's confirmed executions; the reserved Sig is the d-chain
// fill-attestation (P3Q -> starkfri) that makes verification trustless.
type Receipt struct {
ReceiptID [32]byte
Delta BalanceDelta
Sig []byte
}
// dchainUnavailable is the precompile's client when the node-LOCAL D-Chain is not
// reachable. It performs NO matching and holds NO state: every operation returns
// ErrDChainUnavailable so that a node which serves the LP-9010 path but is not
// running its local dexvm D-Chain reverts cleanly — it never fabricates a fill
// and never masks the absent chain as a missing pool.
//
// This is NOT a "backend mode" and NOT a selectable engine. It is the honest
// default for a precompile whose backing chain is absent: the local D-Chain MUST
// be running for 0x9010 to serve DEX operations; until it is, the on-ramp is
// closed. It implements the transitional Engine surface (the live surface the
// PoolManager calls today) so the precompile remains a clean revert.
//
// Contract: never panics, never moves value, never fabricates a fill.
type dchainUnavailable struct{}
// Compile-time assertion: dchainUnavailable satisfies the live Engine surface. It
// does NOT implement custodyEngine / poolRouter / cancelAuthority — there is no
// local D-Chain to route to or fund against.
var _ Engine = (*dchainUnavailable)(nil)
// newDChainUnavailable constructs the no-local-D-Chain client.
func newDChainUnavailable() *dchainUnavailable { return &dchainUnavailable{} }
// Brand identifies the closed-on-ramp state in boot logs / error wrapping.
func (dchainUnavailable) Brand() string { return "DEX (local D-Chain unavailable)" }
// Initialize refuses: no local D-Chain means no market can be created.
func (dchainUnavailable) Initialize(_ *big.Int) (int24, error) {
return 0, ErrDChainUnavailable
}
// Swap refuses: there is no local D-Chain to submit a marketable order to.
func (dchainUnavailable) Swap(_ *PoolState, _ common.Address, _ SwapParams) (BalanceDelta, error) {
return ZeroBalanceDelta(), ErrDChainUnavailable
}
// ModifyLiquidity refuses: there is no local D-Chain book to rest or pull on.
func (dchainUnavailable) ModifyLiquidity(_ *PoolState, _ common.Address, _ ModifyLiquidityParams) (BalanceDelta, BalanceDelta, error) {
return ZeroBalanceDelta(), ZeroBalanceDelta(), ErrDChainUnavailable
}
// Donate refuses: a CLOB has no shared pool to gift into, and no local D-Chain
// is reachable regardless.
func (dchainUnavailable) Donate(_ *PoolState, _, _ *big.Int) (BalanceDelta, error) {
return ZeroBalanceDelta(), ErrDChainUnavailable
}
// Quote returns zero: with no local D-Chain there is no book to read a price
// from. Quote is a read-only estimate, so it returns a benign zero rather than
// an error (the caller treats zero as "no route"), keeping router fallbacks sane.
func (dchainUnavailable) Quote(_ *Pool, _ *big.Int, _ bool) *big.Int {
return big.NewInt(0)
}