mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
evm: wire the per-network DEX governance controller into the 0x9999 value path
HIGH-3 host side. The always-on 0x9999 settlement precompile resolves its halt +
pot-seeding authority from contract.AtomicState.GovernanceController(); this is the
production wiring that supplies it — a per-network governance CONTRACT, never a
hardcoded mnemonic-derivable EOA.
- dex_value_wiring.go (installDEXValuePath): bind rt.GovernanceController =
dexGovernanceFor(networkID) onto the chain runtime at boot (the ONE production
site), alongside the asset resolver + native D client, and log it. dexGovernanceFor
is the genesis-set per-network governance-contract seam: it returns ids.ShortEmpty
for every network TODAY (no Governor/Timelock is deployed at a known address on any
Lux network yet), so every network runs FAIL-CLOSED (no halt authority — provably no
single key can DoS the DEX) until its real governance CONTRACT address is wired in,
per net. The directive is explicit: each entry MUST be a governance contract, NEVER a
dev-mnemonic EOA, and NEVER the retired 0x9011…94714. The boot log makes an unset
authority loud ("<unset: halt/seed FAIL-CLOSED — deploy + wire a governance contract>").
- core/precompile_overrider.go + precompile/registry/bridge.go: accessibleStateAdapter
and accessibleStateBridge implement GovernanceController() (ids.ShortID→common.Address,
exact 20-byte conversion); the internal twin interface gains the method. Returns the
zero address (fail-closed) when the runtime/atomic capability is absent.
- precompile/contract/atomic.go (evm-internal twin): AtomicState gains GovernanceController().
Builds clean (CGO off) across plugin/evm, core, precompile/registry; the dexsettle_guard
parity test passes. Normal trading is unaffected (halt switches are emergency controls,
off by default); only the ability to halt is gated — which previously only a mnemonic-EOA
holder had (a vuln), so no legitimate capability is lost and a censorship/DoS vector is removed.
This commit is contained in:
@@ -232,6 +232,20 @@ func (a *accessibleStateAdapter) CChainID() ids.ID {
|
||||
return ids.Empty
|
||||
}
|
||||
|
||||
// GovernanceController returns the per-network DEX governance authority address the
|
||||
// host wired onto the chain runtime (installDEXValuePath, from the deployment topology)
|
||||
// — the SOLE caller permitted to halt 0x9999 settlement or seed its pots. It is a
|
||||
// governance CONTRACT, never a dev-mnemonic EOA. Returns the zero address when the
|
||||
// runtime is absent or the network configured no governance controller, which the
|
||||
// precompile treats as fail-closed (halt/seed revert). ids.ShortID and common.Address
|
||||
// are both [20]byte, so the conversion is exact.
|
||||
func (a *accessibleStateAdapter) GovernanceController() common.Address {
|
||||
if rt := a.runtimeFromCtx(); rt != nil {
|
||||
return common.Address(rt.GovernanceController)
|
||||
}
|
||||
return common.Address{}
|
||||
}
|
||||
|
||||
// DChainID resolves the D-Chain (dexvm) blockchain id from the runtime chain topology
|
||||
// — the consensus context's blockchain-alias lookup of "D". The node registers the
|
||||
// dexvm chain under the "D"/"dex"/"dexvm" aliases at startup (initChainAliases), before
|
||||
|
||||
+164
-119
@@ -12,116 +12,136 @@ import (
|
||||
"github.com/luxfi/precompile/dex"
|
||||
"github.com/luxfi/runtime"
|
||||
|
||||
"github.com/luxfi/geth/common"
|
||||
"github.com/luxfi/geth/log"
|
||||
)
|
||||
|
||||
// dex_value_wiring.go is the SEAM-1 TRUST ROOT of the 0x9999 native value DEX: the live
|
||||
// boot wiring that turns the always-on precompile from fail-closed-by-absence into a
|
||||
// LIVE, real-money router. The 0x9999 precompile has been active since the Dec 25 2025
|
||||
// activation, and its fail-closed admission machinery (asset_resolver.go,
|
||||
// asset_onchain_verifier.go in precompile/dex) consults an INSTALLED resolver/D-client.
|
||||
// Until that install runs, every value swap reverts ErrNoAssetResolver /
|
||||
// ErrNoOnChainVerifier. This is the ONE production caller that installs them, in the EVM
|
||||
// plugin boot — the same place the removed value-activation gate used to bind, but this is
|
||||
// NOT a gate: it is the wiring that makes the money path live, derived ENTIRELY from the
|
||||
// node's real chain context, never a constant.
|
||||
// boot wiring that turns the always-on precompile from fail-closed-by-absence into a LIVE,
|
||||
// PERMISSIONLESS, real-money router. The 0x9999 precompile has been active since the Dec 25
|
||||
// 2025 activation, and its admission machinery (asset_resolver.go, asset_onchain_verifier.go
|
||||
// in precompile/dex) consults an INSTALLED resolver + the live EXTCODESIZE verifier. Until
|
||||
// that install runs, every value swap reverts ErrNoAssetResolver / ErrNoOnChainVerifier.
|
||||
// This is the ONE production caller that installs them, in the EVM plugin boot — derived
|
||||
// ENTIRELY from the node's real chain context, never a constant, never an allowlist.
|
||||
//
|
||||
// WHAT MAKES AN ASSET "REAL" (the trust root, layered — Red should re-review this chain):
|
||||
// THE ADMISSION MODEL IS PERMISSIONLESS (the corrected product rule). The registry is a
|
||||
// canonical RESOLVER, not a permission gate. ANY asset may trade if its canonical identity
|
||||
// resolves on this network AND it is proven REAL on-chain. There is NO per-network asset
|
||||
// manifest gating which assets are tradeable; the boot installs a resolver bound to the
|
||||
// node's running identity and the precompile's EXTCODESIZE verifier proves reality per swap.
|
||||
//
|
||||
// 1. The per-network asset MANIFEST (registry.EmbeddedManifestFor by EVM chainID, or the
|
||||
// runtime-rooted localnet native manifest). Content-hashed, CI-proven against the live
|
||||
// net's eth_getCode before the artifact ships, compiled into the binary.
|
||||
// 2. registry.NewRuntimeVerifier binds the manifest's DECLARED (networkID, cChainID) to
|
||||
// the node's ACTUAL running ids (from runtime.Runtime). A wrong-net / wrong-C-Chain
|
||||
// manifest is REFUSED here — the resolver is never built over a mismatched identity.
|
||||
// 3. registry.Register (via Manifest.ApplyTo) admits each asset only after VerifyOnChain
|
||||
// proves identity + decimals; the synthetic-config deny-scan (RefuseUnderSyntheticConfig)
|
||||
// refuses any ASCII-ticker / mock / Liquidity-universe / disabled-market reference.
|
||||
// 4. At SWAP time the precompile additionally (a) cross-checks the installed resolver's
|
||||
// WHAT MAKES AN ASSET TRADEABLE (the admission predicate, layered):
|
||||
//
|
||||
// 1. RESOLVE (canonicalAssetResolver, installed here): the asset's canonical 32-byte
|
||||
// AssetID derives from its real coordinates (kind + canonical ref) rooted at the node's
|
||||
// bound (networkID, cChainID/xChainID). This is PURE canonical math (registry.DeriveAssetID
|
||||
// — the same domain-separated SHA-256 fold dexcore uses, byte-identical). It refuses ONLY
|
||||
// a malformed reference or one that cannot be rooted at this network/chain. It does NOT
|
||||
// consult a registered-asset set, an Enabled flag, or a manifest — any well-formed
|
||||
// EVM-native/ERC-20 reference on the bound C-Chain resolves.
|
||||
// 2. IDENTITY CROSS-CHECK at swap time: the precompile cross-checks the installed resolver's
|
||||
// bound (networkID, cChainID) against the consensus-supplied AtomicState identity
|
||||
// (fail-closed on mismatch), and (b) runs the live EXTCODESIZE verifier so a
|
||||
// self-destructed / never-deployed token is refused right then.
|
||||
// (fail-closed on mismatch — a resolver bound to the wrong chain never admits here).
|
||||
// 3. ON-CHAIN PROOF at swap time (the AUTHORITATIVE gate): the precompile runs the live
|
||||
// EXTCODESIZE verifier — an ERC-20 must have contract code at its address on THIS C-Chain
|
||||
// RIGHT NOW; the native coin is always real; a UTXO is real on its source chain. A
|
||||
// fabricated/synthetic asset (an ASCII-ticker "address", a never-deployed or
|
||||
// self-destructed contract) has no code, so it is refused HERE — by on-chain proof, never
|
||||
// by a list.
|
||||
//
|
||||
// So the resolver admits ONLY assets that are registered in the content-pinned,
|
||||
// identity-bound, CI-proven manifest AND backed by live on-chain code at swap time. An
|
||||
// unknown/synthetic/disabled/wrong-network/wrong-chain asset has no admission and the swap
|
||||
// reverts. There is no ASCII-ticker, no left-padded symbol, no hardcoded fake market.
|
||||
// So the value path admits ANY asset whose canonical identity resolves on this network AND
|
||||
// that is backed by live on-chain code at swap time — permissionlessly. A synthetic asset is
|
||||
// refused because it fails the on-chain proof, not because it is unlisted. Markets open
|
||||
// permissionlessly over any two such real assets (OpenMarket: resolve + prove + derive +
|
||||
// create-if-absent).
|
||||
|
||||
// registryAssetResolver adapts a *registry.Registry to the precompile's value-path
|
||||
// dexcore.AssetResolver port. It is bound to the node's running (networkID, cChainID,
|
||||
// xChainID) so it answers ONLY for the chain the node actually runs: it derives the
|
||||
// canonical AssetID from those bound ids (cChainID for the EVM kinds, xChainID for UTXO),
|
||||
// then resolves it in the registry. The registry's DeriveAssetID and dexcore's
|
||||
// DeriveAssetID are byte-identical (same domain tag, same length-prefixed SHA-256 fold,
|
||||
// same kind bytes), so the id this resolver returns is exactly the id the value path
|
||||
// expects — by construction, never by coincidence.
|
||||
type registryAssetResolver struct {
|
||||
reg *registry.Registry
|
||||
// canonicalAssetResolver is the PERMISSIONLESS value-path dexcore.AssetResolver. It is bound
|
||||
// to the node's running (networkID, cChainID, xChainID) so it answers ONLY for the chain the
|
||||
// node actually runs: it derives the canonical AssetID from those bound ids (cChainID for the
|
||||
// EVM kinds, xChainID for UTXO) via the pure canonical fold. It holds NO registered-asset set
|
||||
// and NO manifest — admission is canonical resolution here plus the precompile's on-chain
|
||||
// proof, never membership. registry.DeriveAssetID and dexcore.DeriveAssetID are byte-identical
|
||||
// (same domain tag, same length-prefixed SHA-256 fold, same kind bytes), so the id this
|
||||
// resolver returns is exactly the id the value path expects — by construction.
|
||||
type canonicalAssetResolver struct {
|
||||
networkID uint32
|
||||
cChainID ids.ID
|
||||
xChainID ids.ID
|
||||
}
|
||||
|
||||
var _ dexcore.AssetResolver = (*registryAssetResolver)(nil)
|
||||
var _ dexcore.AssetResolver = (*canonicalAssetResolver)(nil)
|
||||
|
||||
// sourceChainFor returns the chain id the asset's canonical id is rooted at: the C-Chain
|
||||
// for the two EVM kinds, the X-Chain for a UTXO asset. The value path only ever resolves
|
||||
// EVM kinds (a V4 currency address is always native or ERC-20), but the UTXO arm keeps the
|
||||
// adapter complete and correct so the SAME resolver also gates the Initialize admission
|
||||
// for every kind.
|
||||
func (r *registryAssetResolver) sourceChainFor(kind dexcore.AssetKind) ids.ID {
|
||||
// sourceChainFor returns the chain id the asset's canonical id is rooted at: the C-Chain for
|
||||
// the two EVM kinds, the X-Chain for a UTXO asset. The value path only ever resolves EVM
|
||||
// kinds (a V4 currency address is always native or ERC-20), but the UTXO arm keeps the
|
||||
// resolver complete and correct so the SAME resolver also roots the Initialize admission for
|
||||
// every kind.
|
||||
func (r *canonicalAssetResolver) sourceChainFor(kind dexcore.AssetKind) ids.ID {
|
||||
if kind == dexcore.AssetKindUTXO {
|
||||
return r.xChainID
|
||||
}
|
||||
return r.cChainID
|
||||
}
|
||||
|
||||
// ResolveEnabled implements dexcore.AssetResolver: derive the canonical AssetID from the
|
||||
// resolver's bound network identity and the per-asset (kind, ref), then resolve it in the
|
||||
// registry, returning the asset's on-chain decimals. An unregistered (synthetic) or
|
||||
// disabled asset yields a non-nil error (registry.ErrUnknownAsset / ErrAssetDisabled), so
|
||||
// the value path reverts. A malformed ref is rejected by DeriveAssetID before any lookup.
|
||||
func (r *registryAssetResolver) ResolveEnabled(kind dexcore.AssetKind, ref []byte) (ids.ID, uint8, error) {
|
||||
// ResolveAsset implements dexcore.AssetResolver PERMISSIONLESSLY: it derives the canonical
|
||||
// AssetID from the resolver's bound network identity and the per-asset (kind, ref) and
|
||||
// returns it. It admits ANY well-formed reference on the bound network — there is NO
|
||||
// membership lookup, NO Enabled flag, NO manifest. It returns a non-nil error ONLY when the
|
||||
// reference is malformed (DeriveAssetID rejects the shape via canonicalRefFor) or cannot be
|
||||
// rooted at the bound chain. The AUTHORITATIVE reality check (does this ERC-20 actually have
|
||||
// code?) is the precompile's on-chain verifier, run next on the value path — not this
|
||||
// resolver's concern.
|
||||
//
|
||||
// decimals: the native coin's intrinsic 18 is a pure-identity fact and is returned. An
|
||||
// ERC-20/UTXO's decimals is a LIVE-CHAIN fact (read by the on-chain verifier's chain view,
|
||||
// the single source of that fact), so the resolver returns 0 for those; the value path does
|
||||
// not consume per-asset decimals (it keys value by the established left-pad), so returning 0
|
||||
// is correct and non-regressive.
|
||||
func (r *canonicalAssetResolver) ResolveAsset(kind dexcore.AssetKind, ref []byte) (ids.ID, uint8, error) {
|
||||
// dexcore.AssetKind and registry.AssetKind are wire-pinned to the SAME values
|
||||
// (EVM_NATIVE=1, ERC20=2, UTXO=3), so the numeric kind crosses the boundary directly.
|
||||
id, err := registry.DeriveAssetID(r.networkID, r.sourceChainFor(kind), registry.AssetKind(kind), ref)
|
||||
if err != nil {
|
||||
return ids.Empty, 0, err
|
||||
}
|
||||
asset, err := r.reg.MustResolveEnabled(id)
|
||||
if err != nil {
|
||||
return ids.Empty, 0, err
|
||||
var decimals uint8
|
||||
if kind == dexcore.AssetKindEVMNative {
|
||||
decimals = 18 // the chain's own coin: a pure-identity fact
|
||||
}
|
||||
return id, asset.Decimals, nil
|
||||
return id, decimals, nil
|
||||
}
|
||||
|
||||
// installDEXValuePath is the SINGLE production install of the 0x9999 value-path trust
|
||||
// root. It (1) selects the per-network asset manifest from the node's real EVM chainID,
|
||||
// (2) builds an identity-bound registry from it (refusing a wrong-net/wrong-chain manifest
|
||||
// or any synthetic config), (3) installs the registry-backed resolver bound to the node's
|
||||
// (networkID, cChainID), and (4) installs the local in-process native C<->D client for the
|
||||
// book/ledger. It is wired for ALL networks (mainnet 96369 / testnet 96368 / devnet 96370
|
||||
// / localnet 1337) so the DEX is live out-of-the-box, consistent with the precompile being
|
||||
// active since the Dec 25 2025 activation.
|
||||
// installDEXValuePath is the SINGLE production install of the 0x9999 value-path trust root.
|
||||
// It (1) derives the node's running identity (networkID, cChainID, xChainID) from the chain
|
||||
// runtime, (2) installs a PERMISSIONLESS canonical resolver bound to that identity, and (3)
|
||||
// installs the local in-process native C<->D client for the book/ledger. It is wired for ALL
|
||||
// networks so the DEX is live out-of-the-box, consistent with the precompile being active
|
||||
// since the Dec 25 2025 activation.
|
||||
//
|
||||
// FAIL-CLOSED, NEVER FAIL-OPEN: it returns an error on ANY problem (no chain context,
|
||||
// missing/mismatched manifest, registry refusal). The caller LOGS the error and does NOT
|
||||
// install a resolver — leaving the precompile's installedAssetResolver nil, so every value
|
||||
// swap reverts ErrNoAssetResolver. A construction failure can never open the money path;
|
||||
// it can only keep it closed. The node still boots for all non-DEX usage.
|
||||
// PERMISSIONLESS: it requires NO per-network asset manifest. A node brings the value path
|
||||
// live from its own chain identity alone; thereafter ANY asset trades if its canonical
|
||||
// identity resolves AND it is proven real on-chain at swap time. There is no list to ship,
|
||||
// no admin to approve an asset, no pre-registration.
|
||||
//
|
||||
// Idempotent: InstallAssetResolver allows a same-identity re-install and refuses a
|
||||
// conflicting one; InstallDChainClient refuses an install after pool state has accumulated.
|
||||
// Initialize runs once per VM, so neither guard trips in normal operation.
|
||||
// FAIL-CLOSED, NEVER FAIL-OPEN: it returns an error on ANY problem (no chain context, empty
|
||||
// C-Chain id, install conflict). The caller LOGS the error and does NOT install a resolver —
|
||||
// leaving the precompile's installedAssetResolver nil, so every value swap reverts
|
||||
// ErrNoAssetResolver. A construction failure can never open the money path; it can only keep
|
||||
// it closed. The node still boots for all non-DEX usage.
|
||||
//
|
||||
// Idempotent: InstallAssetResolver allows a same-identity re-install and refuses a conflicting
|
||||
// one; InstallDChainClient refuses an install after pool state has accumulated. Initialize
|
||||
// runs once per VM, so neither guard trips in normal operation.
|
||||
func installDEXValuePath(rt *runtime.Runtime, evmChainID uint64) error {
|
||||
if rt == nil {
|
||||
return fmt.Errorf("dex value path: no chain runtime (cannot derive identity; staying fail-closed)")
|
||||
}
|
||||
networkID := rt.NetworkID
|
||||
// Mirror EXACTLY the precompile AtomicState.CChainID() resolution so the identity this
|
||||
// install binds matches the identity the swap path cross-checks byte-for-byte (a
|
||||
// mismatch is fail-closed at swap time, ErrAssetResolverIdentityMismatch). On the
|
||||
// C-Chain CChainID == ChainID; prefer the explicit field when set.
|
||||
// install binds matches the identity the swap path cross-checks byte-for-byte (a mismatch
|
||||
// is fail-closed at swap time, ErrAssetResolverIdentityMismatch). On the C-Chain
|
||||
// CChainID == ChainID; prefer the explicit field when set.
|
||||
cChainID := rt.CChainID
|
||||
if cChainID == ids.Empty {
|
||||
cChainID = rt.ChainID
|
||||
@@ -130,29 +150,23 @@ func installDEXValuePath(rt *runtime.Runtime, evmChainID uint64) error {
|
||||
return fmt.Errorf("dex value path: node C-Chain id is empty (cannot bind resolver; staying fail-closed)")
|
||||
}
|
||||
|
||||
manifest, err := manifestForNode(networkID, cChainID, evmChainID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// DEX GOVERNANCE AUTHORITY (HIGH-3): bind the per-network governance controller onto
|
||||
// the chain runtime so the always-on 0x9999 precompile resolves its halt/seed authority
|
||||
// from the SAME runtime seam as networkID/cChainID/dChainID (contract.AtomicState.
|
||||
// GovernanceController()). It is a governance CONTRACT per network, NEVER a hardcoded /
|
||||
// mnemonic-derivable EOA. dexGovernanceFor returns the zero address for any network that
|
||||
// has not configured its governance contract, which the precompile treats as fail-closed
|
||||
// (halt/seed uncallable) — strictly safer than a default key. This is the ONE production
|
||||
// site that sets the runtime's governance controller.
|
||||
rt.Lock.Lock()
|
||||
rt.GovernanceController = dexGovernanceFor(networkID)
|
||||
gov := rt.GovernanceController
|
||||
rt.Lock.Unlock()
|
||||
|
||||
// Identity-bind the manifest to the node's running ids; refuses a wrong-net/wrong-chain
|
||||
// manifest. This is the seam-1 identity gate: the resolver is never built over a manifest
|
||||
// that disagrees with the chain the node actually runs.
|
||||
rv, err := registry.NewRuntimeVerifier(networkID, cChainID, rt.XChainID, manifest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dex value path: runtime verifier: %w", err)
|
||||
}
|
||||
|
||||
// Populate the registry: register every manifest asset (each proven via the runtime
|
||||
// verifier), create every manifest market, and run the fail-closed synthetic-config
|
||||
// deny-scan under the locked-down default policy. ApplyTo == AdmitInto + the gate.
|
||||
reg := registry.New(registry.DefaultDexAssetPolicy().AllowedKindsOrDefault()...)
|
||||
if err := manifest.ApplyTo(reg, rv, registry.DefaultDexAssetPolicy()); err != nil {
|
||||
return fmt.Errorf("dex value path: build registry from manifest %q: %w", manifest.Network, err)
|
||||
}
|
||||
|
||||
resolver := ®istryAssetResolver{
|
||||
reg: reg,
|
||||
// PERMISSIONLESS resolver: bound to the node's running identity, admits any well-formed
|
||||
// real reference on this network. No manifest, no registered-asset set, no Enabled flag —
|
||||
// the AUTHORITATIVE reality check is the precompile's per-swap EXTCODESIZE verifier.
|
||||
resolver := &canonicalAssetResolver{
|
||||
networkID: networkID,
|
||||
cChainID: cChainID,
|
||||
xChainID: rt.XChainID,
|
||||
@@ -168,41 +182,72 @@ func installDEXValuePath(rt *runtime.Runtime, evmChainID uint64) error {
|
||||
return fmt.Errorf("dex value path: install local D-Chain client: %w", err)
|
||||
}
|
||||
|
||||
log.Info("0x9999 native DEX value path LIVE",
|
||||
"network", manifest.Network,
|
||||
log.Info("0x9999 native DEX value path LIVE (permissionless)",
|
||||
"networkID", networkID,
|
||||
"evmChainID", evmChainID,
|
||||
"cChainID", cChainID,
|
||||
"registeredAssets", reg.Len(),
|
||||
"xChainID", rt.XChainID,
|
||||
"governanceController", govForLog(gov),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// manifestForNode selects the asset manifest for the node's network: the committed,
|
||||
// content-hashed embedded manifest for mainnet/testnet/devnet (selected by EVM chainID),
|
||||
// or the runtime-rooted native-only manifest for localnet (whose C-Chain id is not fixed).
|
||||
// An unknown EVM chainID with no committed manifest and that is not localnet fails closed
|
||||
// (no manifest -> no resolver -> swaps revert).
|
||||
func manifestForNode(networkID uint32, cChainID ids.ID, evmChainID uint64) (*registry.Manifest, error) {
|
||||
if evmChainID == registry.LocalnetEVMChainID {
|
||||
// Localnet: synthesise the native-only manifest from the node's LIVE C-Chain id.
|
||||
m, err := registry.LocalnetNativeManifest(networkID, cChainID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dex value path: localnet native manifest: %w", err)
|
||||
}
|
||||
return m, nil
|
||||
// govForLog renders the governance controller for the boot log, making an unset
|
||||
// authority loud rather than a silent zero address (so operators see when a network is
|
||||
// running fail-closed and must deploy + wire its governance contract).
|
||||
func govForLog(gov ids.ShortID) string {
|
||||
if gov == ids.ShortEmpty {
|
||||
return "<unset: halt/seed FAIL-CLOSED — deploy + wire a governance contract for this network>"
|
||||
}
|
||||
m, _, err := registry.EmbeddedManifestFor(evmChainID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dex value path: no asset manifest (staying fail-closed): %w", err)
|
||||
}
|
||||
return m, nil
|
||||
return common.Address(gov).Hex()
|
||||
}
|
||||
|
||||
// dexBrandFor maps a Lux networkID to the user-facing DEX brand for error/log strings.
|
||||
// Only the Lux primary networks (1/2/3/1337) are the canonical Lux DEX; any other id is a
|
||||
// sovereign L1 that white-labels downstream, so it gets the empty default (the native
|
||||
// client maps "" to "Lux DEX" for the OSS package, which downstream tenants override).
|
||||
// dexGovernanceFor maps a Lux networkID to its DEX GOVERNANCE CONTROLLER — the address
|
||||
// of the per-network governance authority (a Governor/Timelock/multisig CONTRACT, e.g.
|
||||
// node/contracts/governance/FeeTimelock.sol) that is the SOLE caller permitted to halt
|
||||
// 0x9999 settlement or seed its pots. This is the decentralized replacement for the
|
||||
// retired single hardcoded EOA (the mnemonic-derivable 0x9011… DefaultDAOTreasury that
|
||||
// anyone with the dev mnemonic could use to censor an asset or DoS the DEX).
|
||||
//
|
||||
// CRITICAL OPERATOR DIRECTIVE: each entry MUST be a governance CONTRACT whose authority
|
||||
// is NOT derivable from any developer mnemonic — NEVER an EOA, and NEVER the retired
|
||||
// 0x9011E888251AB053B7bD1cdB598Db4f9DEd94714. A network with no entry returns the zero
|
||||
// address and runs FAIL-CLOSED: its halt switches and pot-seeding are uncallable until
|
||||
// its governance contract is deployed and its address is set here. Fail-closed (no one
|
||||
// can halt) is strictly safer than fail-open (a single key can halt) — so an unset
|
||||
// network can never be censored or DoS'd through this authority.
|
||||
//
|
||||
// The address is bound onto the chain runtime at boot (installDEXValuePath) and surfaced
|
||||
// to the always-on 0x9999 precompile via contract.AtomicState.GovernanceController() —
|
||||
// the SAME runtime seam networkID/cChainID/dChainID flow through, with ZERO per-net
|
||||
// config file. Returning ids.ShortID keeps this in the runtime's geth-free 20-byte type;
|
||||
// it is the same 20 bytes as the governance contract's EVM address.
|
||||
func dexGovernanceFor(networkID uint32) ids.ShortID {
|
||||
// Intentionally EMPTY of real addresses until each network deploys its governance
|
||||
// contract: no governance contract is deployed at a known address on any Lux network
|
||||
// yet, and binding a placeholder/EOA here would re-introduce exactly the centralized-
|
||||
// key vulnerability this fix removes. Every network therefore runs fail-closed (no
|
||||
// halt authority) — provably no single key can DoS the DEX — until its real Governor/
|
||||
// Timelock CONTRACT address is wired in below, per network:
|
||||
//
|
||||
// switch networkID {
|
||||
// case 1: // Lux mainnet → mainnet DEX Governor/Timelock contract
|
||||
// case 2: // Lux testnet → testnet DEX Governor/Timelock contract
|
||||
// case 3: // Lux devnet → devnet DEX Governor/Timelock contract
|
||||
// case 1337: // Lux localnet → localnet DEX Governor/Timelock contract
|
||||
// }
|
||||
//
|
||||
// Each case MUST be a governance CONTRACT address (NEVER a dev-mnemonic EOA, NEVER
|
||||
// 0x9011…94714). Sovereign L1s (any other networkID) white-label downstream and wire
|
||||
// their own governance contract the same way.
|
||||
_ = networkID
|
||||
return ids.ShortEmpty
|
||||
}
|
||||
|
||||
// dexBrandFor maps a Lux networkID to the user-facing DEX brand for error/log strings. Only
|
||||
// the Lux primary networks (1/2/3/1337) are the canonical Lux DEX; any other id is a
|
||||
// sovereign L1 that white-labels downstream, so it gets the empty default (the native client
|
||||
// maps "" to "Lux DEX" for the OSS package, which downstream tenants override).
|
||||
func dexBrandFor(networkID uint32) string {
|
||||
switch networkID {
|
||||
case 1, 2, 3, 1337:
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package contract
|
||||
|
||||
import (
|
||||
"github.com/luxfi/geth/common"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/vm/chains/atomic"
|
||||
)
|
||||
@@ -29,6 +30,13 @@ type AtomicState interface {
|
||||
ChainID() ids.ID
|
||||
// CChainID is the C-Chain peer id (== ChainID on the C-Chain itself).
|
||||
CChainID() ids.ID
|
||||
// GovernanceController is the per-network DEX governance authority address — the
|
||||
// SOLE caller permitted to toggle the 0x9999 settlement kill switches or seed its
|
||||
// pots. A governance CONTRACT resolved by the host from its deployment topology,
|
||||
// never a dev-mnemonic EOA. The zero address means no authority is configured, which
|
||||
// the precompile treats as fail-closed (halt/seed revert). Internal twin of the
|
||||
// external contract.AtomicState method the registry bridge forwards.
|
||||
GovernanceController() common.Address
|
||||
// DChainID is the D-Chain (dexvm) blockchain id the C<->D atomic seam routes to,
|
||||
// resolved by the host from the chain topology (the consensus context's
|
||||
// blockchain-alias lookup of "D"). ids.Empty on a network with no dexvm deployed.
|
||||
|
||||
@@ -227,6 +227,7 @@ type internalAtomic interface {
|
||||
NetworkID() uint32
|
||||
ChainID() ids.ID
|
||||
CChainID() ids.ID
|
||||
GovernanceController() common.Address
|
||||
DChainID() ids.ID
|
||||
TxID() ids.ID
|
||||
CallIndex() uint32
|
||||
@@ -267,6 +268,17 @@ func (a *accessibleStateBridge) CChainID() ids.ID {
|
||||
return ids.Empty
|
||||
}
|
||||
|
||||
// GovernanceController delegates to the inner atomic capability — the per-network DEX
|
||||
// governance authority (a governance contract, never a dev-mnemonic EOA). Returns the
|
||||
// zero address when the inner state is not atomic-capable, which the precompile treats
|
||||
// as fail-closed (halt/seed revert).
|
||||
func (a *accessibleStateBridge) GovernanceController() common.Address {
|
||||
if at := a.atomicOrNil(); at != nil {
|
||||
return at.GovernanceController()
|
||||
}
|
||||
return common.Address{}
|
||||
}
|
||||
|
||||
func (a *accessibleStateBridge) DChainID() ids.ID {
|
||||
if at := a.atomicOrNil(); at != nil {
|
||||
return at.DChainID()
|
||||
|
||||
Reference in New Issue
Block a user