19 Commits
Author SHA1 Message Date
zeekayandHanzo Dev 84587ddb0f Resolve the market before a swap can name a halt
SettleSwap took the caller's PoolKey and never asked whether that pool
existed, so key.ID() fed the halt slot, the volume shard and the fill
event alike. A fabricated key names a slot nobody set, and the swap
settles while the real market is halted.

Resolving once above the phase switch and passing the resolved poolID
down means no phase -- including one added later -- can key on anything
the registry has not admitted. checkHalt no longer accepts a key, so it
cannot derive one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 02:41:14 -07:00
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
zeekayandHanzo Dev e0a77cceaf dex: the comments described a seam that no longer exists
Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 17:44:38 -07:00
zeekayandHanzo Dev 42d2759b52 wip: drop order/slippage code the funding rail no longer reaches
Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 17:25:28 -07:00
zeekayandHanzo Dev f8ca4a37be wip: C-side funding rail un-braid (build green)
Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 17:23:58 -07:00
zeekayandHanzo Dev 2391e31aaa dex: the seam's unit of work is an order, not an intent
Order -> Match -> Execution -> Trade is the vocabulary; "intent" was the old
name for what an Order is. Renames the identifiers across the 0x9999 settle
precompile so the code reads in the settled terms.

Values that live on a chain keep their bytes, because renaming one of those is
a migration and not a rename:

  - "lux.dex.native.intent.v1" and "lux.dex.native.intent.pending.v2" are hash
    INPUTS; a byte changed there moves every derived order id and the pending
    trait both repos must agree on.
  - settleStateNamespace + "intent" is an on-disk key prefix; renaming it
    orphans the submitted-order replay set.
  - reclaimIntent(bytes32), IntentSubmitted(...) and IntentReclaimed(...) are
    the deployed ABI; the signature is what hashes, so a rename would move a
    selector and two topics.

Each of those now carries one line saying why the literal does not track the
identifier that names it, so the inconsistency is not "fixed" later.

TestOrderID_CrossRepoGolden still reads 5b0e8d38... and
TestSeamPendingTrait_MatchesDChainGolden still reads 5f10f9ad..., which is the
evidence no derivation input moved.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 16:13:01 -07:00
zeekayandHanzo Dev fd1cc0904e dex: carry the cross-chain object in the transaction, not shared memory
A settled swap could never be synced past. Both C-side consume paths
(ImportSettlement, ImportPositionCollect) read the D->C object from shared
memory DURING EVM execution, while the consuming Remove landed at block
Accept. A node re-executing that block — bootstrapping, state-syncing,
re-tracing — found the object gone, computed a reverted receipt where the
network had computed a successful one, and died on the receipt root. The
seam settled live and wedged every node that tried to follow.

Move the object's 69 bytes into the transaction (Phase-B hookData, and the
collectPosition calldata) so execution is a pure function of the block and
replays byte-identically forever. Both rails now go through ONE binding
primitive, bindImportObject, which also DECLARES the consumption as a log
committing to the object's hash. The host authenticates that declaration
against shared memory at Block.Verify, so a forged object costs the
producer a block instead of forking a receipt root.

The ship rule did not weaken, it moved: a C balance is still credited only
by consuming a real D->C object. What changed is where that is proven.

Also:
- amount is no longer restated on the wire; it IS the object's amount, so
  there is one declaration of the fact instead of two to cross-check.
- the retired DS01 tag is refused BY NAME. The fallback for an unknown tag
  is Phase A, so a stale settle would otherwise have been silently re-read
  as an intent and LOCKED the sender's input — the one mixed-fleet outcome
  worse than a revert.
- the zero-amount refusal moved into the single binding point, so an absent
  object zero-padded to canonical width reports the same clear error on
  both rails rather than surfacing as a cross-rail violation.

336 tests pass, 0 fail.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 06:40:23 -07:00
zeekayandHanzo Dev af68fed375 dex: decomplect to canonical settle-only 0x9999 (remove embedded matcher)
ONE money path: D-Chain matches, C-Chain 0x9999 SETTLES, C never matches.

- 0x9999 swap() routes ONLY to SettleSwap (native C<->D atomic seam): untagged/
  DI01 => Phase A intent (lock input, write C->D object, return intent id); DS01
  => Phase B settlement (consume a real D->C atomic object once, credit RECORDED
  amount). No-receipt-no-settle: absent a real object, Phase B reverts
  ErrNativeNoSettlement (TestDecomplect_NoReceiptNoSettle).
- Deleted the embedded matcher: swap_sync*.go, swap_custody.go (maker order book),
  buildRouter/evmAMMPool in swap_amm_pool.go, and the swapDeposit/withdraw/place/
  cancel selectors. A live in-trie matcher forks consensus; deterministic
  atomic-object consumption is fork-safe inline.
- 0x9010 already non-dispatched. 0x9012 (LXRouter) value selectors exactInput*/
  exactOutput* now revert ErrPrecompileMoved (PRECOMPILE_MOVED); read-only quote/
  route views kept.
- Cleanups (dead code removal, all unreachable via any dispatched selector):
  deleted the legacy engine PoolManager.Swap + LXRouter.Exact*/executeV4Swap*;
  deleted the vestigial makerLockedVault ledger (0 callers, always 0); deleted the
  unwired Block-STM scaffolding (AssertWriteSetWithin/AccessCommitment/Predict*).
- Activation rides the EXISTING canonical gate 1766708400 (Dec 25 2025 16:20 PST);
  no new timestamp. Build CGO_ENABLED=0 (pure-Go luxfi/dex path); go.sum zap
  re-tag checksum refreshed.

327 dex tests PASS / 0 FAIL, go vet clean. RED-reviewed (0 critical, all drain
vectors safe, fund-strand cleared, negative invariant falsified-then-restored).

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-04 12:52:44 -07:00
zeekayandHanzo Dev 844f3d3587 dex: decomplect to canonical settle-only 0x9999 (remove embedded matcher)
ONE money path: D-Chain matches, C-Chain 0x9999 SETTLES, C never matches.

- 0x9999 swap() routes ONLY to SettleSwap (native C<->D atomic seam): untagged/
  DI01 => Phase A intent (lock input, write C->D object, return intent id); DS01
  => Phase B settlement (consume a real D->C atomic object once, credit RECORDED
  amount). No-receipt-no-settle: absent a real object, Phase B reverts
  ErrNativeNoSettlement (TestDecomplect_NoReceiptNoSettle).
- Deleted the embedded matcher: swap_sync*.go, swap_custody.go (maker order book),
  buildRouter/evmAMMPool in swap_amm_pool.go, and the swapDeposit/withdraw/place/
  cancel selectors. A live in-trie matcher forks consensus; deterministic
  atomic-object consumption is fork-safe inline.
- 0x9010 already non-dispatched. 0x9012 (LXRouter) value selectors exactInput*/
  exactOutput* now revert ErrPrecompileMoved (PRECOMPILE_MOVED); read-only quote/
  route views kept.
- Cleanups (dead code removal, all unreachable via any dispatched selector):
  deleted the legacy engine PoolManager.Swap + LXRouter.Exact*/executeV4Swap*;
  deleted the vestigial makerLockedVault ledger (0 callers, always 0); deleted the
  unwired Block-STM scaffolding (AssertWriteSetWithin/AccessCommitment/Predict*).
- Activation rides the EXISTING canonical gate 1766708400 (Dec 25 2025 16:20 PST);
  no new timestamp. Build CGO_ENABLED=0 (pure-Go luxfi/dex path); go.sum zap
  re-tag checksum refreshed.

327 dex tests PASS / 0 FAIL, go vet clean. RED-reviewed (0 critical, all drain
vectors safe, fund-strand cleared, negative invariant falsified-then-restored).

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-04 12:52:44 -07:00
zeekay b362c1758e feat(0x9999): synchronous on-chain router — Gate B (joint C+D journal, real-asset settle, DEXFill event, full test suite)
0x9999.swap routes one swap across on-chain liquidity in-process and atomically:
V4 native CLOB first, falls to V2/V3 AMM when CLOB depth is insufficient, best-exec
split across both. The C balance delta and the D book/fill delta land in ONE joint
journal committed in a SINGLE cEVM block — both-or-revert-both under one EVM snapshot.
No async C->D->C, no live ZAP in Verify, no keeper, no external venue. Real ERC-20s
only (assetID = real token address); the 0x9999 vault holds the real tokens, settled
no-mint conservation-checked.

- swap_sync.go: syncSwap handler (buildSwapRequest -> dexcore.ExecuteSwap -> applyJournalC),
  value-swaps gate (fail-closed unless quorum-finality engine), DEXFill C-event on the
  sync money path (gated dexLogsActive — same activation fork as the async path).
- swap_amm_pool.go: V2/V3 AMM fallthrough bound to 0x9999-stored reserve rows (BindAMMPool).
- swap_custody.go: maker deposit/place/cancel/withdraw over the dexcore ledger.
- settle_module.go: dispatch untagged swap + value-enabled -> runSyncSwap; else async.

Tests (real assets, no stubs): Test9999SyncSwap_{CAndDCommitAtomically, RevertRollsBackCAndD,
ValueGateFailsClosed, ValidatorReplayNoLiveZAP, FakeProposerFillRejected,
TradeVisibleInDStateAndCEvents, ERC20ObservedDelta, NonReentrant, AMMLegMovesRealERC20EndToEnd};
Test9999Routes{CLOBWhenDepth,V3WhenCLOBEmpty}; Test9999BestExecSplitCLOBAndAMM;
Test{HFTAndEVMUseSameBook,NativeMakerEVMTaker,EVMMakerNativeTaker}SameState;
TestNo{LiveZAPSettlement,KeeperSettlementPath,VenueFallback}; TestE2E_SyncSwap_MakerRestsTakerSwaps
(native maker + EVM taker, real ERC-20, fill once, restart preserves, conservation+ownership).

Atomicity proven through a faithful EVM-snapshot boundary: MockStateDB gets a real
Snapshot/RevertToSnapshot (deep-clone) + runWithEVMSnapshot mirroring geth evm.go Call,
so a reverted swap rolls back BOTH C and D — the real production property, not a stub.
2026-06-22 17:46:32 -07:00
zeekay 2936914451 feat(0x9999): synchronous on-chain router — Gate B (joint C+D journal, real-asset settle, DEXFill event, full test suite)
0x9999.swap routes one swap across on-chain liquidity in-process and atomically:
V4 native CLOB first, falls to V2/V3 AMM when CLOB depth is insufficient, best-exec
split across both. The C balance delta and the D book/fill delta land in ONE joint
journal committed in a SINGLE cEVM block — both-or-revert-both under one EVM snapshot.
No async C->D->C, no live ZAP in Verify, no keeper, no external venue. Real ERC-20s
only (assetID = real token address); the 0x9999 vault holds the real tokens, settled
no-mint conservation-checked.

- swap_sync.go: syncSwap handler (buildSwapRequest -> dexcore.ExecuteSwap -> applyJournalC),
  value-swaps gate (fail-closed unless quorum-finality engine), DEXFill C-event on the
  sync money path (gated dexLogsActive — same activation fork as the async path).
- swap_amm_pool.go: V2/V3 AMM fallthrough bound to 0x9999-stored reserve rows (BindAMMPool).
- swap_custody.go: maker deposit/place/cancel/withdraw over the dexcore ledger.
- settle_module.go: dispatch untagged swap + value-enabled -> runSyncSwap; else async.

Tests (real assets, no stubs): Test9999SyncSwap_{CAndDCommitAtomically, RevertRollsBackCAndD,
ValueGateFailsClosed, ValidatorReplayNoLiveZAP, FakeProposerFillRejected,
TradeVisibleInDStateAndCEvents, ERC20ObservedDelta, NonReentrant, AMMLegMovesRealERC20EndToEnd};
Test9999Routes{CLOBWhenDepth,V3WhenCLOBEmpty}; Test9999BestExecSplitCLOBAndAMM;
Test{HFTAndEVMUseSameBook,NativeMakerEVMTaker,EVMMakerNativeTaker}SameState;
TestNo{LiveZAPSettlement,KeeperSettlementPath,VenueFallback}; TestE2E_SyncSwap_MakerRestsTakerSwaps
(native maker + EVM taker, real ERC-20, fill once, restart preserves, conservation+ownership).

Atomicity proven through a faithful EVM-snapshot boundary: MockStateDB gets a real
Snapshot/RevertToSnapshot (deep-clone) + runWithEVMSnapshot mirroring geth evm.go Call,
so a reverted swap rolls back BOTH C and D — the real production property, not a stub.
2026-06-22 17:46:32 -07:00
zeekay c22f390d86 dex/0x9999: chain-observable intent id + MEV price-limit carry (swap-seam)
MEDIUM (watch correlation broken): the off-chain keeper/SDK derived the intent id with
a ZERO txID (pre-landing) while the on-chain SubmitSwapIntent used the REAL txID — the
ids never matched, so a keeper's watch could not correlate against a live chain. FIX:
DeriveIntentID now binds (networkID,cChainID,dChainID,account,assetIn,amountIn,marketID,
NONCE) with NO txID. The nonce is a taker-supplied disambiguator carried in the swap's
DI01 hookData (settle_hookdata.go: deadline[32]|nonce[32]), so the off-chain and
on-chain sides derive the IDENTICAL id from the IDENTICAL calldata. It replaces the
former txID+callIndex axis (callIndex depended on the unknown-pre-signing txID ordering);
the per-tx CallIndex host counter remains for other precompiles. Injectivity holds over
(account+economic tuple+nonce); a reused tuple is replay-rejected (idempotent).
aivmbridge.DeriveIntentID is a separate function (InferenceIntent) — unaffected.

MEDIUM (native swap dropped SqrtPriceLimitX96 + no slippage floor): buildIntentRequest
now carries the taker's worst-acceptable price into the intent. priceLimitToCLOB maps the
V4 SqrtPriceLimitX96 to the CLOB quote-per-base domain (float64 bits) with the correct
direction (zeroForOne SELL=floor, !zeroForOne BUY=ceiling; V4 sentinels => no limit). It
rides the IntentSubmitted event so the keeper carries it onto the settling relay and the
dexvm settle path refuses a fill worse than the limit (bounded sandwich/MEV).

Tests (CGO_ENABLED=0): TestIntentID_OffChainEqualsOnChain (off==on, txID-independent),
TestIntentID_DeterministicAndCollisionFreeAcrossNonces, TestDeriveIntentID_PureOnNonce,
TestPriceLimitToCLOB_* / TestBuildIntentRequest_CarriesPriceLimit. Full dex suite green;
parity (byte-identity with zap) intact.

Extends fix/swap-rail-intent-record (97854ee). Within precompile v0.5.56.
2026-06-21 22:39:07 -07:00
zeekay 96fff4606c dex/0x9999: chain-observable intent id + MEV price-limit carry (swap-seam)
MEDIUM (watch correlation broken): the off-chain keeper/SDK derived the intent id with
a ZERO txID (pre-landing) while the on-chain SubmitSwapIntent used the REAL txID — the
ids never matched, so a keeper's watch could not correlate against a live chain. FIX:
DeriveIntentID now binds (networkID,cChainID,dChainID,account,assetIn,amountIn,marketID,
NONCE) with NO txID. The nonce is a taker-supplied disambiguator carried in the swap's
DI01 hookData (settle_hookdata.go: deadline[32]|nonce[32]), so the off-chain and
on-chain sides derive the IDENTICAL id from the IDENTICAL calldata. It replaces the
former txID+callIndex axis (callIndex depended on the unknown-pre-signing txID ordering);
the per-tx CallIndex host counter remains for other precompiles. Injectivity holds over
(account+economic tuple+nonce); a reused tuple is replay-rejected (idempotent).
aivmbridge.DeriveIntentID is a separate function (InferenceIntent) — unaffected.

MEDIUM (native swap dropped SqrtPriceLimitX96 + no slippage floor): buildIntentRequest
now carries the taker's worst-acceptable price into the intent. priceLimitToCLOB maps the
V4 SqrtPriceLimitX96 to the CLOB quote-per-base domain (float64 bits) with the correct
direction (zeroForOne SELL=floor, !zeroForOne BUY=ceiling; V4 sentinels => no limit). It
rides the IntentSubmitted event so the keeper carries it onto the settling relay and the
dexvm settle path refuses a fill worse than the limit (bounded sandwich/MEV).

Tests (CGO_ENABLED=0): TestIntentID_OffChainEqualsOnChain (off==on, txID-independent),
TestIntentID_DeterministicAndCollisionFreeAcrossNonces, TestDeriveIntentID_PureOnNonce,
TestPriceLimitToCLOB_* / TestBuildIntentRequest_CarriesPriceLimit. Full dex suite green;
parity (byte-identity with zap) intact.

Extends fix/swap-rail-intent-record (97854ee). Within precompile v0.5.56.
2026-06-21 22:39:07 -07:00
zeekayandGitHub d30d0fcaf5 Thinking Chains: precompile (#7)
* dex/0x9999: per-intent swap escrow record — per-taker cap (MEDIUM) + deadline reclaim (HIGH)

The swap rail lacked the LP rail's per-taker bound and any reclaim path: a swap's
locked tokenIn was stranded forever if D never settled (Deadline hardcoded 0, no
reclaim selector, SubmitCancel dead), and ImportSettlement bounded a credit only by
the shared seamReserve — no per-taker cap, so an over-export for one taker could
draw on others' pooled tokenIn.

ONE new per-intent escrow record (native_state.go swapIntentRecord: owner, assetIn,
remaining principal, deadline, status) drives BOTH fixes, created at SubmitSwapIntent
— the swap-rail analog of the LP RestingOrder:

MEDIUM (per-taker cap): ImportSettlement binds each settlement to the taker's OWN
named Open intent (owner==recorded owner, not past deadline). The SAME-ASSET refund
leg (recAsset==intent.AssetIn) is capped by + decrements the remaining locked
principal (ErrSettleExceedsIntent) — the exact same-asset analog of
ErrLPCollectExceedsPosition (the LP collects the asset it committed). The proceeds
leg (opposite asset) is NOT input-unit-capped (output units differ from input at any
price != 1 — an input-unit cap would wrongly refuse honest fills); its no-mint guard
is seamReserve backing + one-time consumption + the intent binding.

HIGH (reclaim liveness): a real non-zero Deadline is plumbed from an explicit DI01
intent-body word (V4 SwapParams tuple UNCHANGED) into the record. New reclaimIntent
selector refunds the remaining locked principal from seamReserve to the locker once
the deadline passes (one-time, deadline-gated; reclaim+late-settle are mutually
exclusive via the shared remaining counter + terminal Reclaimed status; a
compensating C->D Remove prevents D double-funding). So locked swap input can always
exit, like deposit/withdraw.

INFO: corrected the swap-rail-safety docstrings (settle9999.go,
native_dchain_client.go, native_events.go) to state per-rail what C enforces vs
relies on D for; fixed the false "reclaims via the cancel path" comment. Deleted the
dead SubmitCancel/SubmitCollect client methods (the cancel/collect keeper events are
emitted at their lifecycle sites; the swap reclaim is reclaimIntent).

Regression: native_swap_intent_redteam_test.go (8 tests) — RefundBoundToOwnIntent
Principal (MEDIUM cap), ProceedsNotCappedByInputPrincipal (dimension correctness),
ReclaimRefundsStrandedPrincipal + ReclaimGuards + ReclaimAndSettleAreMutually
Exclusive (HIGH), SettleRefusedPastDeadline, DeadlinePlumbedFromIntentBody,
SettleMustNameAnOpenIntent. Full dex suite passes.

Gated on red re-verify; NOT promoted to testnet/mainnet.

* aivmbridge: C->A inference bridge precompile (0x0300..04). Pattern A submit-intent / Pattern B verify committed receipt (keccak-merkle). LP-5301.

* aivmbridge: build-tag crossmodule parity test (CI green)

The cross-module byte-equality test is the only file importing chains/aivm
(resolvable only via go.work). Tag it `crossmodule` so default go test ./...
(CI, no workspace) skips it; run with `go test -tags crossmodule` + workspace.
Production aivmbridge stays decoupled (wire-format only).

---------

Co-authored-by: zeekay <[email protected]>
2026-06-21 20:53:33 -07:00
zeekayandGitHub 741b9a56df Thinking Chains: precompile (#7)
* dex/0x9999: per-intent swap escrow record — per-taker cap (MEDIUM) + deadline reclaim (HIGH)

The swap rail lacked the LP rail's per-taker bound and any reclaim path: a swap's
locked tokenIn was stranded forever if D never settled (Deadline hardcoded 0, no
reclaim selector, SubmitCancel dead), and ImportSettlement bounded a credit only by
the shared seamReserve — no per-taker cap, so an over-export for one taker could
draw on others' pooled tokenIn.

ONE new per-intent escrow record (native_state.go swapIntentRecord: owner, assetIn,
remaining principal, deadline, status) drives BOTH fixes, created at SubmitSwapIntent
— the swap-rail analog of the LP RestingOrder:

MEDIUM (per-taker cap): ImportSettlement binds each settlement to the taker's OWN
named Open intent (owner==recorded owner, not past deadline). The SAME-ASSET refund
leg (recAsset==intent.AssetIn) is capped by + decrements the remaining locked
principal (ErrSettleExceedsIntent) — the exact same-asset analog of
ErrLPCollectExceedsPosition (the LP collects the asset it committed). The proceeds
leg (opposite asset) is NOT input-unit-capped (output units differ from input at any
price != 1 — an input-unit cap would wrongly refuse honest fills); its no-mint guard
is seamReserve backing + one-time consumption + the intent binding.

HIGH (reclaim liveness): a real non-zero Deadline is plumbed from an explicit DI01
intent-body word (V4 SwapParams tuple UNCHANGED) into the record. New reclaimIntent
selector refunds the remaining locked principal from seamReserve to the locker once
the deadline passes (one-time, deadline-gated; reclaim+late-settle are mutually
exclusive via the shared remaining counter + terminal Reclaimed status; a
compensating C->D Remove prevents D double-funding). So locked swap input can always
exit, like deposit/withdraw.

INFO: corrected the swap-rail-safety docstrings (settle9999.go,
native_dchain_client.go, native_events.go) to state per-rail what C enforces vs
relies on D for; fixed the false "reclaims via the cancel path" comment. Deleted the
dead SubmitCancel/SubmitCollect client methods (the cancel/collect keeper events are
emitted at their lifecycle sites; the swap reclaim is reclaimIntent).

Regression: native_swap_intent_redteam_test.go (8 tests) — RefundBoundToOwnIntent
Principal (MEDIUM cap), ProceedsNotCappedByInputPrincipal (dimension correctness),
ReclaimRefundsStrandedPrincipal + ReclaimGuards + ReclaimAndSettleAreMutually
Exclusive (HIGH), SettleRefusedPastDeadline, DeadlinePlumbedFromIntentBody,
SettleMustNameAnOpenIntent. Full dex suite passes.

Gated on red re-verify; NOT promoted to testnet/mainnet.

* aivmbridge: C->A inference bridge precompile (0x0300..04). Pattern A submit-intent / Pattern B verify committed receipt (keccak-merkle). LP-5301.

* aivmbridge: build-tag crossmodule parity test (CI green)

The cross-module byte-equality test is the only file importing chains/aivm
(resolvable only via go.work). Tag it `crossmodule` so default go test ./...
(CI, no workspace) skips it; run with `go test -tags crossmodule` + workspace.
Production aivmbridge stays decoupled (wire-format only).

---------

Co-authored-by: zeekay <[email protected]>
2026-06-21 20:53:33 -07:00
zeekay 49fe03803c dex: native C<->D atomic settlement seam (0x9999) + Red fixes 2/3/4
The 0x9999 money path: V4 ABI swap -> NativeDChainClient -> C staged ->
shared-memory export/import -> D dexvm atomic import/export (Mode A async,
no BLS). C SETTLES, C never matches. Value crosses ONLY as a primary-network
atomic shared-memory object. The BLS cert / VerifierRegistry / DFillReceipt
value path is ripped (quarantined under dex/_deprecated_bls/).

Ship rule (structural, not trust): C credited ONLY by consuming a D->C object
(ImportSettlement); D funded ONLY by consuming a C->D object (SubmitSwapIntent
-> dexvm executeImport). Atomic ops are STAGED in EVM state during execution
(revert-aware) and flushed to shared memory at block accept over the
deterministic parent->current seq window.

contract.AtomicState: the optional host capability a precompile type-asserts to
reach atomic shared memory (the platformvm/dexvm import/export primitive).

Red fixes applied to the seam:

FIX 2 (consensus-liveness chain-halt): native_staging.go FlushAcceptedAtomicOps
already takes a batch; the flush is now one-batch-atomic at the host (see
evm block.go/vm.go) — the SM Apply and the seq-marker advance commit all-or-
nothing, closing the crash-between-Apply-and-marker -> duplicate-Put -> halt
window. Regression: native_haltwindow_test.go (re-accept after a committed
window is a clean no-op; crash-before-commit re-applies cleanly; the marker
must ride the Apply batch).

FIX 3 (conservation blast-radius): native_state.go adds seamReserve[a], the
seam's OWN pot, decoupled from the depositor pot (settleVault) and the maker
pot (makerLockedVault). lockIntentInput funds seamReserve; creditSettlementOutput
checks+debits seamReserve. A settlement credit can never raid a depositor's
claim; a withdraw can never strand a backed settlement. Invariant
realHolding == settleVault + makerLockedVault + seamReserve. Regression:
native_conservation_test.go (both subsystems, same asset).

FIX 4 (reentrancy defense-in-depth): SettleSwap now takes the same single
custody guard slot as deposit/withdraw/modifyLiquidity (enterCustodyKV), so the
whole 0x9999 money surface is single-in-flight and an ERC-20 transfer callback
cannot re-enter. Regression: native_callindex_test.go (callIndex determinism +
intent-id collision-freedom across two swaps + a reverted sub-frame + a
STATICCALL; SettleSwap non-reentrant + guard released for sequential calls).

305 prior + 10 new dex tests pass (CGO_ENABLED=0). Live-EVM-call-tree and
live-crash-injection tests are CI-only (CGO + luxfi/accel LUXCPP).
2026-06-21 00:18:54 -07:00
zeekay 51828edc63 dex: native C<->D atomic settlement seam (0x9999) + Red fixes 2/3/4
The 0x9999 money path: V4 ABI swap -> NativeDChainClient -> C staged ->
shared-memory export/import -> D dexvm atomic import/export (Mode A async,
no BLS). C SETTLES, C never matches. Value crosses ONLY as a primary-network
atomic shared-memory object. The BLS cert / VerifierRegistry / DFillReceipt
value path is ripped (quarantined under dex/_deprecated_bls/).

Ship rule (structural, not trust): C credited ONLY by consuming a D->C object
(ImportSettlement); D funded ONLY by consuming a C->D object (SubmitSwapIntent
-> dexvm executeImport). Atomic ops are STAGED in EVM state during execution
(revert-aware) and flushed to shared memory at block accept over the
deterministic parent->current seq window.

contract.AtomicState: the optional host capability a precompile type-asserts to
reach atomic shared memory (the platformvm/dexvm import/export primitive).

Red fixes applied to the seam:

FIX 2 (consensus-liveness chain-halt): native_staging.go FlushAcceptedAtomicOps
already takes a batch; the flush is now one-batch-atomic at the host (see
evm block.go/vm.go) — the SM Apply and the seq-marker advance commit all-or-
nothing, closing the crash-between-Apply-and-marker -> duplicate-Put -> halt
window. Regression: native_haltwindow_test.go (re-accept after a committed
window is a clean no-op; crash-before-commit re-applies cleanly; the marker
must ride the Apply batch).

FIX 3 (conservation blast-radius): native_state.go adds seamReserve[a], the
seam's OWN pot, decoupled from the depositor pot (settleVault) and the maker
pot (makerLockedVault). lockIntentInput funds seamReserve; creditSettlementOutput
checks+debits seamReserve. A settlement credit can never raid a depositor's
claim; a withdraw can never strand a backed settlement. Invariant
realHolding == settleVault + makerLockedVault + seamReserve. Regression:
native_conservation_test.go (both subsystems, same asset).

FIX 4 (reentrancy defense-in-depth): SettleSwap now takes the same single
custody guard slot as deposit/withdraw/modifyLiquidity (enterCustodyKV), so the
whole 0x9999 money surface is single-in-flight and an ERC-20 transfer callback
cannot re-enter. Regression: native_callindex_test.go (callIndex determinism +
intent-id collision-freedom across two swaps + a reverted sub-frame + a
STATICCALL; SettleSwap non-reentrant + guard released for sequential calls).

305 prior + 10 new dex tests pass (CGO_ENABLED=0). Live-EVM-call-tree and
live-crash-injection tests are CI-only (CGO + luxfi/accel LUXCPP).
2026-06-21 00:18:54 -07:00
zeekay 109874dda7 feat(dex): 0x9999 DEX V4 receipt-settlement precompile (D matches · C settles)
Replaces the deprecated embedded-AMM / inert / ERC-20-custody ZAP path with a
fork-safe receipt-settlement model: the D-Chain matches and BLS-signs a
DFillReceipt; 0x9999 (Uniswap-V4 PoolManager ABI) verifies that certificate
INLINE (BLS verify is deterministic -> fork-safe), debits/credits balances, and
marks the receipt consumed under Block-STM with fine-grained keys. The verify
path never calls a live matcher.

New:
  receipt.go            DFillReceipt + Merkle/wire encoding
  bls_cert.go           BLS quorum certificate verify (message-bound, anti-phantom)
  verifier_registry.go  certType (BLS day-1 -> Q/PQ/ZK) upgrade w/o ABI change
  settle9999.go         0x9999 settlement entrypoint (V4 PoolManager ABI)
  settle_module.go settle_custody.go settle_hookdata.go settle_stm.go settle_addr.go
  halt9999.go           multi-layer fund-preserving halt
  dchain_client.go      D-Chain receipt source client
  settle_test.go settle_consensus_test.go  18 tests: E2E, no-mint conservation,
                        replay, restart-determinism, halt, forged-cert + wrong-chain
                        rejection, 0x9010 forwarder, BLS quorum/invalid/confusion/
                        phantom-bit, verifier registry, wire round-trips

Removed deprecated dead code: engine_inert.go + inert/custody/erc20/zap-rounding/
route-not-simulate tests (the old embedded-AMM and ERC-20-custody design).

0x9010 kept as a deprecated forwarder into the same impl (no ABI break).
Normative spec: LP-9999.
2026-06-20 12:29:12 -07:00
zeekay 9cef96f13d feat(dex): 0x9999 DEX V4 receipt-settlement precompile (D matches · C settles)
Replaces the deprecated embedded-AMM / inert / ERC-20-custody ZAP path with a
fork-safe receipt-settlement model: the D-Chain matches and BLS-signs a
DFillReceipt; 0x9999 (Uniswap-V4 PoolManager ABI) verifies that certificate
INLINE (BLS verify is deterministic -> fork-safe), debits/credits balances, and
marks the receipt consumed under Block-STM with fine-grained keys. The verify
path never calls a live matcher.

New:
  receipt.go            DFillReceipt + Merkle/wire encoding
  bls_cert.go           BLS quorum certificate verify (message-bound, anti-phantom)
  verifier_registry.go  certType (BLS day-1 -> Q/PQ/ZK) upgrade w/o ABI change
  settle9999.go         0x9999 settlement entrypoint (V4 PoolManager ABI)
  settle_module.go settle_custody.go settle_hookdata.go settle_stm.go settle_addr.go
  halt9999.go           multi-layer fund-preserving halt
  dchain_client.go      D-Chain receipt source client
  settle_test.go settle_consensus_test.go  18 tests: E2E, no-mint conservation,
                        replay, restart-determinism, halt, forged-cert + wrong-chain
                        rejection, 0x9010 forwarder, BLS quorum/invalid/confusion/
                        phantom-bit, verifier registry, wire round-trips

Removed deprecated dead code: engine_inert.go + inert/custody/erc20/zap-rounding/
route-not-simulate tests (the old embedded-AMM and ERC-20-custody design).

0x9010 kept as a deprecated forwarder into the same impl (no ABI break).
Normative spec: LP-9999.
2026-06-20 12:29:12 -07:00