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

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

112 lines
5.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package dex
import (
"math/big"
"github.com/holiman/uint256"
"github.com/luxfi/geth/common"
"github.com/luxfi/ids"
)
// native_state.go holds the DURABLE state of the C<->D funding rail: the C->D
// written-claim set, the D->C consumed-claim set, and custody. All live under the
// 0x9999 settlement namespace
// (dex.precompile.v1.9999.*) so the money path's state is one auditable region,
// and all are content-addressed by the cross-chain object id so a re-executed /
// reorged block replays identically on every validator (consensus-shared,
// durable, reverted atomically with the tx).
// --- D-Chain target id: the peer chain the C->D objects are PUT under and the D->C
// objects are read from. It is NOT configured and NOT persisted — 0x9999 is always-on
// with zero per-net config. The host resolves the D-Chain (dexvm) id at RUNTIME from
// the chain topology (the consensus context's blockchain-alias lookup of "D"), surfaced
// to the precompile via contract.AtomicState.DChainID(). ids.Empty (no dexvm on this
// network) closes the on-ramp; the client refuses to move value rather than guess a peer.
// --- C->D written set: a claim id is written at most once. Keyed by the claim id
// (== the object's shared-memory key), value = blockNumber+1 sentinel.
var claimWrittenPrefix = []byte(settleStateNamespace + "written")
func claimWrittenKey(claimID ids.ID) common.Hash {
return makeStorageKey(claimWrittenPrefix, claimID[:])
}
func isClaimWritten(stateDB stateKV, claimID ids.ID) bool {
return stateDB.GetState(poolManagerAddr9999, claimWrittenKey(claimID)) != (common.Hash{})
}
func markClaimWritten(stateDB stateKV, claimID ids.ID, blockNumber uint64) {
var v common.Hash
uint256.NewInt(blockNumber + 1).WriteToSlice(v[:])
stateDB.SetState(poolManagerAddr9999, claimWrittenKey(claimID), v)
}
// --- D->C consumed set: a claim is consumed at most once. Keyed by the claim id,
// value = blockNumber+1 sentinel. This is the exactly-once property the whole rail
// rests on: the mark and the credit land together or neither does.
var claimConsumedPrefix = []byte(settleStateNamespace + "consumed")
func claimConsumedKey(claimID ids.ID) common.Hash {
return makeStorageKey(claimConsumedPrefix, claimID[:])
}
func isClaimConsumed(stateDB stateKV, claimID ids.ID) bool {
return stateDB.GetState(poolManagerAddr9999, claimConsumedKey(claimID)) != (common.Hash{})
}
func markClaimConsumed(stateDB stateKV, claimID ids.ID, blockNumber uint64) {
var v common.Hash
uint256.NewInt(blockNumber + 1).WriteToSlice(v[:])
stateDB.SetState(poolManagerAddr9999, claimConsumedKey(claimID), v)
}
// --- Custody: the funding rail's per-asset pot inside the 0x9999 vault, tracked
// SEPARATELY from the depositor pot (settleVault). The two are distinct CLAIMS on the
// same vault account and must not raid each other:
//
// custody[a] = operator seed of a
// + Σ value exported to D (C->D claims written)
// − Σ value imported from D (D->C claims consumed)
// settleVault[a] = Σ depositorClaim[*][a] (the depositor pot)
//
// THE VAULT-ACCOUNT INVARIANT (per asset a):
//
// GetBalance(0x9999)/ERC20 holding of a == settleVault[a] + custody[a]
//
// TWO TERMS, NOT THREE. A maker's committed value is not a third claim: commitPosition
// funds D through Export, which debits the caller and credits custody[a], so it is
// already inside custody. lockedReserve[owner][a] is a per-owner VIEW of that same
// value for bookkeeping, never a separate holding — adding it to the invariant would
// double-count it, and any balance-difference read that subtracts it refuses honest
// deposits. deliveredNative (native_zap.go) is the one reader of this identity.
//
// An import draws ONLY on custody[a], so it can never pay out of a depositor's claim;
// a withdraw draws ONLY on settleVault[a], so it can never strand a backed import.
//
// THERE IS ONE CUSTODY POT, NOT ONE PER PRODUCT. It used to be two — a swap pot and
// an LP pot — and the object carried a lane byte saying which one a claim could
// draw. That byte was the only thing keeping the two apart, and it made the funding
// rail know what the value was FOR. A rail that moves ownership does not: "value C
// holds because D owns it" is one quantity per asset, so it is one number. Splitting
// it created the cross-lane drain the byte then had to defend against.
var custodyPrefix = []byte(settleStateNamespace + "cust") // custody[asset]
func custodyKey(assetID [32]byte) common.Hash {
return makeStorageKey(custodyPrefix, assetID[:])
}
func loadCustody(stateDB stateKV, assetID [32]byte) *big.Int {
return new(big.Int).SetBytes(stateDB.GetState(poolManagerAddr9999, custodyKey(assetID)).Bytes())
}
func storeCustody(stateDB stateKV, assetID [32]byte, amount *big.Int) {
var w common.Hash
if amount != nil && amount.Sign() > 0 {
amount.FillBytes(w[:])
}
stateDB.SetState(poolManagerAddr9999, custodyKey(assetID), w)
}