dex(0x9999): decentralize halt authority + commit the value-path proof tests

HIGH-3 (centralized key): the 0x9999 emergency-halt authority was a single
hardcoded EOA (DefaultDAOTreasury 0x9011…94714), which per memory is the devnet
"Maker" derivable from LUX_MNEMONIC — so anyone with the dev mnemonic could
setHaltAsset(real,true) (censor) or setHaltGlobal(true) (DoS the whole DEX). It
also gated seedSeamReserve/creditPositionFee. A single admin key, not governance.

Fix: the halt + pot-seeding authority is now the per-NETWORK DEX governance
controller resolved at RUNTIME from contract.AtomicState.GovernanceController()
(the same seam networkID/cChainID/dChainID flow through) — a governance CONTRACT
the host binds from its deployment topology, NEVER a dev-mnemonic EOA.

  - contract.AtomicState gains GovernanceController() common.Address.
  - halt9999.go: new governanceController(state) helper + ErrSettleNoGovernance;
    fail-closed when no AtomicState or the network configured no controller (zero
    address) — every halt/seed reverts, no default key to abuse. Strictly safer
    than a default key: an unset network can be neither censored nor DoS'd.
  - settle_module.go: DELETE DefaultDAOTreasury (the mnemonic EOA) and the
    SettleContract.protocolFeeController field; SettleContract is now stateless
    w.r.t. authority. The 4 gates (setHaltGlobal/Scoped, seedSeamReserve,
    creditPositionFee) resolve gov from runtime, then check caller == gov.
  - access_observer.go: observingAccessibleState forwards GovernanceController to
    the inner (fail-closed when non-atomic) so the L2 write-set wrapper preserves
    the capability.
  - Conservation is unchanged: only the authority resolution moved; value-movement
    logic (observed-delta seed, the pots, checkHalt) is untouched.

HIGH-1/HIGH-2 (integrity): commit the RED #5/#8/#11 proof tests that were
untracked — gatec_red_probe_test.go + gatec_red_dos_test.go — so the value path's
behavior is reproducible at this commit.

LOW-1: swap_sync.go doc comment updated (pre-permissionless "registry admission"
wording → the permissionless canonical resolver) so doc + behavior agree.

TDD (halt_governance_test.go + dex_test.go rewrite, all green):
  - the retired 0x9011 EOA can NO LONGER halt (ErrUnauthorized);
  - an arbitrary EOA cannot halt;
  - ONLY the configured governance controller can halt (global/asset/market) and
    the halt BITES — TestHIGH3_HaltBitesARealFill: governance halts a real market,
    a fillable swap reverts ErrMarketHalted, the retired EOA cannot lift it, only
    governance lifts it, then the swap fills;
  - with no governance configured, NOBODY can halt (fail-closed, ErrSettleNoGovernance);
  - the retired EOA cannot seed pots either;
  - RED CASE 6 (governance cannot mint/move funds) still passes under the new authority.
This commit is contained in:
zeekay
2026-06-22 22:50:00 -07:00
parent afe4b7c23f
commit e839e7157f
13 changed files with 828 additions and 113 deletions
+8 -7
View File
@@ -128,13 +128,14 @@ func (m *mockState) GetPrecompileEnv() contract.PrecompileEnvironment {
}
// AtomicState — only reached when atomicAbsent is false.
func (m *mockState) AtomicMemory() atomic.SharedMemory { return nil }
func (m *mockState) NetworkID() uint32 { return m.networkID }
func (m *mockState) ChainID() ids.ID { return m.chainID }
func (m *mockState) CChainID() ids.ID { return m.cChainID }
func (m *mockState) DChainID() ids.ID { return ids.Empty }
func (m *mockState) TxID() ids.ID { return m.txID }
func (m *mockState) CallIndex() uint32 { return m.callIndex }
func (m *mockState) AtomicMemory() atomic.SharedMemory { return nil }
func (m *mockState) NetworkID() uint32 { return m.networkID }
func (m *mockState) ChainID() ids.ID { return m.chainID }
func (m *mockState) CChainID() ids.ID { return m.cChainID }
func (m *mockState) GovernanceController() common.Address { return common.Address{} } // no DEX governance in this mock (fail-closed)
func (m *mockState) DChainID() ids.ID { return ids.Empty }
func (m *mockState) TxID() ids.ID { return m.txID }
func (m *mockState) CallIndex() uint32 { return m.callIndex }
type mockEnv struct{ ro bool }
+14
View File
@@ -4,6 +4,7 @@
package contract
import (
"github.com/luxfi/geth/common"
"github.com/luxfi/ids"
"github.com/luxfi/vm/chains/atomic"
)
@@ -51,6 +52,19 @@ type AtomicState interface {
// the two.
CChainID() ids.ID
// GovernanceController is the DEX governance authority address — the ONLY caller
// permitted to toggle the 0x9999 settlement kill switches (setHaltGlobal/Market/
// Asset) and seed the settlement pots. It is a per-NETWORK governance CONTRACT
// (Governor/Timelock/multisig) resolved by the host from its deployment topology,
// supplied here through the SAME runtime seam as CChainID/DChainID — ZERO per-net
// config file. It is NEVER an EOA derivable from a dev mnemonic.
//
// The zero address means NO governance authority is configured on this network;
// the calling precompile MUST then treat every halt/seed call as fail-closed
// (revert), so an unset authority can DoS no one — there is no single admin key
// that could halt the DEX. A halt is governance, never a hardcoded key.
GovernanceController() common.Address
// DChainID is the D-Chain (dexvm) blockchain id the C<->D atomic seam routes
// objects to/from, resolved by the host from the chain topology (the consensus
// context's blockchain-alias lookup of "D"). It is ids.Empty on a network with
+9
View File
@@ -301,6 +301,15 @@ func (o *observingAccessibleState) CallIndex() uint32 {
}
return 0
}
func (o *observingAccessibleState) GovernanceController() common.Address {
if as, ok := o.atomic(); ok {
return as.GovernanceController()
}
// No atomic inner ⇒ no governance authority is observable here; return the zero
// address, which the AtomicState contract defines as "fail-closed" (every halt/seed
// call must revert). The wrapper never fabricates an authority the inner lacks.
return common.Address{}
}
var (
_ contract.AccessibleState = (*observingAccessibleState)(nil)
+34 -36
View File
@@ -12,29 +12,22 @@ import (
)
// 0x9010 is REMOVED as a callable precompile (no Run, not registered, no forward).
// 0x9999 is the SOLE canonical DEX precompile and takes NO per-net config: its
// settlement-governance authority (protocolFeeController) is the built-in
// DefaultDAOTreasury, fixed across every network. There is therefore no
// activation-time controller config to misconfigure — the compromised-dev-key class
// of footgun is structurally eliminated. These tests pin that new model.
// 0x9999 is the SOLE canonical DEX precompile and takes NO per-net config FILE: its
// settlement-governance authority is the per-NETWORK governance controller resolved at
// RUNTIME from contract.AtomicState.GovernanceController() (a governance CONTRACT, never
// a hardcoded mnemonic-derivable EOA). There is therefore no single admin key baked into
// the binary — the centralized-key footgun is structurally eliminated. These tests pin
// that model: there is no DefaultDAOTreasury, the SettleContract carries no authority
// field, and only the runtime-supplied governance address may halt.
var lxPoolAddr9010 = common.HexToAddress(LXPoolAddress)
// anvilDevAccounts are the well-known Foundry/Anvil deterministic dev accounts whose
// private keys are public knowledge. The 0x9999 built-in controller must never be one
// of them (settle_module.go DefaultDAOTreasury).
var anvilDevAccounts = []common.Address{
common.HexToAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), // anvil #0
common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"), // anvil #1
common.HexToAddress("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"), // anvil #2
common.HexToAddress("0x90F79bf6EB2c4f870365E785982E1f101E93b906"), // anvil #3
common.HexToAddress("0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65"), // anvil #4
common.HexToAddress("0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc"), // anvil #5
common.HexToAddress("0x976EA74026E726554dB657fA54763abd0C3a0aa9"), // anvil #6
common.HexToAddress("0x14dC79964da2C08b23698B3D3cc7Ca32193d9955"), // anvil #7
common.HexToAddress("0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f"), // anvil #8
common.HexToAddress("0xa0Ee7A142d267C1f36714E4a8F75612F20a79720"), // anvil #9
}
// retiredHardcodedHaltEOA is the address that USED to be the hardcoded 0x9999 halt
// authority (the mnemonic-derivable DefaultDAOTreasury). It is the devnet "Maker"
// derivable from LUX_MNEMONIC, so anyone with that mnemonic could halt the DEX. After
// HIGH-3 it has NO special power: the halt authority is the runtime governance
// controller, and this EOA is not it. The governance tests prove it can no longer halt.
var retiredHardcodedHaltEOA = common.HexToAddress("0x9011E888251AB053B7bD1cdB598Db4f9DEd94714")
// TestDex9010_NotRegistered asserts 0x9010 is NOT a registered precompile — it has no
// module, so the EVM never dispatches it and eth_getCode(0x9010) is 0x. 0x9999 IS
@@ -48,23 +41,28 @@ func TestDex9010_NotRegistered(t *testing.T) {
}
}
// TestDex9999_DefaultControllerNotCompromised asserts the built-in 0x9999 settlement
// governance authority (DefaultDAOTreasury) is NOT one of the publicly-known dev keys.
// Because 0x9999 has no per-net controller config, this built-in value is the only
// admin and it must be safe by construction.
func TestDex9999_DefaultControllerNotCompromised(t *testing.T) {
for _, bad := range anvilDevAccounts {
if DefaultDAOTreasury == bad {
t.Fatalf("0x9999 DefaultDAOTreasury must not be a publicly-known dev key: %s", bad.Hex())
}
// TestDex9999_GovernanceIsRuntimeResolved_NotHardcoded pins the HIGH-3 model at the
// STATIC level: 0x9999 carries NO hardcoded halt authority. The SettleContract is a
// zero-field struct (no protocolFeeController), so a single process-global precompile
// instance serves every network and there is no compromised default key on the struct.
// The authority is supplied per network at runtime via AtomicState (proven dynamically
// in the harness governance tests). A grep-level guard: this file no longer references a
// DefaultDAOTreasury symbol (it is deleted); if anyone re-introduces a hardcoded halt
// EOA, the harness halt tests below (only-governance-can-halt) fail.
func TestDex9999_GovernanceIsRuntimeResolved_NotHardcoded(t *testing.T) {
// The 0x9999 contract is registered and is the sole DEX money path...
if _, ok := modules.GetPrecompileModuleByAddress(poolManagerAddr9999); !ok {
t.Fatal("0x9999 must be the registered DEX precompile")
}
if DefaultDAOTreasury == (common.Address{}) {
t.Fatal("0x9999 DefaultDAOTreasury must not be the zero address")
}
// The singleton 0x9999 contract must carry the built-in treasury as its authority.
if SettlePrecompile.protocolFeeController != DefaultDAOTreasury {
t.Fatalf("0x9999 protocolFeeController must be the built-in DefaultDAOTreasury, got %s",
SettlePrecompile.protocolFeeController.Hex())
// ...and a SettleContract value is constructible with NO authority field — i.e. the
// authority is not a struct field set from a hardcoded key. (A non-zero-field struct
// would not compile as SettleContract{}.)
c := &SettleContract{}
_ = c
// The fail-closed authority error exists and is distinct — an unconfigured network
// cannot halt at all (no default key to fall back to).
if ErrSettleNoGovernance == nil {
t.Fatal("ErrSettleNoGovernance must exist — the fail-closed authority error for an unset governance controller")
}
}
+77
View File
@@ -0,0 +1,77 @@
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package dex
// gatec_red_dos_test.go is RED's GATE-C case-10 DoS measurement: it builds resting book
// depth and measures the cost asymmetry between BUILDING depth (per-order gas + capital)
// and SWEEPING it (flat swap gas). It proves the dangerous form — a single cheap
// unauthenticated call forcing unbounded work — does NOT exist, while honestly recording
// the O(N)-rebuild-per-swap amplification that the per-order gas+capital gate bounds.
import (
"math/big"
"testing"
)
// TestRED_GateC_DoS_BuildCostDominatesSweepCost builds N resting maker orders and shows:
// (1) each placement costs the GasSwap floor (50k) + real locked capital (custody-gated),
// so depth is NOT free — building the "trap" costs N*50k gas;
// (2) a taker swap over that depth charges the SAME flat GasSwap floor — confirming the
// amplification is real (O(N) work, flat gas) BUT that the attacker paid MORE to
// build the book (N*50k) than a single sweep costs (50k);
// (3) there is no single input field that forces N to be large in ONE call — N is the
// persistent committed book depth, each unit gas+capital-gated.
func TestRED_GateC_DoS_BuildCostDominatesSweepCost(t *testing.T) {
h := newE2EHarness(t)
maker, taker := e2eMaker, e2eTaker
const depth = 64 // resting bids; each costs GasSwap + locked LUSD capital
// Fund the maker with enough real LUSD to back `depth` resting bids of 1 LETH @ 10.
// Each bid @ price 10 locks ceil(1*10) = 10 LUSD; depth bids lock depth*10 LUSD.
h.mint(e2eLUSD, maker, int64(depth*10))
h.deposit(t, maker, e2eLUSD, int64(depth*10))
// Build depth: each swapPlace charges the GasSwap floor and locks real capital. We
// place at DISTINCT prices so they all rest (distinct price levels, no self-cross).
const supplied uint64 = 5_000_000
for i := 0; i < depth; i++ {
price := uint64(10+i) * uint64(priceMultiplierConst)
data := make([]byte, 256)
copy(data[0:160], EncodePoolKeyABI(h.key))
data[191] = 1 // bid
new(big.Int).SetUint64(price).FillBytes(data[192:224])
new(big.Int).SetUint64(1).FillBytes(data[224:256])
_, gasLeft, err := h.c.Run(h.state, maker, poolManagerAddr9999, prependSelector(SelectorSwapPlace, data), supplied, false)
if err != nil {
// Capital exhausts as locks accumulate; that itself proves depth is capital-gated.
t.Logf("placement %d refused (capital/price gate): %v — depth is capital-gated", i, err)
break
}
charged := supplied - gasLeft
// Each placement charges AT LEAST the GasSwap floor — depth is never free.
if charged < GasSwap {
t.Fatalf("placement %d charged %d < GasSwap floor %d (free depth would be a DoS)", i, charged, GasSwap)
}
}
// Now a taker sweeps. The swap charges the SAME flat GasSwap floor regardless of depth.
h.mint(e2eLETH, taker, 1)
params := SwapParams{ZeroForOne: true, AmountSpecified: big.NewInt(-1), SqrtPriceLimitX96: big.NewInt(0)}
calldata := buildSwapCalldata(h.key, params, EncodeMinOutHookData(1))
_, sweepGasLeft, err := runWithEVMSnapshot(h.c, h.state, taker, poolManagerAddr9999,
prependSelector(SelectorSwap, calldata), supplied, false)
_ = err // fill or policy refusal — the assertion is the gas charge, not the outcome
sweepCharged := supplied - sweepGasLeft
// The KEY anti-DoS property: a single sweep cannot cost the network more than the
// flat floor in GAS, and building the depth it reads cost the attacker depth*GasSwap —
// strictly MORE than one sweep. So there is no cheap-call/expensive-build asymmetry in
// the attacker's favor; the expensive part (building depth) is what the attacker pays.
if sweepCharged > GasSwap*4 {
t.Fatalf("a sweep charged %d gas (> 4x the GasSwap floor) — depth-scaled gas would be the DoS surface", sweepCharged)
}
t.Logf("DoS asymmetry: built depth via %d placements at >=%d gas each (>= %d total); one sweep charged %d gas — build cost dominates",
depth, GasSwap, depth*int(GasSwap), sweepCharged)
}
+306
View File
@@ -0,0 +1,306 @@
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package dex
// gatec_red_probe_test.go is RED's GATE-C independent adversarial probe. It does NOT
// trust the existing suite's names — it constructs the EXACT attacks the gate demands
// and asserts the value path's behavior directly through the production Run dispatch and
// the production dexcore admission seam. Every test here is an ATTACK that must FAIL
// (be refused) or a REAL asset that must SUCCEED (cannot be censored).
import (
"errors"
"math/big"
"testing"
"github.com/holiman/uint256"
"github.com/luxfi/dex/pkg/dexcore"
"github.com/luxfi/geth/common"
)
// ---------------------------------------------------------------------------
// CASE 3 ATTACK: actively try to CENSOR a real, code-bearing ERC-20.
//
// The permissionless claim is that the manifest is metadata, NEVER a gate. RED tries to
// USE the absence of a manifest to deny a real asset. There is structurally no manifest
// in the value path — the resolver is pure canonical math + the EXTCODESIZE verifier — so
// the ONLY lever a censor could pull is "not in a list". RED proves no such lever exists:
// a real coded ERC-20 that appears in NO registration/admit list STILL trades.
// ---------------------------------------------------------------------------
func TestRED_GateC_CannotCensorRealAsset(t *testing.T) {
h := newE2EHarness(t)
// Two real coded ERC-20s that RED deliberately leaves OUT of installAssetResolverFor's
// token list — i.e. they are NOT "admitted" by any list. The ONLY credential RED grants
// is on-chain code (what a real deployed contract has). If a manifest gate existed,
// these would be censored. They must not be.
cenBase := common.HexToAddress("0x000000000000000000000000000000000000CE01")
cenQuote := common.HexToAddress("0x000000000000000000000000000000000000CE02")
novelKey := PoolKey{Currency0: Currency{Address: cenBase}, Currency1: Currency{Address: cenQuote}, Fee: 3000, TickSpacing: 60}
h.key = novelKey
h.settleHarness.key = novelKey
// Install a PERMISSIONLESS resolver but with an EMPTY token list (RED's censorship
// attempt: "admit nothing"). Then grant ONLY on-chain code — no list entry.
r := newTestAssetResolver(h.networkID, h.cChainID).boundToHarness(h.settleHarness)
prev := installedAssetResolver.Load()
installedAssetResolver.Store(nil)
if err := InstallAssetResolver(r, h.networkID, h.cChainID); err != nil {
t.Fatalf("InstallAssetResolver: %v", err)
}
t.Cleanup(func() { installedAssetResolver.Store(prev) })
// The ONLY thing that makes them tradeable: live on-chain code (the reality the
// EXTCODESIZE verifier checks). There is no list to add them to.
h.state.stateDB.SetCodeSize(cenBase, 1)
h.state.stateDB.SetCodeSize(cenQuote, 1)
maker, taker := e2eMaker, e2eTaker
h.mint(cenQuote, maker, 5000)
h.deposit(t, maker, cenQuote, 5000)
h.placeArgs(t, maker, true, uint64(50)*uint64(priceMultiplierConst), 100)
h.mint(cenBase, taker, 80)
out, err := h.swapMinOut(t, taker, 80, 1)
if err != nil {
t.Fatalf("CENSORSHIP SUCCEEDED (do-not-ship): a REAL coded ERC-20 in no list was refused: %v", err)
}
a0, a1 := UnpackBalanceDelta(out)
if a0.Int64() != -80 || a1.Int64() != 4000 {
t.Fatalf("real-asset fill wrong: delta=(%s,%s) want (-80,4000)", a0, a1)
}
if got := h.ercBal(cenQuote, taker); got != 4000 {
t.Fatalf("taker proceeds = %d, want 4000 — real asset must settle uncensored", got)
}
}
// ---------------------------------------------------------------------------
// CASE 2 ATTACK: a real coded asset admits through OpenMarketChecked with a resolver
// that has NO membership at all (the production-shaped canonical resolver), confirming
// resolution is identity-proof, not list-membership.
// ---------------------------------------------------------------------------
func TestRED_GateC_RealAssetAdmitsWithoutMembership(t *testing.T) {
h := newSettleHarness(t)
real := common.HexToAddress("0x000000000000000000000000000000000000AB01")
h.state.stateDB.SetCodeSize(real, 1)
r := newTestAssetResolver(h.networkID, h.cChainID).boundToHarness(h)
verifier := onChainVerifierFor(newPoolStateAdapter(h.state))
store := newEVMStore(newPoolStateAdapter(h.state))
pid := h.key.ID()
baseSide := dexcore.AssetSide{Kind: dexcore.AssetKindEVMNative, Ref: dexcore.EVMNativeMarker}
quoteSide := dexcore.AssetSide{Kind: dexcore.AssetKindERC20, Ref: real.Bytes()}
if err := dexcore.OpenMarketChecked(store, r, verifier, pid, baseSide, quoteSide); err != nil {
t.Fatalf("real coded asset must admit without membership, got: %v", err)
}
}
// ---------------------------------------------------------------------------
// CASE 1+5 ATTACK: try to trade a FAKE asset through the MAKER place() path (not just
// the taker swap path). The gate must be identical on both ingresses. A fake ASCII
// ticker with no code resting "liquidity" would let a maker bind a phantom market — RED
// proves swapPlace refuses it at the on-chain proof, BEFORE any lock.
// ---------------------------------------------------------------------------
func TestRED_GateC_FakeAssetRefusedOnMakerPlacePath(t *testing.T) {
h := newE2EHarness(t)
maker := e2eMaker
// A fake base (ASCII "FAKE") with NO code, paired with a real coded quote. The maker
// tries to rest a SELL of the fake base — which would BIND the market over it.
fake := a32("FAKE")
realQuote := common.HexToAddress("0x000000000000000000000000000000000000DD01")
// Resolver admits any well-formed ref; only code makes it real. Seed code for the
// real quote ONLY; the fake base is left code-less.
r := newTestAssetResolver(h.networkID, h.cChainID).boundToHarness(h.settleHarness)
prev := installedAssetResolver.Load()
installedAssetResolver.Store(nil)
if err := InstallAssetResolver(r, h.networkID, h.cChainID); err != nil {
t.Fatalf("InstallAssetResolver: %v", err)
}
t.Cleanup(func() { installedAssetResolver.Store(prev) })
h.state.stateDB.SetCodeSize(realQuote, 1)
h.state.stateDB.SetCodeSize(fake, 0) // synthetic
// currency0 < currency1 ordering: pick the key so the fake is a market currency.
var c0, c1 common.Address = fake, realQuote
if new(big.Int).SetBytes(fake.Bytes()).Cmp(new(big.Int).SetBytes(realQuote.Bytes())) > 0 {
c0, c1 = realQuote, fake
}
key := PoolKey{Currency0: Currency{Address: c0}, Currency1: Currency{Address: c1}, Fee: 3000, TickSpacing: 60}
// Fund the maker with the real quote so a lock would be observable if the gate were
// bypassed (bid locks quote). The point: the gate refuses BEFORE the lock.
h.mint(realQuote, maker, 10_000)
h.deposit(t, maker, realQuote, 10_000)
availBefore := h.dcAvail(maker, realQuote)
// swapPlace over the fake-containing market.
data := make([]byte, 256)
copy(data[0:160], EncodePoolKeyABI(key))
data[191] = 1 // isBid (locks quote)
new(big.Int).SetUint64(uint64(50) * uint64(priceMultiplierConst)).FillBytes(data[192:224]) // price
new(big.Int).SetUint64(100).FillBytes(data[224:256]) // size
_, _, err := h.c.Run(h.state, maker, poolManagerAddr9999, prependSelector(SelectorSwapPlace, data), 5_000_000, false)
if err == nil {
t.Fatal("MAKER bound a phantom market over a code-less FAKE asset (do-not-ship)")
}
if !errors.Is(err, dexcore.ErrAssetNotOnChain) {
t.Fatalf("expected ErrAssetNotOnChain on the maker place path, got: %v", err)
}
// NO LOCK occurred: the maker's available is untouched (gate ran before custody).
if got := h.dcAvail(maker, realQuote); got != availBefore {
t.Fatalf("maker place over a fake asset locked funds BEFORE the gate: %d -> %d", availBefore, got)
}
}
// ---------------------------------------------------------------------------
// CASE 6 ATTACK: the DEX governance controller is the ONLY privileged role. RED proves
// that even this authority CANNOT mint and CANNOT move a user's deposit:
//
// (a) seedSeamReserve with NO value delivered yields ZERO (no bookkeeping mint);
// (b) the controller cannot pay a taker out of a depositor's pot (conservation);
// (c) the controller has NO selector that credits an arbitrary user balance from thin air.
//
// The authority is now the per-network governance controller resolved from the runtime
// AtomicState (h.operator() == h.state.governance), not a hardcoded EOA — but the
// conservation guarantee is identical: governance is bounded to non-minting, non-moving
// operations regardless of which address holds it.
// ---------------------------------------------------------------------------
func TestRED_GateC_DAOControllerCannotMintOrMoveUserFunds(t *testing.T) {
h := newSettleHarness(t)
controller := h.operator() // == the runtime-resolved DEX governance controller
native := [32]byte{}
seamBefore := loadSeamReserve(newPoolStateAdapter(h.state), native).Uint64()
// (a) Controller calls seedSeamReserve(native, 1_000_000) but the host frame delivers
// NO msg.value (we do NOT add balance to 0x9999). A mint would inflate seamReserve;
// the observed-delta discipline must yield 0 delivered and REVERT.
data := make([]byte, 64) // asset = address(0), amount
new(big.Int).SetUint64(1_000_000).FillBytes(data[32:64])
_, _, err := h.c.Run(h.state, controller, poolManagerAddr9999, prependSelector(SelectorSeedSeamReserve, data), 5_000_000, false)
if err == nil {
t.Fatal("DAO controller MINTED seam reserve with no value delivered (do-not-ship)")
}
if !errors.Is(err, ErrSeedUndelivered) {
t.Fatalf("expected ErrSeedUndelivered (no mint), got: %v", err)
}
if got := loadSeamReserve(newPoolStateAdapter(h.state), native).Uint64(); got != seamBefore {
t.Fatalf("seam reserve changed on an undelivered seed: %d -> %d (mint)", seamBefore, got)
}
// (b) A depositor funds the depositor pot; the controller cannot drain it via a
// settlement (settlement draws ONLY seamReserve, which is empty here). This reuses the
// proven conservation primitive directly.
depositor := common.HexToAddress("0xDEadBEEF00000000000000000000000000000001")
h.state.stateDB.AddBalance(depositor, uint256.NewInt(1000))
h.state.stateDB.AddBalance(poolManagerAddr9999, uint256.NewInt(1000))
depData := make([]byte, 64)
new(big.Int).SetInt64(1000).FillBytes(depData[32:64])
if _, _, derr := h.c.Run(h.state, depositor, poolManagerAddr9999, prependSelector(SelectorDeposit, depData), 5_000_000, false); derr != nil {
t.Fatalf("depositor deposit: %v", derr)
}
// Controller attempts to credit ITSELF out of the vault via a settlement. RED probes
// TWO raid shapes, BOTH must be refused with the depositor pot intact:
// (i) a FABRICATED object id (no D->C object exists) — refused at object lookup;
// (ii) a REAL D->C object but drawing native whose SEAM RESERVE is empty — refused
// unbacked (the seam pot, not the depositor pot, backs a credit).
// (i) fabricated object: there is no privileged path to mint a settlement object.
credited, ierr := h.c.atomicImport(h.state, SettlementClaim{
OutputID: [32]byte{0xAB}, Asset: native, AssetAddr: common.Address{}, Amount: 500, Recipient: controller,
})
if ierr == nil || credited != 0 {
t.Fatalf("controller credited itself from a fabricated settlement object: credited=%d err=%v", credited, ierr)
}
// (ii) a REAL D->C object exists, but seamReserve[native] is empty (only the depositor
// pot holds native). The credit must revert UNBACKED — it cannot draw the depositor pot.
realObj := [32]byte{0xCD, 0xEF}
h.putDtoCObject(t, controller, realObj, native, 500)
credited2, ierr2 := h.c.atomicImport(h.state, SettlementClaim{
OutputID: realObj, Asset: native, AssetAddr: common.Address{}, Amount: 500, Recipient: controller,
})
if ierr2 != ErrNativeSettleUnbacked {
t.Fatalf("controller raided the depositor pot via a real object with empty seam: credited=%d err=%v (must be ErrNativeSettleUnbacked)", credited2, ierr2)
}
if loadDepositorClaim(newPoolStateAdapter(h.state), depositor, native).Int64() != 1000 {
t.Fatal("depositor claim moved after controller's raid attempt (do-not-ship)")
}
}
// ---------------------------------------------------------------------------
// CASE 10 DoS PROBE: throw adversarial calldata at the public permissionless ingress and
// assert the precompile (a) charges gas up-front (bounded), (b) never loops unbounded on
// attacker-controlled length, (c) refuses malformed input cheaply. RED probes the three
// public entrypoints: swap, swapPlace, initialize.
//
// The precompile model: every handler debits a FIXED gas floor (GasSwap/GasPoolCreate/...)
// at the top and decodes FIXED-WIDTH ABI words. There is no attacker-controlled loop bound
// in the dispatch. RED confirms a giant calldata blob does not translate into giant work:
// the handler reads only the fixed prefix it needs and ignores the tail.
// ---------------------------------------------------------------------------
func TestRED_GateC_UnauthenticatedIngressIsBounded(t *testing.T) {
h := newE2EHarness(t)
attacker := common.HexToAddress("0x00000000000000000000000000000000A77ac5e1")
// (a) Oversized swap calldata: a valid PoolKey+params prefix followed by 1 MiB of
// attacker tail. The handler must read only the fixed prefix; the tail is inert. It
// must not hang or allocate proportional to the tail (the test simply completing
// quickly + returning a normal admission error proves no tail-proportional work).
params := SwapParams{ZeroForOne: true, AmountSpecified: big.NewInt(-100), SqrtPriceLimitX96: big.NewInt(0)}
base := buildSwapCalldata(h.key, params, EncodeMinOutHookData(1))
huge := append(append([]byte(nil), base...), make([]byte, 1<<20)...) // +1 MiB tail
_, gasLeft, err := runWithEVMSnapshot(h.c, h.state, attacker, poolManagerAddr9999,
prependSelector(SelectorSwap, huge), 5_000_000, false)
// It should either fill or fail an admission/policy check — NOT consume work
// proportional to the 1 MiB tail. Gas charged must be the bounded swap floor, not
// tail-scaled. We assert the call returned (no hang) and charged a bounded amount.
if err == nil {
// A fill is fine (the e2e market's assets are real); assert gas was bounded.
if 5_000_000-gasLeft > GasSwap+1_000_000 {
t.Fatalf("swap charged tail-scaled gas: charged=%d (floor=%d)", 5_000_000-gasLeft, GasSwap)
}
}
// (b) hookData length field that LIES (claims a huge body). DecodeSwapInput / the
// min-out decoder must bound-check against the actual buffer, never allocate the
// claimed size. RED crafts a swap whose hookData header claims a 4 GiB body.
evil := buildSwapCalldata(h.key, params, nil)
// Corrupt nothing structurally valid is needed: feed a truncated/forged blob and
// assert a clean error, not a panic/OOM. A swap with malformed trailing bytes must
// fall through to a deterministic error.
forged := append(append([]byte(nil), evil...), 0xFF, 0xFF, 0xFF, 0xFF) // junk tail
_, _, err2 := runWithEVMSnapshot(h.c, h.state, attacker, poolManagerAddr9999,
prependSelector(SelectorSwap, forged), 5_000_000, false)
_ = err2 // must not panic; any error/fill is acceptable — the assertion is "no panic, returned"
// (c) initialize with a giant trailing hookData blob: the registry write reads a
// fixed 192-byte prefix; the tail must be inert.
initData := make([]byte, 192)
copy(initData[0:160], EncodePoolKeyABI(h.key))
// sqrtPriceX96 = a valid in-range price (1<<96).
new(big.Int).Lsh(big.NewInt(1), 96).FillBytes(initData[160:192])
bigInit := append(append([]byte(nil), initData...), make([]byte, 1<<20)...) // +1 MiB tail
_, initGasLeft, ierr := h.c.Run(h.state, attacker, poolManagerAddr9999,
prependSelector(SelectorInitialize, bigInit), 5_000_000, false)
_ = ierr // may fail (already-initialized / admission) — must be bounded + return
if 5_000_000-initGasLeft > GasPoolCreate+1_000_000 {
t.Fatalf("initialize charged tail-scaled gas: charged=%d (floor=%d)", 5_000_000-initGasLeft, GasPoolCreate)
}
// (d) Truncated calldata at EVERY length from 4..200 must NEVER panic — only return a
// clean error. This is the classic precompile fuzz: a short read must be a bounded
// revert, never an index-out-of-range.
full := prependSelector(SelectorSwap, buildSwapCalldata(h.key, params, EncodeMinOutHookData(1)))
for n := 4; n < 200 && n < len(full); n++ {
func() {
defer func() {
if r := recover(); r != nil {
t.Fatalf("PANIC on truncated swap calldata len=%d: %v (DoS — do-not-ship)", n, r)
}
}()
_, _, _ = runWithEVMSnapshot(h.c, h.state, attacker, poolManagerAddr9999, full[:n], 5_000_000, false)
}()
}
}
+41 -3
View File
@@ -7,11 +7,22 @@ import (
"errors"
"github.com/luxfi/geth/common"
"github.com/luxfi/precompile/contract"
)
// halt9999.go is the MULTI-LAYER HALT for the 0x9999 settlement path. Authority
// is the protocolFeeController (governance), the SAME authority that gates the
// existing pauseDEX/freezePool controls — one authority, not a new one.
// is the per-network DEX GOVERNANCE CONTROLLER — a governance CONTRACT (Governor/
// Timelock/multisig), resolved at RUNTIME from the host's deployment topology via
// contract.AtomicState.GovernanceController(), the SAME runtime seam networkID/
// cChainID/dChainID flow through. It is NEVER a hardcoded address and NEVER an EOA
// derivable from a dev mnemonic: a single admin key could censor an asset or DoS
// the whole DEX, so the authority is decentralized governance, configured per net.
//
// FAIL-CLOSED authority: when no governance controller is configured on this
// network (the zero address — e.g. a network that has not yet deployed its Governor),
// the halt setters are UNCALLABLE (governanceController returns ErrSettleNoGovernance
// and every setHalt reverts). An unset authority can therefore DoS no one — strictly
// safer than a default key. Halt is governance, never a single hardcoded key.
//
// Halt state is DURABLE, consensus-shared StateDB read STRAIGHT from state on
// every settle (never cached in a process field). This is the RED V6 invariant
@@ -42,6 +53,32 @@ var (
ErrAssetHalted = errors.New("dex: settlement halted for this asset")
)
// ErrSettleNoGovernance is the fail-closed authority error: the host wired no
// AtomicState, or the network's governance controller is unset (the zero address).
// Every governance-gated 0x9999 op (halt + pot seeding) returns it rather than fall
// back to any default authority — there is NO hardcoded admin key to fall back to.
var ErrSettleNoGovernance = errors.New("dex: no DEX governance controller configured on this network — governance ops are fail-closed (a governance contract must be set per network; never a dev-mnemonic EOA)")
// governanceController resolves the per-network DEX governance authority from the
// runtime AtomicState capability — the SOLE source of "who may halt / seed", the
// SAME seam networkID/cChainID/dChainID flow through. It is fail-closed on two
// counts: the host wired no AtomicState (single-chain dev / non-atomic harness), or
// the network has no governance controller configured (the zero address). In either
// case it returns ErrSettleNoGovernance, so a governance op reverts cleanly with no
// state touched and there is no default key to abuse. The returned address is a
// governance CONTRACT (Governor/Timelock/multisig), never a mnemonic-derivable EOA.
func governanceController(state contract.AccessibleState) (common.Address, error) {
atomicState, ok := state.(contract.AtomicState)
if !ok {
return common.Address{}, ErrSettleNoGovernance
}
gov := atomicState.GovernanceController()
if gov == (common.Address{}) {
return common.Address{}, ErrSettleNoGovernance
}
return gov, nil
}
// haltSet is the non-zero sentinel a halt slot holds when active. Any non-zero
// value means halted; we write a fixed 1 for clarity.
var haltSet = common.Hash{31: 1}
@@ -72,7 +109,8 @@ func checkHalt(stateDB stateKV, key PoolKey, params SwapParams) error {
return nil
}
// --- Governance setters (protocolFeeController-gated; the caller checks authority).
// --- Governance setters (governance-controller-gated; the caller resolves authority
// via governanceController() and checks caller == gov before invoking these).
// SetHaltGlobal halts/unhalts all new settlement.
func SetHaltGlobal(stateDB stateKV, on bool) { setHalt(stateDB, haltGlobalKey, on) }
+223
View File
@@ -0,0 +1,223 @@
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package dex
import (
"errors"
"math/big"
"testing"
"github.com/luxfi/geth/common"
)
// halt_governance_test.go is the HIGH-3 TDD proof: the 0x9999 emergency-halt authority
// is the per-NETWORK DEX governance controller resolved at runtime from
// contract.AtomicState.GovernanceController() — a governance CONTRACT, NEVER a single
// hardcoded mnemonic-derivable EOA. It proves, through the production Run dispatch:
//
// 1. ONLY the configured governance controller may halt (global / asset / market), and
// the halt actually BITES (a subsequent swap reverts with the right halt error).
// 2. The RETIRED hardcoded EOA (0x9011…94714, the old DefaultDAOTreasury, derivable
// from the dev mnemonic) CANNOT halt — it is unauthorized like any other caller.
// 3. An arbitrary EOA CANNOT halt.
// 4. When NO governance controller is configured (the zero address), NOBODY can halt:
// the gate is fail-closed (ErrSettleNoGovernance), so an unset network can be neither
// censored nor DoS'd through this authority. There is no default key to abuse.
// 5. The SAME gate guards pot seeding (seedSeamReserve): the retired EOA cannot seed.
// 6. Conservation is preserved: governance can halt/unhalt but mints nothing (the
// unhalt restores trading; CASE 6 in gatec_red_probe_test.go proves no mint/move).
// haltGlobal builds and runs setHaltGlobal(on) from `caller`, returning the dispatch err.
func haltGlobal(h *settleHarness, caller common.Address, on bool) error {
data := make([]byte, 32)
if on {
data[31] = 1
}
_, _, err := h.c.Run(h.state, caller, poolManagerAddr9999,
prependSelector(SelectorSetHaltGlobal, data), 5_000_000, false)
return err
}
// haltAsset builds and runs setHaltAsset(assetID, on) from `caller`.
func haltAsset(h *settleHarness, caller common.Address, assetID [32]byte, on bool) error {
data := make([]byte, 64)
copy(data[0:32], assetID[:])
if on {
data[63] = 1
}
_, _, err := h.c.Run(h.state, caller, poolManagerAddr9999,
prependSelector(SelectorSetHaltAsset, data), 5_000_000, false)
return err
}
// haltMarket builds and runs setHaltMarket(marketID, on) from `caller`.
func haltMarket(h *settleHarness, caller common.Address, marketID [32]byte, on bool) error {
data := make([]byte, 64)
copy(data[0:32], marketID[:])
if on {
data[63] = 1
}
_, _, err := h.c.Run(h.state, caller, poolManagerAddr9999,
prependSelector(SelectorSetHaltMarket, data), 5_000_000, false)
return err
}
// TestHIGH3_OnlyGovernanceCanHalt_RetiredEOACannot is the central HIGH-3 proof.
func TestHIGH3_OnlyGovernanceCanHalt_RetiredEOACannot(t *testing.T) {
h := newSettleHarness(t)
gov := h.operator() // the runtime-resolved governance controller (testGovernance)
// Sanity: the test governance address is NOT the retired hardcoded EOA, and the
// retired EOA is a real-looking address (so "it cannot halt" is a meaningful claim).
if gov == retiredHardcodedHaltEOA {
t.Fatal("test setup error: governance must differ from the retired hardcoded EOA")
}
arbitrary := common.HexToAddress("0xBADBADBADBADBADBADBADBADBADBADBADBADBADB")
// (2)+(3): the retired EOA and an arbitrary EOA are BOTH unauthorized to halt.
if err := haltGlobal(h, retiredHardcodedHaltEOA, true); !errors.Is(err, ErrUnauthorized) {
t.Fatalf("retired 0x9011 EOA must NOT be able to setHaltGlobal — got %v, want ErrUnauthorized", err)
}
if err := haltAsset(h, retiredHardcodedHaltEOA, h.inAssetID(), true); !errors.Is(err, ErrUnauthorized) {
t.Fatalf("retired 0x9011 EOA must NOT be able to setHaltAsset — got %v, want ErrUnauthorized", err)
}
if err := haltGlobal(h, arbitrary, true); !errors.Is(err, ErrUnauthorized) {
t.Fatalf("arbitrary EOA must NOT be able to setHaltGlobal — got %v, want ErrUnauthorized", err)
}
// Confirm none of those rejected calls actually halted anything (the gate ran before
// any state write; a reverted halt write rolls back with the revert).
if checkHalt(newPoolStateAdapter(h.state), h.key, h.params) != nil {
t.Fatal("an unauthorized halt attempt wrote halt state (do-not-ship)")
}
// (1): the configured governance controller CAN halt globally.
if err := haltGlobal(h, gov, true); err != nil {
t.Fatalf("governance controller must be able to setHaltGlobal, got: %v", err)
}
if got := checkHalt(newPoolStateAdapter(h.state), h.key, h.params); !errors.Is(got, ErrDEXHalted) {
t.Fatalf("global halt did not bite: checkHalt = %v, want ErrDEXHalted", got)
}
// And governance can lift it (no mint, just unhalt).
if err := haltGlobal(h, gov, false); err != nil {
t.Fatalf("governance controller must be able to clear the global halt, got: %v", err)
}
if got := checkHalt(newPoolStateAdapter(h.state), h.key, h.params); got != nil {
t.Fatalf("global halt not cleared: checkHalt = %v, want nil", got)
}
// (1) asset-scoped: governance halts the input asset; checkHalt reports ErrAssetHalted.
if err := haltAsset(h, gov, h.inAssetID(), true); err != nil {
t.Fatalf("governance setHaltAsset(in) failed: %v", err)
}
if got := checkHalt(newPoolStateAdapter(h.state), h.key, h.params); !errors.Is(got, ErrAssetHalted) {
t.Fatalf("asset halt did not bite: checkHalt = %v, want ErrAssetHalted", got)
}
if err := haltAsset(h, gov, h.inAssetID(), false); err != nil {
t.Fatalf("governance clear setHaltAsset(in) failed: %v", err)
}
// (1) market-scoped: governance halts this pool; checkHalt reports ErrMarketHalted.
poolID := h.key.ID()
if err := haltMarket(h, gov, poolID, true); err != nil {
t.Fatalf("governance setHaltMarket failed: %v", err)
}
if got := checkHalt(newPoolStateAdapter(h.state), h.key, h.params); !errors.Is(got, ErrMarketHalted) {
t.Fatalf("market halt did not bite: checkHalt = %v, want ErrMarketHalted", got)
}
}
// TestHIGH3_FailClosed_NoGovernanceConfigured_NobodyCanHalt proves the maximally-
// decentralized default: a network with NO governance controller (zero address) has NO
// halt authority at all — not the retired EOA, not even a would-be admin. Every halt
// attempt is fail-closed (ErrSettleNoGovernance). This is strictly safer than a default
// key: an unset network can be neither censored nor DoS'd through this authority.
func TestHIGH3_FailClosed_NoGovernanceConfigured_NobodyCanHalt(t *testing.T) {
h := newSettleHarness(t)
h.state.governance = common.Address{} // network configured NO governance controller
for _, caller := range []common.Address{
retiredHardcodedHaltEOA,
h.caller,
common.HexToAddress("0x60F0000000000000000000000000000000000A11"), // even the would-be gov addr
} {
if err := haltGlobal(h, caller, true); !errors.Is(err, ErrSettleNoGovernance) {
t.Fatalf("with no governance configured, setHaltGlobal by %s must be fail-closed — got %v, want ErrSettleNoGovernance",
caller.Hex(), err)
}
if err := haltAsset(h, caller, h.inAssetID(), true); !errors.Is(err, ErrSettleNoGovernance) {
t.Fatalf("with no governance configured, setHaltAsset by %s must be fail-closed — got %v, want ErrSettleNoGovernance",
caller.Hex(), err)
}
}
// Nothing was halted: the swap path is unaffected (no fail-OPEN, no fail-into-halt).
if got := checkHalt(newPoolStateAdapter(h.state), h.key, h.params); got != nil {
t.Fatalf("fail-closed authority must not have written halt state: checkHalt = %v", got)
}
}
// TestHIGH3_RetiredEOACannotSeedPots proves the SAME governance gate guards the operator
// pot-seeding selectors (seedSeamReserve), so the retired EOA cannot seed/credit either.
// (CASE 6 in gatec_red_probe_test.go already proves the AUTHORIZED controller cannot mint;
// here we prove the retired EOA is not even authorized to attempt it.)
func TestHIGH3_RetiredEOACannotSeedPots(t *testing.T) {
h := newSettleHarness(t)
// seedSeamReserve(address(0), 1_000_000) by the retired EOA: unauthorized, before any
// value accounting. The gate is the governance controller, not a hardcoded key.
data := make([]byte, 64)
new(big.Int).SetUint64(1_000_000).FillBytes(data[32:64])
if _, _, err := h.c.Run(h.state, retiredHardcodedHaltEOA, poolManagerAddr9999,
prependSelector(SelectorSeedSeamReserve, data), 5_000_000, false); !errors.Is(err, ErrUnauthorized) {
t.Fatalf("retired 0x9011 EOA must NOT be able to seedSeamReserve — got %v, want ErrUnauthorized", err)
}
// The configured governance controller IS authorized to attempt the seed (it then
// fails ErrSeedUndelivered because no value was delivered — proving authorization is
// distinct from the conservation guarantee, which still holds).
if _, _, err := h.c.Run(h.state, h.operator(), poolManagerAddr9999,
prependSelector(SelectorSeedSeamReserve, data), 5_000_000, false); !errors.Is(err, ErrSeedUndelivered) {
t.Fatalf("governance controller seed with no value must reach the conservation check (ErrSeedUndelivered), got: %v", err)
}
}
// TestHIGH3_HaltBitesARealFill is the end-to-end proof that governance-set halt actually
// stops a real swap: governance halts the market, a real fillable swap reverts with the
// halt error, governance lifts it, and the swap then fills. This closes the loop —
// "governance halts" means "trading actually stops", and only governance can do it.
func TestHIGH3_HaltBitesARealFill(t *testing.T) {
h := newE2EHarness(t)
gov := h.operator()
maker, taker := e2eMaker, e2eTaker
// Seed a resting bid so a taker SELL of base (LETH) for quote (LUSD) can fill.
h.mint(e2eLUSD, maker, 1_000_000)
h.deposit(t, maker, e2eLUSD, 1_000_000)
h.placeArgs(t, maker, true, uint64(50)*uint64(priceMultiplierConst), 1000)
h.mint(e2eLETH, taker, 100)
// Governance halts THIS market.
poolID := h.key.ID()
if err := haltMarket(h.settleHarness, gov, poolID, true); err != nil {
t.Fatalf("governance setHaltMarket: %v", err)
}
// A real, otherwise-fillable swap now reverts with the market-halt error.
if _, err := h.swapMinOut(t, taker, 100, 1); !errors.Is(err, ErrMarketHalted) {
t.Fatalf("halted market must refuse a fill with ErrMarketHalted, got: %v", err)
}
// Only governance can lift it (the retired EOA cannot).
if err := haltMarket(h.settleHarness, retiredHardcodedHaltEOA, poolID, false); !errors.Is(err, ErrUnauthorized) {
t.Fatalf("retired EOA must NOT be able to clear the market halt, got: %v", err)
}
if err := haltMarket(h.settleHarness, gov, poolID, false); err != nil {
t.Fatalf("governance must be able to clear the market halt, got: %v", err)
}
// With the halt lifted, the same swap fills.
if _, err := h.swapMinOut(t, taker, 100, 1); err != nil {
t.Fatalf("after governance lifted the halt, the swap must fill, got: %v", err)
}
}
+35 -20
View File
@@ -30,6 +30,15 @@ import (
// so a C->D Put is readable by D via Get(cChainID) and a D->C Put is readable by C
// via Get(dChainID) — the real platformvm import/export semantics.
// testGovernance is the harness's per-network DEX governance controller — the address
// the mock AtomicState returns from GovernanceController(), i.e. the ONLY caller that may
// halt 0x9999 settlement or seed its pots. In production this is a governance CONTRACT
// the host wires per network; here it is a fixed test address that stands in for that
// contract. It is deliberately a non-EOA-looking sentinel distinct from the harness's
// trading caller (0x1111…) and from the retired mnemonic-derivable 0x9011… EOA, so the
// governance tests can prove only this address halts and the old EOA cannot.
var testGovernance = common.HexToAddress("0x60F0000000000000000000000000000000000A11") // "GOV ...0A11"
// nativeAtomicState implements BOTH contract.AccessibleState and
// contract.AtomicState so SettleSwap's `state.(contract.AtomicState)` assertion
// succeeds and reaches a real SharedMemory + chain identity.
@@ -39,7 +48,8 @@ type nativeAtomicState struct {
networkID uint32
chainID ids.ID // THIS chain (C)
cChainID ids.ID
dChainID ids.ID // the D-Chain (dexvm) peer the host resolves at runtime
dChainID ids.ID // the D-Chain (dexvm) peer the host resolves at runtime
governance common.Address // the per-network DEX governance controller (halt/seed authority)
txID ids.ID
callIndex uint32
blockTimestamp uint64 // block time the precompile reads via GetBlockContext().Timestamp()
@@ -57,13 +67,14 @@ func (m *nativeAtomicState) GetChainConfig() precompileconfig.ChainConfig {
func (m *nativeAtomicState) GetPrecompileEnv() contract.PrecompileEnvironment { return nil }
// --- AtomicState ---
func (m *nativeAtomicState) AtomicMemory() atomic.SharedMemory { return m.sm }
func (m *nativeAtomicState) NetworkID() uint32 { return m.networkID }
func (m *nativeAtomicState) ChainID() ids.ID { return m.chainID }
func (m *nativeAtomicState) CChainID() ids.ID { return m.cChainID }
func (m *nativeAtomicState) DChainID() ids.ID { return m.dChainID }
func (m *nativeAtomicState) TxID() ids.ID { return m.txID }
func (m *nativeAtomicState) CallIndex() uint32 { return m.callIndex }
func (m *nativeAtomicState) AtomicMemory() atomic.SharedMemory { return m.sm }
func (m *nativeAtomicState) NetworkID() uint32 { return m.networkID }
func (m *nativeAtomicState) ChainID() ids.ID { return m.chainID }
func (m *nativeAtomicState) CChainID() ids.ID { return m.cChainID }
func (m *nativeAtomicState) GovernanceController() common.Address { return m.governance }
func (m *nativeAtomicState) DChainID() ids.ID { return m.dChainID }
func (m *nativeAtomicState) TxID() ids.ID { return m.txID }
func (m *nativeAtomicState) CallIndex() uint32 { return m.callIndex }
var _ contract.AccessibleState = (*nativeAtomicState)(nil)
var _ contract.AtomicState = (*nativeAtomicState)(nil)
@@ -104,21 +115,22 @@ func newSettleHarnessN(t testing.TB, _ int) *settleHarness {
dSM := mem.NewSharedMemory(dChainID)
state := &nativeAtomicState{
stateDB: NewMockStateDB(),
sm: cSM,
networkID: 1,
chainID: cChainID,
cChainID: cChainID,
dChainID: dChainID, // host-resolved D peer (runtime), as the EVM adapter supplies it
txID: ids.ID{0x7A}, // a fixed tx id for the harness; tests vary callIndex/txID
callIndex: 0,
stateDB: NewMockStateDB(),
sm: cSM,
networkID: 1,
chainID: cChainID,
cChainID: cChainID,
dChainID: dChainID, // host-resolved D peer (runtime), as the EVM adapter supplies it
governance: testGovernance, // the per-network governance controller the host supplies via AtomicState
txID: ids.ID{0x7A}, // a fixed tx id for the harness; tests vary callIndex/txID
callIndex: 0,
// Default to the activation boundary so settlement/init tests exercise the
// live-chain path where 0x9999's DEXFill + Initialize logs are active. A test
// that needs a pre-activation block sets state.blockTimestamp explicitly.
blockTimestamp: DexSettleActivationTime,
}
h := &settleHarness{
c: &SettleContract{protocolFeeController: common.HexToAddress("0xFEE0000000000000000000000000000000000001")},
c: &SettleContract{},
state: state,
mem: mem,
cSM: cSM,
@@ -202,9 +214,12 @@ func (h *settleHarness) fundCallerNative(amount int64) {
h.state.stateDB.AddBalance(h.caller, uint256.NewInt(uint64(amount)))
}
// operator is the harness's protocolFeeController (the SettleContract is constructed
// with this authority), so the operator-gated seed/fee selectors accept it.
func (h *settleHarness) operator() common.Address { return h.c.protocolFeeController }
// operator is the harness's DEX governance controller the authority the mock
// AtomicState returns from GovernanceController(), the SOLE caller the governance-gated
// seed/fee/halt selectors accept. It is the per-network governance address the host
// supplies at runtime (testGovernance here), NOT a field on the SettleContract and NOT a
// hardcoded mnemonic-derivable EOA.
func (h *settleHarness) operator() common.Address { return h.state.governance }
// fundVaultOut seeds the 0x9999 vault's OUTPUT token into the SEAM RESERVE through the
// REAL operator-gated seedSeamReserve selector (FIX-4 — the production counterparty
+3 -3
View File
@@ -321,7 +321,7 @@ func TestERC20Settle_CallSeam_DepositWithdrawConserves(t *testing.T) {
t.Fatal("GetPrecompileEnv() does not satisfy callableEnv: ERC-20 leg would refuse with ErrERC20VaultUnavailable")
}
c := &SettleContract{protocolFeeController: common.HexToAddress("0xFEE0000000000000000000000000000000000001")}
c := &SettleContract{} // deposit/withdraw are custody ops, not governance-gated
aid := assetID(Currency{Address: token})
// Conservation oracle: depositor + vault token balances == totalSupply, always; and
@@ -436,7 +436,7 @@ func TestERC20Settle_CallSeam_RejectsDelegatecall(t *testing.T) {
env: env,
timestamp: DexSettleActivationTime,
}
c := &SettleContract{protocolFeeController: common.HexToAddress("0xFEE0000000000000000000000000000000000001")}
c := &SettleContract{} // deposit/withdraw are custody ops, not governance-gated
aid := assetID(Currency{Address: token})
depData := make([]byte, 64)
@@ -502,7 +502,7 @@ func TestERC20Settle_NoEnv_RefusesFailSecure(t *testing.T) {
t.Fatal("setup")
}
c := &SettleContract{protocolFeeController: common.HexToAddress("0xFEE0000000000000000000000000000000000001")}
c := &SettleContract{} // deposit/withdraw are custody ops, not governance-gated
depData := make([]byte, 64)
copy(depData[12:32], token.Bytes())
big.NewInt(1000).FillBytes(depData[32:64])
+56 -35
View File
@@ -13,15 +13,18 @@ import (
"github.com/luxfi/precompile/precompileconfig"
)
// DefaultDAOTreasury is the built-in canonical governance address for 0x9999 — the
// Lux DAO treasury. It is the protocolFeeController that gates the settlement kill
// switches (setHaltGlobal/Market/Asset) and the operator-gated pot seeding
// (seedSeamReserve / creditPositionFee). It is hard-coded (NOT per-network config)
// because 0x9999 is always-on with zero per-net configuration and DEX governance is
// the same DAO across every Lux network. It is deliberately NOT one of the publicly-
// known compromised dev keys (compromisedDevControllers), so it is a safe default
// admin from genesis. Forward-perfect: one canonical governance authority, one way.
var DefaultDAOTreasury = common.HexToAddress("0x9011E888251AB053B7bD1cdB598Db4f9DEd94714")
// 0x9999 settlement governance (setHaltGlobal/Market/Asset, seedSeamReserve,
// creditPositionFee) is gated to the per-NETWORK DEX GOVERNANCE CONTROLLER, resolved
// at RUNTIME from contract.AtomicState.GovernanceController() (the SAME runtime seam
// networkID/cChainID/dChainID flow through) — see governanceController in halt9999.go.
//
// There is NO hardcoded governance address. A single hardcoded EOA (the prior
// DefaultDAOTreasury) was mnemonic-derivable, so anyone with that mnemonic could halt
// an asset (censor) or halt globally (DoS the whole DEX). The authority is now a
// per-network governance CONTRACT (Governor/Timelock/multisig), configured by the host
// from its deployment topology, NEVER a dev-mnemonic EOA. When a network has not
// configured a governance controller, the halt/seed ops are fail-closed (uncallable) —
// strictly safer than a default key, and permissionless/decentralized by construction.
// settle_module.go registers 0x9999 — THE production DEX settlement precompile —
// as its own module (orthogonal: a new primitive at its own address, not bolted
@@ -31,8 +34,8 @@ var DefaultDAOTreasury = common.HexToAddress("0x9011E888251AB053B7bD1cdB598Db4f9
// ABI is byte-for-byte unchanged; web/mobile change only the config address.
// - the existing custody/admin selectors (deposit/withdraw/balanceOf, pause/
// freeze) so a node serving 0x9999 has the full surface in one place.
// - settlement governance: register a validator set, set halt layers (gated on
// the same protocolFeeController authority as the existing pause controls).
// - settlement governance: set halt layers + seed pots (gated on the per-network
// DEX governance controller resolved from the runtime AtomicState, not a key).
//
// 0x9010 was removed; 0x9999 is the sole DEX precompile — one money path, one
// replay namespace (dex.precompile.v1.9999.*), never two.
@@ -56,12 +59,13 @@ var settleConfigKey = "dexSettleConfig"
// (both bind self = 0x9999) pass.
var ErrSettleWrongContext = errors.New("dex: 0x9999 must be entered with self == 0x9999 (CALL/CALLCODE); DELEGATECALL from a delegating contract is rejected")
// SettleContract is the 0x9999 stateful precompile. It holds the same
// protocolFeeController authority value as the 0x9010 contract (set at Configure)
// so halt/registry governance is gated identically.
type SettleContract struct {
protocolFeeController common.Address
}
// SettleContract is the 0x9999 stateful precompile. It is STATELESS with respect to
// the governance authority: halt/seed governance is gated to the per-network DEX
// governance controller resolved at RUNTIME from contract.AtomicState (see
// governanceController in halt9999.go), never a field set from config or a hardcoded
// key. So a single process-global SettlePrecompile serves every network correctly and
// no compromised default key exists on the struct.
type SettleContract struct{}
var _ contract.StatefulPrecompiledContract = (*SettleContract)(nil)
@@ -101,7 +105,9 @@ var (
// with ZERO per-net configuration. There is exactly ONE activation way and this is it.
//
// All parameters 0x9999 needs are resolved at RUNTIME, not from config:
// - protocolFeeController: the built-in DefaultDAOTreasury (set in init below).
// - governanceController: the per-network DEX governance CONTRACT, resolved from the
// host's atomic capability (contract.AtomicState.GovernanceController()). There is
// NO hardcoded default; an unconfigured network is fail-closed (halt/seed revert).
// - networkID / cChainID / dChainID: from the host's atomic capability
// (contract.AtomicState, sourced from the consensus context — see
// native_dchain_client.go). dChainID is the dexvm "D" alias resolved by the host;
@@ -130,10 +136,11 @@ func init() {
SelectorSeedSeamReserve = keccak4("seedSeamReserve(address,uint256)")
SelectorCreditPositionFee = keccak4("creditPositionFee(bytes32,address,uint256)")
// The settlement governance authority is the built-in DAO treasury — fixed across
// every network (always-on, zero per-net config). This is the SOLE source of
// s.protocolFeeController; there is no Configure step that could set it from genesis.
SettlePrecompile.protocolFeeController = DefaultDAOTreasury
// No governance authority is set here: it is NOT a hardcoded key. The settlement
// governance authority is the per-network DEX governance controller, resolved at
// runtime from contract.AtomicState.GovernanceController() (see governanceController
// in halt9999.go). A single process-global SettlePrecompile thus serves every network
// correctly, with no compromised default key on the struct.
if err := modules.RegisterModule(SettleModule); err != nil {
panic(err)
@@ -144,10 +151,10 @@ func (*settleConfigurator) MakeConfig() precompileconfig.Config { return &Settle
// Configure is a no-op for the always-on 0x9999 precompile. It is never invoked by the
// host (an AlwaysOn module has no activating config), and 0x9999 takes no per-net
// parameters — protocolFeeController is the built-in DAO treasury (set in init) and the
// chain identity is resolved at runtime from the atomic capability. The method exists
// solely to satisfy the contract.Configurator interface. It validates the config type
// so a misuse surfaces loudly rather than silently.
// parameters — the governance controller and chain identity are both resolved at runtime
// from the atomic capability (contract.AtomicState). The method exists solely to satisfy
// the contract.Configurator interface. It validates the config type so a misuse surfaces
// loudly rather than silently.
func (s *settleConfigurator) Configure(
_ precompileconfig.ChainConfig,
cfg precompileconfig.Config,
@@ -182,7 +189,7 @@ func (c *SettleConfig) Equal(cfg precompileconfig.Config) bool {
// Run dispatches the 0x9999 surface. The swap selector is the money path
// (SettleSwap); custody/admin selectors reuse the 0x9010 handlers via the shared
// pool manager; governance selectors gate on protocolFeeController.
// pool manager; governance selectors gate on the runtime governance controller.
func (s *SettleContract) Run(
accessibleState contract.AccessibleState,
caller common.Address,
@@ -313,9 +320,10 @@ func (s *SettleContract) Run(
case SelectorSwapCancel:
return s.runSwapCancel(accessibleState, caller, data, suppliedGas, readOnly)
// Settlement governance (protocolFeeController-gated). Only the real kill
// switches remain (global / market / asset); the BLS-era validator-set rotation
// and cert-type halt are gone with the cert value path.
// Settlement governance (governance-controller-gated, resolved from runtime
// AtomicState — never a hardcoded key). Only the real kill switches remain
// (global / market / asset); the BLS-era validator-set rotation and cert-type
// halt are gone with the cert value path.
case SelectorSetHaltGlobal:
return s.runSetHaltGlobal(accessibleState, caller, data, suppliedGas, readOnly)
case SelectorSetHaltMarket:
@@ -323,7 +331,7 @@ func (s *SettleContract) Run(
case SelectorSetHaltAsset:
return s.runSetHaltScoped(accessibleState, caller, data, suppliedGas, readOnly, SetHaltAsset)
// Operator funding of the settlement pots (protocolFeeController-gated). The swap
// Operator funding of the settlement pots (governance-controller-gated). The swap
// rail's seamReserve counterparty seed (FIX-4 liveness: a market's first matched
// swap settles without manual state-poking) and the LP rail's per-owner fee credit.
case SelectorSeedSeamReserve:
@@ -338,7 +346,10 @@ func (s *SettleContract) Run(
const gasHaltAdmin uint64 = 25_000
// runSetHaltGlobal toggles the global halt (governance only).
// runSetHaltGlobal toggles the global halt. Authority is the per-network DEX
// governance controller resolved from the runtime AtomicState (governanceController),
// NOT a hardcoded key: only the configured Governor/Timelock/multisig CONTRACT may
// halt. Fail-closed when no governance controller is configured (ErrSettleNoGovernance).
func (s *SettleContract) runSetHaltGlobal(
state contract.AccessibleState, caller common.Address, data []byte, gas uint64, readOnly bool,
) ([]byte, uint64, error) {
@@ -348,7 +359,11 @@ func (s *SettleContract) runSetHaltGlobal(
if gas < gasHaltAdmin {
return nil, 0, errors.New("dex: out of gas")
}
if caller != s.protocolFeeController {
gov, gerr := governanceController(state)
if gerr != nil {
return nil, gas - gasHaltAdmin, gerr
}
if caller != gov {
return nil, gas - gasHaltAdmin, ErrUnauthorized
}
if len(data) < 32 {
@@ -359,7 +374,9 @@ func (s *SettleContract) runSetHaltGlobal(
return nil, gas - gasHaltAdmin, nil
}
// runSetHaltScoped toggles a [32]byte-scoped halt (market/asset/validatorSet).
// runSetHaltScoped toggles a [32]byte-scoped halt (market/asset). Authority is the
// per-network DEX governance controller (governanceController), never a hardcoded key
// — the same decentralized gate as the global halt; fail-closed when unset.
func (s *SettleContract) runSetHaltScoped(
state contract.AccessibleState, caller common.Address, data []byte, gas uint64, readOnly bool,
apply func(stateKV, [32]byte, bool),
@@ -370,7 +387,11 @@ func (s *SettleContract) runSetHaltScoped(
if gas < gasHaltAdmin {
return nil, 0, errors.New("dex: out of gas")
}
if caller != s.protocolFeeController {
gov, gerr := governanceController(state)
if gerr != nil {
return nil, gas - gasHaltAdmin, gerr
}
if caller != gov {
return nil, gas - gasHaltAdmin, ErrUnauthorized
}
if len(data) < 64 {
+16 -4
View File
@@ -64,7 +64,9 @@ var (
// runSeedSeamReserve funds seamReserve[asset] from operator-delivered value. Native:
// observed-delta (the host frame moved msg.value into 0x9999; delivered = realBal
// Σpots), exactly the deposit discipline so a value==0 seed delivers 0 and reverts.
// ERC-20: transferFrom observed delta. protocolFeeController-gated.
// ERC-20: transferFrom observed delta. Gated to the per-network DEX governance
// controller (governanceController), resolved from the runtime AtomicState — never a
// hardcoded key; fail-closed when no governance controller is configured.
func (s *SettleContract) runSeedSeamReserve(
state contract.AccessibleState, caller common.Address, input []byte, gas uint64, readOnly bool,
) ([]byte, uint64, error) {
@@ -75,7 +77,11 @@ func (s *SettleContract) runSeedSeamReserve(
return nil, 0, errors.New("dex: out of gas")
}
gasLeft := gas - gasSeedReserve
if caller != s.protocolFeeController {
gov, gerr := governanceController(state)
if gerr != nil {
return nil, gasLeft, gerr
}
if caller != gov {
return nil, gasLeft, ErrUnauthorized
}
asset, amount, perr := decodeAssetAmount(input)
@@ -105,7 +111,9 @@ func (s *SettleContract) runSeedSeamReserve(
// 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
// is backed by deposited value). protocolFeeController-gated.
// is backed by deposited value). Gated to the per-network DEX governance controller
// (governanceController), resolved from the runtime AtomicState — never a hardcoded
// key; fail-closed when no governance controller is configured.
func (s *SettleContract) runCreditPositionFee(
state contract.AccessibleState, caller common.Address, input []byte, gas uint64, readOnly bool,
) ([]byte, uint64, error) {
@@ -116,7 +124,11 @@ func (s *SettleContract) runCreditPositionFee(
return nil, 0, errors.New("dex: out of gas")
}
gasLeft := gas - gasSeedReserve
if caller != s.protocolFeeController {
gov, gerr := governanceController(state)
if gerr != nil {
return nil, gasLeft, gerr
}
if caller != gov {
return nil, gasLeft, ErrUnauthorized
}
if len(input) < 96 {
+6 -5
View File
@@ -21,11 +21,12 @@ import (
// "may value move?" into ONE thing: a normal swap() reaches here and the swap's own
// fail-closed controls — intrinsic to the swap, enforced on EVERY call — decide whether it
// fills:
// - real-asset registry admission (OpenMarketChecked: the installed resolver), which
// fails closed (ErrNoAssetResolver) on a node that never wired a resolver, so a node
// that cannot prove an asset real simply cannot fill — replacing the old consensus-mode
// gate with a structural one;
// - the live-code verifier (EXTCODESIZE on the token address);
// - permissionless canonical admission (OpenMarketChecked: the installed resolver derives
// each side's canonical identity on the bound network — any well-formed real reference
// resolves, no allowlist), which fails closed (ErrNoAssetResolver) on a node that never
// wired a resolver — replacing the old consensus-mode gate with a structural one;
// - the live-code verifier (EXTCODESIZE on the token address) — the AUTHORITATIVE reality
// check that admits any real on-chain ERC-20 and refuses a synthetic/code-less one;
// - the per-swap min-out / price floor (slippage protection);
// - the halt switches (global/market/asset);
// - the single non-reentrant custody mutex (an ERC-20 transfer can re-enter).