mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
main
35
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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]> |
||
|
|
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. |
||
|
|
2b0e35ad84 |
dex: a deposit may claim only what a deposit delivered
0x9999 holds ONE native balance backing THREE pots -- settleVault (depositor claims), seamReserve (swap-rail input locks) and committedPositions (LP commits). A precompile cannot read msg.value, so every native funding path asks the same question by difference: balance(0x9999) minus what is already accounted for. The operator seed subtracted all three pots. deposit subtracted settleVault alone, so it read the seam and LP pots as value the caller had just carried and minted a claim over them; withdraw then paid that claim out of the one balance, leaving the intents and positions that locked it unbacked. Its own comment stated the property the code did not hold. deliveredNative is now that question, asked in one place and answered the same way for both paths. A pot added later cannot be forgotten by half the callers, because there is only one caller of the arithmetic. Mutation: dropping the two pot terms fails ten tests, four new and six that already pinned the seed side. The five Run entry points read their selector through contract.Cursor. Three of them spelled the same four-byte read three different ways, and all five handed their handlers input[4:] -- a two-index slice, so cap ran on into the EVM memory the caller filled with MSTORE before the call. Every downstream guard bounds its own high index correctly today, but the property was held up by fifty independent hand-written checks; Cursor.Rest caps the slice to its own length, so a slice expression past the declared input refuses instead of returning bytes the caller chose. Suite green with the cap in place, which is the evidence that no live path reads past its input. |
||
|
|
b8d2f08b9e |
dex: retire creditPositionFee — it reflected fees the D-Chain does not compute
native_state.go opens by defining the pot:
custody[a] = operator seed of a
+ sum of value exported to D
- sum of value imported from D
One pot per asset is defensible exactly while that holds, because one pot means
every claim draws on the same number, and the identity is what makes that number
"value C holds because D owns it" — no more, so a claim cannot draw on backing
nobody on D is owed, and no less, so every D holder can be paid.
creditPositionFee was a fourth term. Governance delivered real value and it
raised custody, an LP record's withdrawable, and that owner's reserve, as "the
keeper's per-owner reflection of fees the D-Chain CLOB credited a maker".
The D-Chain credits no maker fees. Its settlement path has no fee engine and
says so in as many words (dex pkg/dchain/settle.go: "deliberately does NOT model
a full fee/tick/lot/minNotional engine"). The function reflected an event that
does not occur.
It could not have worked if it did. An LP collects only by consuming a D->C
claim, and D exports only what a D account holds; value credited on C never
reached D, so nothing on D could export it. The record promised a withdrawable
the rail had no way to deliver, and the real value landed in the shared pot
where it backed everyone's claims instead of that LP's. Its own test had to
hand-write a D->C object for principal+fees — value D never held. The
fabrication was the feature.
It had no ABI encoder, no keeper, and no caller anywhere outside its own tests.
It was the only writer of that fourth term and the only raiser of LockedAmt
outside a real commit. It was also the only position-moving path with no halt
gate, and it needed no atomic capability, so unlike the rest of the rail it was
live on every network today.
Deleted. The identity now holds as written and seedSeamReserve is the only way
value enters custody other than a C->D export. When D grows fees they will be
D-side balance in the maker's own account and will leave by the ordinary export,
like every other unit — the way out already exists, and it is the only one.
The two LP tests that ran a fee credit now assert what is actually true of them:
a collect is bounded by custody, and a commit plus a full collect is
value-neutral.
Co-authored-by: Hanzo Dev <[email protected]>
|
||
|
|
e0a77cceaf |
dex: the comments described a seam that no longer exists
Co-authored-by: Hanzo Dev <[email protected]> |
||
|
|
f8ca4a37be |
wip: C-side funding rail un-braid (build green)
Co-authored-by: Hanzo Dev <[email protected]> |
||
|
|
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]>
|
||
|
|
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]> |
||
|
|
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]> |
||
|
|
a081f8489b |
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.
|
||
|
|
73f1772a64 |
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.
|
||
|
|
37527d0dc3 |
refactor(dex): decomplect 0x9999 value path to one live synchronous router + wire prod asset resolver
The 0x9999 native DEX is ALWAYS-ON and UNCONDITIONAL (live since the Dec 25 2025 activation). Remove the value-activation gate entirely (value_gate_boot.go, valueSwapsEnabled/EnableValueSwaps/ErrValueSwapsNotEnabled, the EnableValueSwapsViaQuorumGate/RequireQuorumFinalityForValueDEX composition) and make the synchronous on-chain smart-order-router the sole untagged-swap path. The gate is replaced by the swap's INTRINSIC fail-closed controls, enforced on every call: real-asset registry admission (the installed AssetResolver), the live-code EXTCODESIZE verifier, the per-swap min-out / price floor, the halt switches, and the non-reentrant custody mutex. A node that wired no resolver fails closed (ErrNoAssetResolver) — the structural replacement for the consensus-mode gate. - swap_sync.go / swap_custody.go (deposit+place) / settle_module.go dispatch / settle_stm_stateful.go predictor: drop the gate; untagged swap routes to runSyncSwap unconditionally. - settle_market.go: gate Initialize through the SAME resolver+verifier admission (admitMarketAssets) so a fabricated-asset initialize is refused (no phantom MarketRecord), at parity with the swap path. - swap_sync.go: fix the stale 'left-16-bytes' account-fold comment to the full 32-byte injective AccountFromBytes left-pad. - tests: harness installs a default resolver for its own market (hermetic, no cross-test global leak); add TestDecomplect_* proving a real registered asset fills end-to-end and an unregistered/synthetic asset fails closed. value_gate_boot.go deleted for real. Full dex suite green (-count=1). |
||
|
|
0401061006 |
refactor(dex): decomplect 0x9999 value path to one live synchronous router + wire prod asset resolver
The 0x9999 native DEX is ALWAYS-ON and UNCONDITIONAL (live since the Dec 25 2025 activation). Remove the value-activation gate entirely (value_gate_boot.go, valueSwapsEnabled/EnableValueSwaps/ErrValueSwapsNotEnabled, the EnableValueSwapsViaQuorumGate/RequireQuorumFinalityForValueDEX composition) and make the synchronous on-chain smart-order-router the sole untagged-swap path. The gate is replaced by the swap's INTRINSIC fail-closed controls, enforced on every call: real-asset registry admission (the installed AssetResolver), the live-code EXTCODESIZE verifier, the per-swap min-out / price floor, the halt switches, and the non-reentrant custody mutex. A node that wired no resolver fails closed (ErrNoAssetResolver) — the structural replacement for the consensus-mode gate. - swap_sync.go / swap_custody.go (deposit+place) / settle_module.go dispatch / settle_stm_stateful.go predictor: drop the gate; untagged swap routes to runSyncSwap unconditionally. - settle_market.go: gate Initialize through the SAME resolver+verifier admission (admitMarketAssets) so a fabricated-asset initialize is refused (no phantom MarketRecord), at parity with the swap path. - swap_sync.go: fix the stale 'left-16-bytes' account-fold comment to the full 32-byte injective AccountFromBytes left-pad. - tests: harness installs a default resolver for its own market (hermetic, no cross-test global leak); add TestDecomplect_* proving a real registered asset fills end-to-end and an unregistered/synthetic asset fails closed. value_gate_boot.go deleted for real. Full dex suite green (-count=1). |
||
|
|
4ccb06e588 |
dex(0x9999): restore A2 value-activation gate + wire L2 sync access-set soundness
A2 (do-not-ship): restore the fail-closed value-activation gate that the working
tree had deleted (value_gate_boot.go gone, dispatch conjunct stripped, custody
gates removed, regression test inverted). Root cause of the deletion: HEAD d60a0db
double-declared the gate symbols in BOTH swap_sync.go and value_gate_boot.go (does
not compile); the dedup correctly removed them from swap_sync.go but over-corrected
by deleting value_gate_boot.go. Fix (DRY, one home): gate symbols live ONLY in
value_gate_boot.go. Restored:
- the fail-closed gate check in runSyncSwap (value FILL)
- the && ValueSwapsEnabled() dispatch conjunct in settle_module.go
- the value gate on runSwapDeposit + runSwapPlace (value-IN / new trading)
- EXEMPTED runSwapWithdraw + runSwapCancel (EXIT paths — fail-secure, matches the
canonical 'halt blocks new trading, ALWAYS allows cancel'; HEAD over-gated these,
a latent fund-lock now fixed)
- un-inverted Test9999SyncSwap_ValueGateFailsClosed (gate-off -> no sync fill)
B7/B8: PredictSyncSwapWriteSet models runSyncSwap's ACTUAL writes (swapCounterKey +
dexcore book/index/ledger rows via the dexcore key schema + native legs + custody
guard); route-dependent maker rows enumerated from the LIVE order index (sound
superset, mirrors PredictModifyLiquidityWriteSet). PredictWriteSetForCall now routes
a value-enabled untagged swap to the sync predictor. Parity gate proven (observed
subset declared) with fail-without on swapCounterKey (the async predictor's blind spot).
L2: the live sync dispatch is wrapped in AssertWriteSetWithin(PredictSyncSwapWriteSet)
— an undeclared 0x9999 write fails closed (ErrAccessSetUndeclaredWrite) in Verify,
never a fork. The sync predictor's keys are not amount-derived, so a fee-on-transfer
ERC-20 does not false-reject (B6: sync path fee-on-transfer-safe by construction).
writeObservingStateDB.GetCodeSize forwards the EXTCODESIZE capability so a
poolStateAdapter wrapping the observer stays code-capable (else the C1 verifier
fails closed under L2).
Test isolation: newSettleHarness installs a default market resolver; sync-path C1/
registry/BLS tests enable the value gate explicitly. Full dex suite green 417/0.
|
||
|
|
9b7e500726 |
dex(0x9999): restore A2 value-activation gate + wire L2 sync access-set soundness
A2 (do-not-ship): restore the fail-closed value-activation gate that the working
tree had deleted (value_gate_boot.go gone, dispatch conjunct stripped, custody
gates removed, regression test inverted). Root cause of the deletion: HEAD 292d954
double-declared the gate symbols in BOTH swap_sync.go and value_gate_boot.go (does
not compile); the dedup correctly removed them from swap_sync.go but over-corrected
by deleting value_gate_boot.go. Fix (DRY, one home): gate symbols live ONLY in
value_gate_boot.go. Restored:
- the fail-closed gate check in runSyncSwap (value FILL)
- the && ValueSwapsEnabled() dispatch conjunct in settle_module.go
- the value gate on runSwapDeposit + runSwapPlace (value-IN / new trading)
- EXEMPTED runSwapWithdraw + runSwapCancel (EXIT paths — fail-secure, matches the
canonical 'halt blocks new trading, ALWAYS allows cancel'; HEAD over-gated these,
a latent fund-lock now fixed)
- un-inverted Test9999SyncSwap_ValueGateFailsClosed (gate-off -> no sync fill)
B7/B8: PredictSyncSwapWriteSet models runSyncSwap's ACTUAL writes (swapCounterKey +
dexcore book/index/ledger rows via the dexcore key schema + native legs + custody
guard); route-dependent maker rows enumerated from the LIVE order index (sound
superset, mirrors PredictModifyLiquidityWriteSet). PredictWriteSetForCall now routes
a value-enabled untagged swap to the sync predictor. Parity gate proven (observed
subset declared) with fail-without on swapCounterKey (the async predictor's blind spot).
L2: the live sync dispatch is wrapped in AssertWriteSetWithin(PredictSyncSwapWriteSet)
— an undeclared 0x9999 write fails closed (ErrAccessSetUndeclaredWrite) in Verify,
never a fork. The sync predictor's keys are not amount-derived, so a fee-on-transfer
ERC-20 does not false-reject (B6: sync path fee-on-transfer-safe by construction).
writeObservingStateDB.GetCodeSize forwards the EXTCODESIZE capability so a
poolStateAdapter wrapping the observer stays code-capable (else the C1 verifier
fails closed under L2).
Test isolation: newSettleHarness installs a default market resolver; sync-path C1/
registry/BLS tests enable the value gate explicitly. Full dex suite green 417/0.
|
||
|
|
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.
|
||
|
|
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.
|
||
|
|
28fa09e8cb |
dex/0x9999: CALL-only guard rejects DELEGATECALL on settle (0x9999) + position (0x9996)
The whole ERC-20 money path rests on one invariant: the token leg (transferFrom/transfer) runs with the settlement precompile self as the token's msg.sender, so a deposit pulls against approve(0x9999,amount) and the vault holds the asset at 0x9999. geth binds the call-opcode self as Run's addr: CALL/CALLCODE bind self=target, DELEGATECALL rebinds self to the delegating contract. Under DELEGATECALL the token msg.sender would become the delegator, breaking conservation and stranding the caller's funds. - settle_module.go: ErrSettleWrongContext + 'addr != poolManagerAddr9999' guard at the top of SettleContract.Run (0x9999), before any state touch. - position_module.go: same 'addr != positionManagerAddr' guard (0x9996), which composes 0x9999 and moves ERC-20 LP liquidity with itself as msg.sender. - settle_erc20_callseam_test.go: TestERC20Settle_CallSeam_RejectsDelegatecall — proves a DELEGATECALL (addr != 0x9999) refuses with NO token movement / no allowance consumed / no claim recorded, while the same input via CALL settles. - module_erc20.go: blast-radius comment precision (Call is an optional concrete capability, not a PrecompileEnvironment widening; GetPrecompileEnv IS on the shared AccessibleState so the two controls — bridge allowlist + this CALL-only guard — bound the reach) + 0x9010->0x9999 live-seam caller-semantics notes. erc20_vault.go (genuinely-legacy 0x9010) intentionally untouched. |
||
|
|
019588b7c7 |
dex/0x9999: CALL-only guard rejects DELEGATECALL on settle (0x9999) + position (0x9996)
The whole ERC-20 money path rests on one invariant: the token leg (transferFrom/transfer) runs with the settlement precompile self as the token's msg.sender, so a deposit pulls against approve(0x9999,amount) and the vault holds the asset at 0x9999. geth binds the call-opcode self as Run's addr: CALL/CALLCODE bind self=target, DELEGATECALL rebinds self to the delegating contract. Under DELEGATECALL the token msg.sender would become the delegator, breaking conservation and stranding the caller's funds. - settle_module.go: ErrSettleWrongContext + 'addr != poolManagerAddr9999' guard at the top of SettleContract.Run (0x9999), before any state touch. - position_module.go: same 'addr != positionManagerAddr' guard (0x9996), which composes 0x9999 and moves ERC-20 LP liquidity with itself as msg.sender. - settle_erc20_callseam_test.go: TestERC20Settle_CallSeam_RejectsDelegatecall — proves a DELEGATECALL (addr != 0x9999) refuses with NO token movement / no allowance consumed / no claim recorded, while the same input via CALL settles. - module_erc20.go: blast-radius comment precision (Call is an optional concrete capability, not a PrecompileEnvironment widening; GetPrecompileEnv IS on the shared AccessibleState so the two controls — bridge allowlist + this CALL-only guard — bound the reach) + 0x9010->0x9999 live-seam caller-semantics notes. erc20_vault.go (genuinely-legacy 0x9010) intentionally untouched. |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
3e720a92d4 |
dex(0x9999): scrub stale 0x9010 comments — 0x9999 is the sole DEX precompile (v0.5.54)
Red L1 (cleanup): three comment blocks still described a forwarding/reverting
0x9010 that no longer exists. 0x9010 is removed (not a registered precompile, no
dispatch, no forward); 0x9999 is the SOLE DEX precompile, one money path, one
replay namespace (dex.precompile.v1.9999.*). Comment-only — no behavior change.
- settle_addr.go: LXSettleAddress doc no longer claims 0x9010 forwards here.
- settle_module.go: module doc drops the '0x9010 FORWARDS its swap' clause.
- types.go: LP-901x block reframed as RETIRED address space (constants
kept only so values are not reused; nothing dispatches there).
|
||
|
|
bde867c3e1 |
dex(0x9999): scrub stale 0x9010 comments — 0x9999 is the sole DEX precompile (v0.5.54)
Red L1 (cleanup): three comment blocks still described a forwarding/reverting
0x9010 that no longer exists. 0x9010 is removed (not a registered precompile, no
dispatch, no forward); 0x9999 is the SOLE DEX precompile, one money path, one
replay namespace (dex.precompile.v1.9999.*). Comment-only — no behavior change.
- settle_addr.go: LXSettleAddress doc no longer claims 0x9010 forwards here.
- settle_module.go: module doc drops the '0x9010 FORWARDS its swap' clause.
- types.go: LP-901x block reframed as RETIRED address space (constants
kept only so values are not reused; nothing dispatches there).
|
||
|
|
9cfae862cf |
dex(0x9999): always-on settlement precompile, runtime params, zero per-net config
0x9999 — the native C<->D atomic DEX settlement money path — is now ALWAYS-ON:
present + active on every chain that loads the EVM plugin, from genesis, with NO
dexSettleConfig genesis/upgrade entry. This is the production-launch design; the
per-net config-gate is killed. Forward-perfect, one activation way.
Activation: modules.Module gains an AlwaysOn flag (+ modules.AlwaysOnModules()).
SettleModule sets AlwaysOn=true. The Configurator is retained only to satisfy the
interface; for an always-on module the host never invokes Configure (no activating
config). SettleConfig collapses to a parameter-free {Upgrade} marker that always
Verifies — 0x9999 takes no per-net parameters.
Params resolved at RUNTIME, not config (Configure has no consensus context, so it
cannot resolve them):
- protocolFeeController: built-in DefaultDAOTreasury (set in init). One canonical
DAO governance authority across every network; not a compromised dev key.
- networkID / cChainID: already sourced at runtime via contract.AtomicState.
- dChainID (THE crux): contract.AtomicState gains DChainID(). The 4 native-client
sites that read loadDChainTarget(stateDB) now read atomicState.DChainID(), which
the host resolves from the chain topology (consensus-context "D"/dexvm alias).
ids.Empty (no dexvm on this network) closes the seam — fail-secure, no mint.
Money-path logic is byte-for-byte unchanged: DeriveIntentID/DerivePositionCommitID,
lock/stage/credit, conservation, exactly-once, rail discriminator, FIX-2 one-batch
flush, nonReentrant/custody guards all untouched. The ONLY change is HOW the
precompile activates and obtains its params (runtime, not config). The Empty-dChain
revert is preserved (strengthened: SubmitSwapIntent/SubmitPositionCommit now revert
on Empty instead of falling back to the C-Chain's own id).
Dead config machinery removed (one way, no dead code): SetSettle{ChainIdentity,
DChainTarget}, loadDChainTarget, loadSettleChainIdentity, cfg{Net,CChain,DChain}IDKey
slots, the STM read-set entries for them, and the now-unused kvAdapter Configure bridge.
Tests: full dex suite 335/335 PASS, 0 fail, 0 skip (CGO_ENABLED=0; the test binary's
only CGO dep is luxfi/accel, unrelated to the pure-Go money path). Harness feeds the
D peer via the runtime AtomicState capability, mirroring production exactly — proof
that the money path is intact with zero config.
go.sum: reconcile e2e threshold v1.9.4 to the origin-canonical hash (upstream re-tag).
|
||
|
|
91539c69bc |
dex(0x9999): always-on settlement precompile, runtime params, zero per-net config
0x9999 — the native C<->D atomic DEX settlement money path — is now ALWAYS-ON:
present + active on every chain that loads the EVM plugin, from genesis, with NO
dexSettleConfig genesis/upgrade entry. This is the production-launch design; the
per-net config-gate is killed. Forward-perfect, one activation way.
Activation: modules.Module gains an AlwaysOn flag (+ modules.AlwaysOnModules()).
SettleModule sets AlwaysOn=true. The Configurator is retained only to satisfy the
interface; for an always-on module the host never invokes Configure (no activating
config). SettleConfig collapses to a parameter-free {Upgrade} marker that always
Verifies — 0x9999 takes no per-net parameters.
Params resolved at RUNTIME, not config (Configure has no consensus context, so it
cannot resolve them):
- protocolFeeController: built-in DefaultDAOTreasury (set in init). One canonical
DAO governance authority across every network; not a compromised dev key.
- networkID / cChainID: already sourced at runtime via contract.AtomicState.
- dChainID (THE crux): contract.AtomicState gains DChainID(). The 4 native-client
sites that read loadDChainTarget(stateDB) now read atomicState.DChainID(), which
the host resolves from the chain topology (consensus-context "D"/dexvm alias).
ids.Empty (no dexvm on this network) closes the seam — fail-secure, no mint.
Money-path logic is byte-for-byte unchanged: DeriveIntentID/DerivePositionCommitID,
lock/stage/credit, conservation, exactly-once, rail discriminator, FIX-2 one-batch
flush, nonReentrant/custody guards all untouched. The ONLY change is HOW the
precompile activates and obtains its params (runtime, not config). The Empty-dChain
revert is preserved (strengthened: SubmitSwapIntent/SubmitPositionCommit now revert
on Empty instead of falling back to the C-Chain's own id).
Dead config machinery removed (one way, no dead code): SetSettle{ChainIdentity,
DChainTarget}, loadDChainTarget, loadSettleChainIdentity, cfg{Net,CChain,DChain}IDKey
slots, the STM read-set entries for them, and the now-unused kvAdapter Configure bridge.
Tests: full dex suite 335/335 PASS, 0 fail, 0 skip (CGO_ENABLED=0; the test binary's
only CGO dep is luxfi/accel, unrelated to the pure-Go money path). Harness feeds the
D peer via the runtime AtomicState capability, mirroring production exactly — proof
that the money path is intact with zero config.
go.sum: reconcile e2e threshold v1.9.4 to the origin-canonical hash (upstream re-tag).
|
||
|
|
d2cf9b449a |
dex: close Red H1 cross-rail D->C consumption + per-object/owner collect bound (FIX 1-5)
Rail-bind the C<->D atomic object to close H1 in BOTH directions, plus the LP-collect per-object/per-owner bound, lifecycle, seam liveness seed, and the AssetAddr-trust hardening. Targeted fixes on the existing seam + LP rail; no redesign. Wire goes 60->61 bytes (rail tag leads); unreleased branch, markets:0, no live objects exist on the old wire. FIX 1 (HIGH, H1 both directions): the D->C object now carries a rail tag (native_wire.go: rail(1)|owner(20)|asset(32)|amount(8)). ImportSettlement consumes ONLY railSwap (debits seamReserve); ImportPositionCollect consumes ONLY railLP (debits committedPositions). SubmitSwapIntent stamps railSwap; SubmitPositionCommit stamps railLP; dexvm executeWithdraw stamps railLP, fill exports default railSwap. executeImport binds the credited rail to the consumed UTXO's recorded rail (symmetric to asset/owner). Plus a per-object gate: collectPosition names a positionID; ImportPositionCollect binds the recorded owner to THAT record (Open/Closing) and bounds the credit by its LockedAmt. H1-A closed: TestRED_LP_CollectObjectCannotBeSwapSettled (railLP -> swap settle reverts ErrSettleWrongRail; seamReserve untouched). H1-B closed: TestRED_LP_SwapObjectCannotBeCollected (railSwap -> collect reverts ErrLPCollectWrongRail; committedPositions untouched). Per-object gate: TestRED_LP_CollectMustNameAnOpenPosition; dexvm TestNativeRail_ImportRejectsRailMismatch (+ matching-rail positive control). FIX 2 (MED): collect is bounded by the recorded owner's OWN committed reserve (the named record's LockedAmt + loadLockedReserve), decremented on collect. TestRED_LP_CollectBoundToOwnPositionPrincipal: an over-export for X cannot draw on Y's committed principal in the shared pot. FIX 3 (LOW): collect drives the record Open->Closing->Closed by remaining LockedAmt (Closed at 0); re-commit while Closing is forbidden (ErrLPCommitWhileClosing). ownerHasOpenPosition removed (dead). TestRED_LP_LifecycleClosedOnFullCollect. FIX 4 (MED, liveness): seedSeamReserve(address,uint256) operator-gated selector funds the swap rail's counterparty backing (production path, not test-only), so a real market's first matched swap settles; creditPositionFee(bytes32,address, uint256) is the symmetric per-owner LP fee credit. Both value-backed (observed-delta native / transferFrom) and protocolFeeController-gated. TestFIX4_FirstFillRevertsWithoutSeed / OperatorSeedBacksFirstFill / SeedIsOperatorGatedAndValueBacked / FeeCreditIsOperatorGated. FIX 5 (LOW): creditSettlementOutput / creditPositionCollect derive the transfer token from the recorded asset (assetAddress(recAsset)); claim.AssetAddr is ignored for the transfer. Corrected the custodyGuardKey9999 comment. TestRED_FIX5_CreditTokenDerivedFromRecordedAsset. Ship rule (fixed): a C credit comes ONLY from consuming a D->C object OF THE MATCHING RAIL; a unit is CSpendable XOR DCommitted across BOTH rails. All prior gates kept (AccountID/AssetID full width, observed-delta, one-time, nonReentrant, CEI, 4-pot conservation realHolding == settleVault+makerLockedVault+seamReserve+ committedPositions). Pure Go, luxfi only. CGO_ENABLED=0 GOWORK=off go build ./dex/... + go test ./dex/ green. |
||
|
|
7432a1e09f |
dex: close Red H1 cross-rail D->C consumption + per-object/owner collect bound (FIX 1-5)
Rail-bind the C<->D atomic object to close H1 in BOTH directions, plus the LP-collect per-object/per-owner bound, lifecycle, seam liveness seed, and the AssetAddr-trust hardening. Targeted fixes on the existing seam + LP rail; no redesign. Wire goes 60->61 bytes (rail tag leads); unreleased branch, markets:0, no live objects exist on the old wire. FIX 1 (HIGH, H1 both directions): the D->C object now carries a rail tag (native_wire.go: rail(1)|owner(20)|asset(32)|amount(8)). ImportSettlement consumes ONLY railSwap (debits seamReserve); ImportPositionCollect consumes ONLY railLP (debits committedPositions). SubmitSwapIntent stamps railSwap; SubmitPositionCommit stamps railLP; dexvm executeWithdraw stamps railLP, fill exports default railSwap. executeImport binds the credited rail to the consumed UTXO's recorded rail (symmetric to asset/owner). Plus a per-object gate: collectPosition names a positionID; ImportPositionCollect binds the recorded owner to THAT record (Open/Closing) and bounds the credit by its LockedAmt. H1-A closed: TestRED_LP_CollectObjectCannotBeSwapSettled (railLP -> swap settle reverts ErrSettleWrongRail; seamReserve untouched). H1-B closed: TestRED_LP_SwapObjectCannotBeCollected (railSwap -> collect reverts ErrLPCollectWrongRail; committedPositions untouched). Per-object gate: TestRED_LP_CollectMustNameAnOpenPosition; dexvm TestNativeRail_ImportRejectsRailMismatch (+ matching-rail positive control). FIX 2 (MED): collect is bounded by the recorded owner's OWN committed reserve (the named record's LockedAmt + loadLockedReserve), decremented on collect. TestRED_LP_CollectBoundToOwnPositionPrincipal: an over-export for X cannot draw on Y's committed principal in the shared pot. FIX 3 (LOW): collect drives the record Open->Closing->Closed by remaining LockedAmt (Closed at 0); re-commit while Closing is forbidden (ErrLPCommitWhileClosing). ownerHasOpenPosition removed (dead). TestRED_LP_LifecycleClosedOnFullCollect. FIX 4 (MED, liveness): seedSeamReserve(address,uint256) operator-gated selector funds the swap rail's counterparty backing (production path, not test-only), so a real market's first matched swap settles; creditPositionFee(bytes32,address, uint256) is the symmetric per-owner LP fee credit. Both value-backed (observed-delta native / transferFrom) and protocolFeeController-gated. TestFIX4_FirstFillRevertsWithoutSeed / OperatorSeedBacksFirstFill / SeedIsOperatorGatedAndValueBacked / FeeCreditIsOperatorGated. FIX 5 (LOW): creditSettlementOutput / creditPositionCollect derive the transfer token from the recorded asset (assetAddress(recAsset)); claim.AssetAddr is ignored for the transfer. Corrected the custodyGuardKey9999 comment. TestRED_FIX5_CreditTokenDerivedFromRecordedAsset. Ship rule (fixed): a C credit comes ONLY from consuming a D->C object OF THE MATCHING RAIL; a unit is CSpendable XOR DCommitted across BOTH rails. All prior gates kept (AccountID/AssetID full width, observed-delta, one-time, nonReentrant, CEI, 4-pot conservation realHolding == settleVault+makerLockedVault+seamReserve+ committedPositions). Pure Go, luxfi only. CGO_ENABLED=0 GOWORK=off go build ./dex/... + go test ./dex/ green. |
||
|
|
a6adb47838 |
dex: LP D-committed liquidity on the native C<->D atomic seam (0x9999 modifyLiquidity commit + 0x9996/0x9999 collect)
Replace the prior C-side-only maker lock (makerLockedVault available->locked reclassification that kept funds on C) with the CTO-locked committed-liquidity model: an LP add COMMITS funds to a funded D position via a C->D atomic object (DL01); collect/decrease/burn/cancel return funds via a consumed D->C object. Two value states per asset, never both: CSpendable (EVM balance) <-> DCommitted (committedPositions pot backing a live D position). Plus DPendingCollect -> CSettled. - native_wire.go: DerivePositionCommitID (own positionCommitDomain, disjoint id space) - native_state.go: committedPositions pot (4th orthogonal vault claim) - native_dchain_client.go: SubmitPositionCommit (C->D DL01) + ImportPositionCollect (D->C, credits from committedPositions, rail-gated by ownerHasOpenPosition) - position_commit.go: the LP-rail kernel (runSettleModifyLiquidity commit/withdraw, runSettleCollectPosition, position lifecycle Open/Closing/Closed) - settle_module.go: collectPosition selector wired into 0x9999 - position_module.go: 0x9996 collect emits a D->C collect request (no longer Unsupported) - settle_stm.go: Block-STM access set updated to committedPositions + staging seq Ship rule: D position funded ONLY by consuming a C->D object; C credited (collect) ONLY by consuming a D->C object; never CSpendable && DCommitted. All prior red-found gates kept (full AccountID/AssetID, owner/asset/amount bind, one-time import, callIndex, observed-delta ERC-20, nonReentrant, conservation invariant extended to realHolding == settleVault + makerLockedVault + seamReserve + committedPositions). 10 LP tests + 3 rewritten surface tests; dex 325 pass. |
||
|
|
0666a222bb |
dex: LP D-committed liquidity on the native C<->D atomic seam (0x9999 modifyLiquidity commit + 0x9996/0x9999 collect)
Replace the prior C-side-only maker lock (makerLockedVault available->locked reclassification that kept funds on C) with the CTO-locked committed-liquidity model: an LP add COMMITS funds to a funded D position via a C->D atomic object (DL01); collect/decrease/burn/cancel return funds via a consumed D->C object. Two value states per asset, never both: CSpendable (EVM balance) <-> DCommitted (committedPositions pot backing a live D position). Plus DPendingCollect -> CSettled. - native_wire.go: DerivePositionCommitID (own positionCommitDomain, disjoint id space) - native_state.go: committedPositions pot (4th orthogonal vault claim) - native_dchain_client.go: SubmitPositionCommit (C->D DL01) + ImportPositionCollect (D->C, credits from committedPositions, rail-gated by ownerHasOpenPosition) - position_commit.go: the LP-rail kernel (runSettleModifyLiquidity commit/withdraw, runSettleCollectPosition, position lifecycle Open/Closing/Closed) - settle_module.go: collectPosition selector wired into 0x9999 - position_module.go: 0x9996 collect emits a D->C collect request (no longer Unsupported) - settle_stm.go: Block-STM access set updated to committedPositions + staging seq Ship rule: D position funded ONLY by consuming a C->D object; C credited (collect) ONLY by consuming a D->C object; never CSpendable && DCommitted. All prior red-found gates kept (full AccountID/AssetID, owner/asset/amount bind, one-time import, callIndex, observed-delta ERC-20, nonReentrant, conservation invariant extended to realHolding == settleVault + makerLockedVault + seamReserve + committedPositions). 10 LP tests + 3 rewritten surface tests; dex 325 pass. |
||
|
|
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). |
||
|
|
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). |
||
|
|
c932ae09dc |
dex: native V4 receipt-settlement precompile surface at 0x9999 (LP-9999)
Replace the synchronous-venue 0x9010 path (which forked consensus by calling a
live matcher at Verify) with a fork-safe receipt-settlement model:
D-Chain (dexvm) matches + BLS-signs a DFillReceipt; C-Chain 0x9999 verifies
the BLS aggregate cert INLINE (deterministic -> fork-safe) and settles under
Block-STM via cevm. "D matches - C settles - BLS certifies day-1 - Q later."
Build-vs-verify split is the safety invariant: the build/propose path may call
dexvm; the verify/replay path NEVER calls a live matcher -- it settles only from
the carried DFillReceipt + cert in V4 hookData. A live-matcher call at Verify
forks consensus (proven by chains/dexvm TestRED_PerValidatorRelay_SplitsConsensus).
Native V4 surface (lower gas than Solidity, one settlement kernel):
0x9999 PoolManager full V4 write surface; the ONLY money path
0x9998 Quoter view
0x9997 StateView view (extsload/extsloadArray)
0x9996 PositionManager CLOB order-position adapter (composes 0x9999, no custody)
0x9010 deprecated -> PRECOMPILE_MOVED for all value-moving calls
certType enum (1=BLS_FAST_PATH, 2=Q_CERT, 3=Q_CERT_PQ, 4=ZK_PROOF) + a
registry-pinned VerifierRegistry => upgrade the cert scheme with NO ABI change.
Red-swarm hardening (21 confirmed findings fixed, 6 critical) -- all present in
v0.5.48 and caught before cascade:
- cert now binds the full receipt value (sender/recipient/assets/amounts) in
the signed message; a valid cert can no longer be rebound to a new payload
- quorum removed from the wire cert: registry-pinned + hard >=2/3 BFT floor
(matches Lux warp VerifyWeight) -- closes the attacker-set-quorum=1 drain
- native deposit credits the observed balance delta, not absolute balance
- maker liquidity locks move funds to a separate makerLockedVault slot
(settleVault[a] + makerLockedVault[a] == on-chain balance invariant)
- halt keyed on PoolKeyHash; extsloadArray offset-overflow -> clean revert
(validator-liveness DoS); gas formula O(N)/O(S); PoP enforced (rogue-key);
int128 truncation guard; STM read/write sets corrected
Block-STM aware: PredictAccesses exposes fine-grained keys, ZERO global hot
write slots (sharded feeBucket/volume), so two swaps conflict only when they
share an account/asset/receipt/allowance/pool.
Coverage: LP-9999 critical surface 91.3% of statements; 31 adversarial
TestRED_* tests + FuzzSettleExtsloadArray. Build/vet/test green.
|
||
|
|
8b08ef107c |
dex: native V4 receipt-settlement precompile surface at 0x9999 (LP-9999)
Replace the synchronous-venue 0x9010 path (which forked consensus by calling a
live matcher at Verify) with a fork-safe receipt-settlement model:
D-Chain (dexvm) matches + BLS-signs a DFillReceipt; C-Chain 0x9999 verifies
the BLS aggregate cert INLINE (deterministic -> fork-safe) and settles under
Block-STM via cevm. "D matches - C settles - BLS certifies day-1 - Q later."
Build-vs-verify split is the safety invariant: the build/propose path may call
dexvm; the verify/replay path NEVER calls a live matcher -- it settles only from
the carried DFillReceipt + cert in V4 hookData. A live-matcher call at Verify
forks consensus (proven by chains/dexvm TestRED_PerValidatorRelay_SplitsConsensus).
Native V4 surface (lower gas than Solidity, one settlement kernel):
0x9999 PoolManager full V4 write surface; the ONLY money path
0x9998 Quoter view
0x9997 StateView view (extsload/extsloadArray)
0x9996 PositionManager CLOB order-position adapter (composes 0x9999, no custody)
0x9010 deprecated -> PRECOMPILE_MOVED for all value-moving calls
certType enum (1=BLS_FAST_PATH, 2=Q_CERT, 3=Q_CERT_PQ, 4=ZK_PROOF) + a
registry-pinned VerifierRegistry => upgrade the cert scheme with NO ABI change.
Red-swarm hardening (21 confirmed findings fixed, 6 critical) -- all present in
v0.5.48 and caught before cascade:
- cert now binds the full receipt value (sender/recipient/assets/amounts) in
the signed message; a valid cert can no longer be rebound to a new payload
- quorum removed from the wire cert: registry-pinned + hard >=2/3 BFT floor
(matches Lux warp VerifyWeight) -- closes the attacker-set-quorum=1 drain
- native deposit credits the observed balance delta, not absolute balance
- maker liquidity locks move funds to a separate makerLockedVault slot
(settleVault[a] + makerLockedVault[a] == on-chain balance invariant)
- halt keyed on PoolKeyHash; extsloadArray offset-overflow -> clean revert
(validator-liveness DoS); gas formula O(N)/O(S); PoP enforced (rogue-key);
int128 truncation guard; STM read/write sets corrected
Block-STM aware: PredictAccesses exposes fine-grained keys, ZERO global hot
write slots (sharded feeBucket/volume), so two swaps conflict only when they
share an account/asset/receipt/allowance/pool.
Coverage: LP-9999 critical surface 91.3% of statements; 31 adversarial
TestRED_* tests + FuzzSettleExtsloadArray. Build/vet/test green.
|
||
|
|
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.
|
||
|
|
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.
|