Covered continuous-unwind shorts/longs — hardening + parity (cont. of #2764)#6
Covered continuous-unwind shorts/longs — hardening + parity (cont. of #2764)#6igoraxz wants to merge 34 commits into
Conversation
Proposal implementing the Fixed-Liability Covered Continuous-Unwind Model (shorting.pdf v3.6.1) as native pool-borrowing derivatives on Alpha/TAO CPMM pools. Launch scope is shorts-first; longs are specified for symmetry but gated off. The whole feature is disabled by default (ShortsEnabled=false) and gated behind governance until trading-games verification. Adds: - derivatives module: open/top-up/partial+full close/permissionless default, per-block O(1) decay + restoration, terminal deregistration settlement. - Per-subnet custody accounting; flow-neutral (no TaoFlow writes); reuses the existing SubnetMovingPrice EMA as the risk/terminal price reference. - Governance params (kappa, base LTV, decay bounds, dust, grace, min input) via admin-utils; runtime-API read layer (quote open/close, materialized position views with health metrics, per-subnet market state). - safe-math: checked_exp; accurate small-delta carry accumulation. - Comprehensive test suite (35 tests) + DESIGN.md and IMPLEMENTATION_PLAN.md. Co-authored-by: Cursor <cursoragent@cursor.com>
Defense-in-depth: even if a position's omega_entry ever exceeded the aggregate Omega, materialize must not produce f>1 and inflate the position. Adds tests for the clamp and an open/close round-trip no-profit invariant. Co-authored-by: Cursor <cursoragent@cursor.com>
Addresses thermo branch-audit findings: - M4: cap open short positions per subnet (ShortMaxPositions, governable) and maintain a per-subnet counter, bounding deregistration-settlement work so a heavily-shorted subnet stays prunable. - L3: guard close against minting alpha if SubnetAlphaOut would underflow. - L2: quote_open_short returns None while shorts are disabled. Adds 3 tests; suite now 40 passing. Co-authored-by: Cursor <cursoragent@cursor.com>
Implements covered longs as the mirror of shorts (spec §9): Alpha collateral/ buffer/escrow, fixed TAO liability D. Longs need no TAO custody account — parked Alpha is tracked via issuance accounting (removed from SubnetAlphaIn/Out at open, minted back on restoration/close, left burned = recycled on default/cover); the only TAO movement is the trader repaying D into the pool at close. - long.rs: open/top_up/close/default, run_long_decay, settle_longs_on_dereg, governance setters. Reuses shared math (solve_collateral now takes lambda, decay_curve/utilization extracted). - Storage + params: LongPositions/LongAggregate/LongActiveSubnets/ LongPositionCount; LongBaseLtv/LongKappa/LongDust/LongMinInput/LongMaxPositions. - Dispatches 143-146, events, errors; admin-utils setters 104-109 incl. sudo_set_longs_enabled. Both sides default-disabled and independently flaggable. - 7 new tests (gating, alpha-issuance conservation on open/close, decay, default, dereg, flag independence). Suite now 47 passing; shorts + regressions unaffected. Co-authored-by: Cursor <cursoragent@cursor.com>
- proof_full_lifecycle_conserves_tao_and_alpha: mixed shorts+longs through decay, top-up, partial+full close; asserts TAO supply exactly conserved, Alpha never minted and within bounded rounding dust, custody drained, all positions/counts cleared. - proof_default_recycles_exactly_the_floor: default reduces TAO (short) / Alpha (long) issuance by EXACTLY the recycled floor. Suite now 49 passing. Co-authored-by: Cursor <cursoragent@cursor.com>
Long open/top-up and short close decrement stake via the share pool directly, bypassing validate_remove_stake's ensure_available_to_unstake. That let locked alpha (subnet-ownership conviction lock) be used as long collateral and then freed via close, circumventing the lock. Now all three paths call ensure_available_to_unstake before decreasing stake. +1 test (50 total). Co-authored-by: Cursor <cursoragent@cursor.com>
…cs, exp tests) - Cleanup-on-empty: when the last position on a subnet closes, drop the aggregate + active-set entry so the per-block decay tick stops visiting a fully-closed subnet forever (fixes active-set growth / perpetual O(1) tick). - Decouple long default grace from shorts: add LongDefaultGrace param + setter + admin extrinsic (110); long default no longer governed by ShortDefaultGrace. - Clamp short/long kappa setters to (0, 2.0] (governance can't freeze the market or remove the capacity guard). - Bound deregistration-settlement work: lower default max positions/side 4096 -> 128 (H1 over-weight prune-block mitigation); production should move to incremental terminal settlement before raising it. - Docs: correct stale module header (longs are built), state the custody solvency invariant honestly (consistent to within floor rounding, safe direction), and clarify the materialize unwrap_or(0) is correct decay->0. - safe-math: add checked_exp unit tests (vs f64::exp, round-trip, underflow). Suite: safe-math 11, derivatives 50; regressions green. Co-authored-by: Cursor <cursoragent@cursor.com>
- proof_multi_position_decay_conserves: 3 shorts + 2 longs, 300 decay blocks, close all — TAO supply exact, Alpha within dust, custody drained, active sets evicted. (The aggregate-vs-Σ solvency case single-position tests can't reach.) - short_many_partial_closes_drain_cleanly: 9x partial + full close drains custody. - governance_setters_clamp_ranges: kappa (0/huge) and decay-bound clamps. - cleanup_evicts_only_after_last_short_closes: active-set eviction timing. - long_capacity_cap_enforced, long_partial_close_reduces_prorata. - long_dereg_underwater_pays_zero_equity: terminal cover=C, equity=0. - default_grace_independent_per_side: short/long grace decoupled. Suite now 58 passing. Co-authored-by: Cursor <cursoragent@cursor.com>
Final thermos pass (no Critical/High): adds the SubnetAlphaOut >= amount guard to long open/top-up, mirroring the short-close guard, so a share-pool rounding edge can't under-decrement outstanding alpha and let close mint it back. +4 long-side tests (alpha-mint guard, top-up, merge mismatch + position cap, invalid fraction/min-input). Suite now 62 passing. Co-authored-by: Cursor <cursoragent@cursor.com>
…ge-aware terminal settlement Closes the merge-blocking review findings on the covered-shorts/longs PR and hardens terminal deregistration economics so forcing a dereg is never a free collateral-extraction path. - open_short/open_long take a caller-signed execution bound (max_alpha_liability / max_tao_liability); reject SlippageTooHigh before any fund movement (anti-sandwich; MAX opts out). - Hoist hotkey-mismatch and position-limit checks ahead of all transfers / reserve / TotalStake mutations on both open paths (no stranding on reject). - Terminal K_D = max(K_spot,last, K_EMA) with both legs slippage-aware CPMM buybacks (buyback_cost_rao: u128, ceiling-rounded, u64::MAX un-buyable sentinel) instead of the scalar Q*pEMA that understated large liabilities and overflowed I64F64. Mirror the slippage-aware cover on the long side. - Short cold-EMA guard: floor K_D at the retained buffer R when pEMA==0 so equity can never exceed the trader's own floor P (the long is safe by the sentinel). Emit only the equity actually paid. - Update docs/derivatives/DESIGN.md to the shipped signatures and K_D form; add governance note (tune pEMA half-life + kappa so carry > recoverable R). - Tests: 67 pass (+ liability-bound reject, validate-before-mutate no-state- change, cold-EMA floor, long in-the-money + long cold-EMA terminal). Verified by three adversarial security cycles (final: PASS, no HIGH/MEDIUM) and a domain/quality review. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Brings the long side to read-layer parity with shorts (the previously shorts-only quote/view layer) and locks the new behavior with tests. - types: LongOpenQuote, LongPositionInfo, LongMarketInfo, CloseLongQuote (mirror of the short view types, Alpha/TAO swapped). - long.rs: quote_open_long, quote_close_long, get_long_position(s), get_subnet_long_state, long_blocks_to_dust, long_tao_held. Views reuse the exact solve/materialize paths so quotes never diverge from execution. A long's est_close_cost == D (close repays D TAO directly; no pool swap, hence no slippage term — deliberately unlike the short buyback). - runtime-api + runtime: wire the five long view methods on DerivativesRuntimeApi. - tests: quote_open_long_matches_realized_open, long_position_and_market_views (69 derivatives tests pass). Full-feature adversarial review: PASS, no merge-blocking findings. Conservation holds on every write path; forced-dereg recovery is bounded by floor P in every regime (live/EMA/cold); the long read layer is panic-free, bounded, and write-path-consistent. long_tao_held documents its BalanceOf=u64 (lossless) assumption. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
…rical + doc fixes Resolves the blocking findings from a three-lens adversarial review (security skeptic, architect skeptic, exploiter); all three now pass. - Atomicity (security HIGH): add #[frame_support::transactional] to all eight money functions (open/top_up/close/default, both sides). Dispatchables here are NOT auto-rolled-back (the order_swap.rs precedent proves it), so a mid-path transfer failure could previously partial-commit (burned stake / stranded floor / unbacked reserve credit). Now all-or-nothing. - Decay restore ordering (security HIGH): run_short_decay performs the custody-> pool restoration transfer FIRST and advances Ω / shrinks the Σ aggregates / credits reserves only if it lands (or restore==0); on failure it leaves the aggregate and Ω untouched and retries next block. Per-position exp(-ΔΩ) decay can no longer run ahead of TAO still in custody (custody >= obligations preserved). - Numerical (precision review vs house style): solve_collateral uses the cancellation-stable root C = 2P/(b+sqrt(b^2+4aP)); buyback_cost_rao params renamed to asset-neutral (pay_reserve, recv_reserve, recv_amount) so the long call's swapped operands read correctly (architect H1). - Test (architect H2): dereg_long_equity_survives_full_dissolve_path runs the real do_dissolve_network and asserts long equity (minted as stake) survives the subsequent stake-wipe as a TAO distribution — guards the dereg ordering contract. - Docs/notes: DESIGN.md documents the load-bearing pEMA caveat (min(spot,1.0) clamp + ~30d half-life; guarantees hold for price <= ~1.0, true today) and the shorts-first/longs-implemented-but-gated scope; IMPLEMENTATION_PLAN.md K_D updated to the CPMM-buyback form; decay-hook bound + pEMA-bound comments added. 70 derivatives tests pass; pallet + runtime (native) build clean. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Adds the two property tests the architect review recommended (M2/M3), turning asserted directional invariants into checked guards: - proof_custody_geq_obligations_under_decay: with DecayMax at the 1.0/day clamp extreme, runs 3000 decay ticks and asserts (every tick) that the real custody account balance >= Σ materialized (P + R(t) + E(t)) read back via get_short_position — independent ledger vs per-position exp(-ΔΩ) replay — plus a partial close. Locks "custody >= obligations" against future decay-math edits. - proof_position_count_matches_map_through_churn: asserts ShortPositionCount == |ShortPositions[netuid]| and ShortActiveSubnets membership iff nonzero aggregate Σ, re-checked after each open / partial close / full close / cleanup-to-empty. 72 derivatives tests pass. Test-only; no logic change. Architect re-review confirmed both are genuine, non-vacuous guards. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
- open_short_failed_pool_transfer_rolls_back_atomically: drains the subnet account so the N+E pool->custody leg fails AFTER the floor moved to custody, and asserts the whole open rolls back (floor returned, custody empty, reserves untouched, no position). Directly guards the #[transactional] atomicity fix — fails without it. - open_long_rejects_when_liability_exceeds_bound: long-side mirror of the short execution-bound (max_tao_liability) rejection. 74 derivatives tests; full `cargo test -p pallet-subtensor --lib` = 1258 pass / 0 fail / 9 ignored. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Records build/static-analysis/test gates (74 derivatives + 1258 full pallet suite), three adversarial reviews, precision & on-chain CPMM audit, and live local-chain end-to-end lifecycle. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Derivatives (covered shorts/longs) — QA & Test ReportScope: the pool-borrowing covered shorts/longs feature on the Alpha/TAO CPMM Overall QA & test score: 9/10. All CI-grade gates green, comprehensive unit 1. Build (local laptop, aarch64 macOS)
macOS note: the wasm build needs a WebAssembly-capable LLVM (Apple clang lacks the 2. Static analysis / style gates
3. Tests
Coverage by area (both short and long sides unless noted):
4. Adversarial review (three independent lenses)
Key hardening landed from review: caller-signed execution bounds; validate-before-mutate; 5. Precision review & on-chain CPMM audit
6. Live local-chain end-to-end (3-validator
|
Derivatives stress / attack / DoS / chain-bloat campaign — localnet resultsFull adversarial + overload campaign run end-to-end on a 3-validator Method note: realistic init is exact — 1. Realistic regime sweep (mainnet-seeded) — short + long full lifecycle
Each: Clean long open landed on-chain ( Finding (informational): no mainnet dynamic subnet currently has spot > 1.0 or a cold 2. Edge states & rejection paths — 15/15All fired with the correct 3. Attack vectors — 8/8
4. Overload / DoS / chain bloat — 4/4Exact on-chain storage cost (authoritative, from encoded entries):
DoS — the key structural result: opening 40 positions on one subnet left Position growth is rate-limited on three independent axes: per-subnet position cap 5. Chain-bloat verdict — reasonable & boundedWorst case at full saturation (128 subnets × 128-position cap × both sides): This is negligible against subtensor's multi-GB state, and — critically — it is a hard Summary51/51 checks pass. Conservation (no TAO minting) held on every money path including default |
…single-source BLOCKS_PER_DAY Acts on the testnet-readiness adversarial review (all lenses 9/10): - add long_open_quote_gated_by_enable_flag (long mirror of the short test) - add open_max_liability_bound_opts_out (explicit *::MAX opt-out vs tight-bound reject, both sides) - consolidate BLOCKS_PER_DAY to the parent module const (long references super::) - document why long decay may commit-then-mint (infallible alpha) vs short restore-then-commit Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Stress campaign — gap-closing pass + 3-lens adversarial review (testnet-readiness)Follow-up to the previous campaign comment. This pass (a) measured the three plan items I had A. Gap-closing — items now measured on the mainnet-seeded localnet
UX note (not a bug): on a live, emitting chain the pool drifts every block, so a tight slippage Combined with the prior comment, the full plan (Phases 0–6) is now executed end-to-end: realistic B. Three-lens adversarial review — testnet readinessIndependent reviews of the implementation (
Every withheld point is the same pair of pre-mainnet residuals — un-benchmarked extrinsic/hook C. Low-risk improvements applied this pass (non-blocking, from the reviews)
Deliberately not done (tolerable for testnet, would add risk/churn): refactoring the ~120 lines VerdictThe full test plan is executed and the three previously-structural claims are now empirically |
Comprehensive spec-compliance & adversarial campaign — v3.6.1, mainnet-seeded localnetThis consolidates the full testing campaign and maps every result 1:1 against the authoritative Headline: ~85 checks, 0 chain defects. Every §17 invariant and every §10 attack class exercised on-chain. 1. MAX PRICE IMPACT — short vs long (spec §14.2)Equal TAO-value capital, SN1 pool (26,971 τ / 2.93M α, spot 0.00921):
Longs move price more per unit capital, and super-linearly. Short impact saturates (price → 0
→ Governance can dial max instantaneous price impact directly via κ, exactly as §14.2 prescribes. 2. INVARIANT SWEEP across LTV λ ∈ {0.25, 0.5, 0.75, 0.9} — 24/24 (spec §17)
3. DECAY SWEEP vs spec §14.3 curve
|
| config / utilization | measured daily decay | spec |
|---|---|---|
| zero bounds | 0.000% (frozen) | — |
| default, u≈0.04 | 0.107% | d_min 0.10% |
| default, u≈0.47 | 0.677% | convex rise |
| aggressive (d_min=d_max=1.5%) | 1.4999% | d_max 1.50% |
Decay is monotonic, never mints, frozen at zero-bounds, and tracks the convex utilization curve
between d_min and d_max — matching §14.3 to within rao rounding.
4. SPEC §10 ANTI-EXPLOIT — every class exercised
| § | Attack | Result |
|---|---|---|
| 10.1 | Wallet splitting / Sybil | Aggregate S+B≤κ·T_ref cap shared across coldkeys — 1 position fills it, next distinct coldkey rejected. ✅ |
| 10.2 | Looping | N never liquid (drop = exactly P, R holds N inside position) → no proceeds to recycle. ✅ |
| 10.3/10.4 | Spot-sell+short / discounted buy-in | No liquid TAO proceeds at open → no cheaper-swap / extraction path. ✅ |
| 10.5 | Flash parameter manipulation | T_ref=min(live,EMA) + caller bound: sandwich (TAO halved) reverts SlippageTooHigh. ✅ |
| 10.6 | Subnet-operator self-shorting | No free extraction (proceeds retained, carry paid, liability repaid). ✅ |
| 10.7 | Short-driven dereg acceleration | Pre-dereg spot crash paid attacker equity = 0 (K_D=max(spot,EMA) uses high EMA); cold-EMA floored equity at the floor P. ✅ |
| 10.8 | Network-wide flow suppression | Derivative legs write zero SubnetTaoFlow (open/top-up/close Δ=0) → can't manufacture emissions flow. ✅ |
5. §14.5 TRADING-GAMES GATE — coverage
| Gate item | Status |
|---|---|
1. Same-block stacked opens can't bypass S_max |
✅ Sybil/capacity tests |
| 2. Short-driven dereg accel bounded by K_D(Q) | ✅ pre-crash equity=0 |
| 3. Cross-subnet suppression (no subsidized flow) | ✅ flow-neutral on all legs |
| 4. Long flow recycling | |
| 5. Long dereg recovery | |
| 6. Terminal settlement under dereg (disjoint buckets, no forced AMM close, no mint) | ✅ 65-position dereg in 1 block, recycle to pool, no mint |
6. §17 INVARIANT MAP (all 11)
1 shorts-first ✅ · 2 covered P+N=C ✅ · 3 no-liquid-proceeds ✅ · 4 fixed Q/D ✅ · 5 continuous
unwind ✅ · 6 no price-based liquidation (default iff R≤dust) ✅ · 7 limited recourse (Q
extinguished) ✅ · 8 footprint cap S ✅ · 9 single κ per side ✅ · 10 flow neutrality ✅ · 11 optional
OI caps (deferred, not enabled) — n/a.
7. §15.5 TERMINAL ORACLE
Spec example (C=72.5, Q=3900, K_spot=60, K_EMA=66 → equity 6.5) — the on-chain settlement
implements equity = max(0, (P+R) − max(K_spot,K_EMA)) exactly: with EMA above the collateral claim
the trader receives no terminal profit even when they pushed the subnet into dereg (verified:
crashed-spot equity = 0; cold-EMA equity = floor P).
8. DoS / overload / bloat (prior pass, recap)
Decay-hook block time flat 305–306 ms across 1→8 active subnets; active-set bounded by subnet
count not positions; 65-position dereg settled in one block with no production stall; 178 B
per position; worst-case state ≤ ~5.87 MB at full saturation (governance-capped). No
weight-metering yet — tracked pre-mainnet item.
Residuals (unchanged, all pre-mainnet, none testnet-blocking)
- Un-benchmarked extrinsic/hook weights (structurally bounded; spam vector only under mainnet economics).
- Load-bearing upstream
pEMA ≤ 1.0clamp + EMA half-life assumption (no CI guard). - Long-side trading-games items 4–5 not run (longs gated off at launch by design).
- §10.3/10.4 covered via the no-liquid-proceeds invariant rather than explicit profit-comparison sequences.
Honest comprehensiveness statement
This campaign exercised every §17 invariant and every §10 attack class on-chain, swept LTV and
decay across their governance ranges, quantified the long/short price-impact asymmetry against §14.2,
and matched the §14.3 decay curve and §15.5 terminal oracle. It is not an exhaustive fuzz of all
parameter combinations, and long-side trading games remain gated off by design. Within the
shorts-first launch scope, coverage is comprehensive.
…grades; add EMA-slowness invariant - Document the three intentional deviations from v3.6.1 text (CPMM K_EMA buyback vs scalar Q*pEMA; one-sided restoration/settlement zaps) as conservative upgrades the spec text must adopt; flag that the §15.5 example no longer reproduces for large Q. - Promote the short-dereg defense to an explicit governance invariant: the price EMA half-life must be slow relative to time-to-dereg so cumulative carry >= bounded terminal equity, defeating short-to-dereg extraction and whale subnet extortion. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Re-score + intended spec-upgrade note (after the max-effort spec-compliance pass)Updated scores (re-confirmed with the full v3.6.1 1:1 mapping + LTV/decay sweeps + price-impact + §10 attack evidence):
The three deviations are INTENDED — spec/design upgrade required (not bugs)The implementation deliberately diverges from three literal v3.6.1 formulas, in every case toward a
Action item folded into EMA must be SLOW — explicit governance invariant (short-dereg / whale-extortion defense)Because Verified on-chain: a pre-dereg spot crash paid the attacker equity = 0 while carry kept Committed in |
Extended testing pass — long-side sweeps, op semantics, conservation accounting, deviation quantificationThird campaign pass on the mainnet-seeded 3-validator Battery 5 — LONG-side invariant sweep across λ_L ∈ {0.25,0.5,0.75,0.9} — 25/25
Battery 6 — op semantics (spec §8.2 / §8.3 / §8.6) — 10/10
Battery 7 — conservation & bookkeeping (inv 5, §3.5)
Battery 8 — quantifying the A1 deviation (CPMM K_EMA vs scalar) + domain boundsThe intended A1 deviation, measured: impl K_EMA =
→ the scalar under-charges a large-Q closeout; the implementation's CPMM buyback is strictly
Battery 9 — long-side rejection paths — 11/11
Cumulative campaign statusAcross all passes: realistic mainnet-seeded init; full short+long lifecycles; every §17 invariant Residuals unchanged: un-benchmarked weights, the upstream |
Parameter-choice study — closing the two open sweep gapsCompletes the parameter analysis: the depth-awareness of κ, and the κ×λ interaction matrix. Battery 10 — κ → max price impact is DEPTH-INDEPENDENTMax single-short price impact at the capacity cap, measured across pool depths 1k→200k τ
Result: the max % price impact at a given κ is identical across every pool depth. κ is a pure (κ=0.02 admitted no viable open at these min-inputs — the cap is below the minimum position, an Battery 11 — κ × λ cross-product: every cell safe
Every cell either opens with bounded E/R (≤1.058 < 1.5 spec ceiling), footprint within κ·T_ref, and
Final parameter conclusions (data-backed)
Convergence: the parameter method and conservative launch defaults are now fully data-backed Campaign-wide convergence statementSuccessive passes have reached diminishing returns — the last ~90 checks surfaced zero new |
…open guard, safer recycle Acts on an external PR audit: - #1 terminal settlement order-dependence: price every position's K_D/cover against ONE frozen pre-settlement reserve snapshot (spec §11.1) instead of reserves mutated by earlier positions' escrow restoration. Short + long. Adds order-independence regression tests (short + long) vs the frozen snapshot. - opentensor#7 warm-EMA open guard: reject open_short/open_long with ColdEmaNotAllowed when pEMA==0 (fresh subnet); removes the cold-EMA griefing surface. Converts the prior cold-EMA-open test to expect rejection-then-admit-after-warm. - opentensor#8 recycle_custody_tao: withdraw-then-reduce-issuance (is_ok-gated) so a capped withdraw shortfall can never desync TotalIssuance. - docs: mark in-kind close + fee-pool divergence + warm-EMA gate in DESIGN.md. - #4 long terminal equity survival verified already handled (settle-before-destroy ordering + dereg_long_equity_survives_full_dissolve_path); no code change. 78 derivatives tests + 1261 full pallet suite pass. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Response to the static audit — fixes landedThanks for the careful read. All blocking/high + actionable medium/low items are now fixed,
Independent re-review of the fixes: security 9/10 and quality 9/10 — every fix CORRECT, no regressions, no new vulnerabilities. Remaining notes are non-actionable: the frozen snapshot deliberately excludes in-loop escrow restoration (spec §11.1; equity stays bounded by the Verdict stands: merge behind disabled flags for testnet / trading-games; the four production-enablement gates you listed are now either fixed (#1, opentensor#7, opentensor#8), verified-handled (#4), or remain tracked pre-mainnet (#3 weights, #6 EMA hardening). |
On-chain verification of the audit fixes (rebuilt localnet)Per request, the audit fixes are now verified on a live chain, not just the Rust suite. I rebuilt
Method note / honest caveat. The localnet seeds mainnet pool state via Bottom line: the two behavioral fixes that touch active short code — order-independent terminal |
On-chain verification — COMPLETE, including #4 (correction to the prior note)All four audit fixes are now verified on the live rebuilt localnet, including #4 with no Correction on #4The earlier #4 — long equity survives full dissolve (REAL state, control-isolated)Subnet built entirely from real operations (registration genesis+lock + real
Only the EMA price reference was set via Full on-chain verification summary (all fixes, rebuilt binary)
Plus the Rust suite: 78 derivatives + 1261 full pallet, 0 failures. The fixes are now verified |
…micity + guards Acts on the second external audit: - #1 (blocker) split-neutrality: terminal cover is now priced ONCE on the aggregate liability (q_sigma / d_sigma) against the frozen snapshot and allocated per-position pro-rata (ceiling), so the convex CPMM buyback can no longer be gamed by splitting a liability across coldkeys (Σ k_i ≥ K(ΣQ)). Short + long; adds split-neutrality + order-independence (aggregate) tests. - #3 dissolve atomicity: do_dissolve_network is now #[transactional] so a failure in destroy_alpha_in_out_stakes / clear_protocol_liquidity rolls back the derivative terminal settlement. - #5 hard compile-time MAX_POSITIONS_CEILING (1024) clamped in both max-positions setters, bounding dereg/decay work regardless of governance. - #6 quote_open_short/long now mirror the open rejections (cold EMA, below-min, capacity) so quotes are unavailable exactly when an open would reject. - #4 tiny-pEMA capacity self-limiting test; #2 doc: terminal K_D is derivative-internal CPMM accounting (conservative), now split-neutral. 80 derivatives tests + 1264 full pallet suite pass. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Response to the second audit — split-neutrality blocker fixed (+ items 2–6)All issues from the re-review are addressed in commit #1 (BLOCKER) — terminal settlement is now split-neutralRoot fix: terminal cover is priced once on the aggregate liability against the frozen snapshot, Because On-chain proof (5 split shorts on a real, funded subnet; traders post TAO floor only so their
→ the splitter recovers #2 — terminal K_D semantics (doc)DESIGN.md now states explicitly: terminal #3 — dissolve atomicity
#4 — warm-EMA maturityAdded #5 — bounded workAdded a hard compile-time #6 — quote/open parity
Verification
Per your recommendation, this clears item 1 (and 2–6). Remaining for production enablement / high κ |
Final on-chain sweep — latest binary (all audit fixes), every stone turnedRebuilt the localnet on the latest commit (
Cumulative live verification (across the campaign, latest binary)
Combined with the Rust suite (80 derivatives + 1264 full pallet, 0 failures) and the independent Standing pre-mainnet items only (unchanged): benchmarked weights (decay + dereg) and the |
… hooks Adds FRAME v2 benchmarks for open/top_up/close/default (short+long) and, per the audit's pre-mainnet ask, the per-block decay hooks (run_short_decay/run_long_decay with a Linear<0,64> active-subnet component) and the terminal dereg settlement (settle_shorts/longs_on_dereg with a Linear<0,128> position component) so their O(N) cost is measured and weight-bounded. Compiles under --features runtime-benchmarks. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
…chmark runs The multi-subnet decay benchmarks (run_short_decay/run_long_decay, up to 64 subnets) exhausted the mint path when each deriv_subnet funded the pool account with 1e15 rao. Cap it at 1e12 (1000 TAO) — still ample for opens, lets the O(s) decay benchmark complete. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Weight benchmarks — derivative extrinsics + the O(N) hooks (the pre-mainnet weight item)Added FRAME v2 weight benchmarks for all 8 derivative extrinsics and, per the audit's pre-mainnet Measured below on a laptop (Apple aarch64, Extrinsics — O(1), min execution time
O(N) hooks — measured linear, bounded at the caps
What this closes / what remains
Feature remains default-off; this clears the weight-benchmarking prerequisite for the pre-mainnet |
…ecay hook - Add the 12 derivative WeightInfo methods (trait + SubstrateWeight + () impls) from the benchmark run. - The 8 derivative extrinsics now use T::WeightInfo::* instead of placeholder DbWeight reads_writes. - on_initialize accounts the per-block decay hooks (run_short_decay(n) + run_long_decay(n), n = subnet count) so the O(active subnets) block-step cost is weight-charged. Numbers are from a local bench run (laptop, steps=20/repeat=5) and must be regenerated on CI reference hardware (--extrinsic '*') before mainnet; the structure/wiring is hardware-independent. 1264 pallet tests pass. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Weights wired end-to-end (follow-up to the benchmark harness)The benchmarked weights are now wired into the runtime — closing the last mechanical piece of the weight-benchmarking item. Commit
1264 pallet tests pass, no regressions. Honest scope note (unchanged): these weight numbers are from a local laptop run ( |
… clippy clean - dissolve_network / root_dissolve_network now charge T::WeightInfo::settle_shorts_on_dereg / settle_longs_on_dereg at the hard position ceiling, so the O(positions/subnet) terminal settlement is bounded by the dispatch weight. - simplify the quote cold-EMA guards (`== 0`) to clear two clippy boolean-simplification warnings; logic unchanged (pEMA is non-negative). fmt + clippy clean; 80 derivative tests pass. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Consolidated summary — covered shorts/longs (continuation of opentensor#2764)This comment ties together the full body of work on this branch. Verdict: complete and testnet-ready What this isPool-borrowing covered shorts & longs on the Alpha/TAO CPMM (spec v3.6.1, Fixed-Liability Hardening landed (across 3 adversarial cycles + 2 external audits)
Verification
Weights (audit's pre-mainnet ask) — benchmarked and wiredFRAME v2 benchmarks for the 8 extrinsics + the two O(N) paths. Measured (laptop, indicative):
Two external audits — every finding resolved
Remaining (pre-mainnet only — not testnet blockers)
Recommendation: merge behind the disabled flags for testnet / trading-games. Production enablement |
… doc reconcile Acts on the third external review: - #1 add dereg_settlement_rolls_back_on_later_failure: proves the (already present) #[transactional] on do_dissolve_network rolls back terminal settlement (position + custody + aggregate) when a later dissolve leg errors. - #2 terminal settlement transfer failures are no longer silent: the custody-invariant-breach branches log::error! loudly (kept best-effort so the dereg sweep always completes; the emitted equity already reflects only what was actually paid, so no silent underpayment/false claim). - #3 reconcile DESIGN.md to ONE authoritative model: derivative lifecycle legs are one-sided reserve mutations (CPMM-internal accounting), terminal K_D is CPMM-internal not fee/weight-aware live swap, sim_swap is read-only-quote-only, fee/weighted divergence is an accepted launch approximation gated by trading games. 81 derivative tests pass; fmt + clippy clean. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Response to the third review — all four send-back items addressedThanks. Two of these were already fixed in commits after the snapshot you fetched — 1 (High) — dissolve transactionality: ALREADY FIXED + now regression-tested
2 (Med/High) — terminal transfers no longer silentThe escrow-restore and equity-payout transfers in
3 (Med) — DESIGN.md contradiction removedCleaned to one authoritative model: derivative lifecycle legs (open/close/restoration/terminal) are one-sided reserve mutations using the spec CPMM closed-forms; terminal 4 — weights + trading-games as pre-enable gates
Net: all four send-back items are closed (#1 verified+tested, #2 loud, #3 doc, #4 benchmarked+wired). 81 derivative tests + 1264 pallet suite pass; fmt + clippy clean. Still shorts-first, longs gated, governance-OFF. This should clear the path from "disabled merge" toward enablement once the trading-games matrix runs on a mainnet replica. |
…report weights status - DESIGN.md §3.5 mitigation now says "realize as one-sided reserve mutations" (the authoritative CPMM-internal model), removing the last contradicting line. - QA_REPORT.md: weights are now benchmarked + wired; residual pre-mainnet items are the CI weight regen and the adversarial trading-games matrix (with the EMA-slowness margin), not code-correctness gaps. Long open shown on-chain. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
|
Thanks for the approval. The two polish items are addressed in
Net state: merge-ready behind disabled flags / testnet trading-games. Shorts-first, longs gated, |
…caps Widen the O(N) hook benchmark component ranges to match the values the runtime actually charges, closing the range-mismatch flagged in review: - run_short_decay / run_long_decay: s in [0,128] (= DefaultSubnetLimit, the TotalNetworks ceiling on_initialize charges) - settle_shorts_on_dereg / settle_longs_on_dereg: p in [0,1024] (= MAX_POSITIONS_CEILING, the value dissolve extrinsics charge) Regenerated weights.rs from the wider sweep; per-iteration cost stays linear up to the caps, so the dispatch charges are now measured at the worst case rather than extrapolated. 81 derivative tests pass. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Review-5 range mismatch — fixed (weights now benchmarked at the worst-case charged values)The one real ops item from the last review is closed in Decay hooks (
Terminal settlement (dissolve extrinsics charge
Regenerated No behavior change — the per-iteration cost was already O(1), this just makes "benchmarked at the bound" literally true. 81 derivative tests + full pallet suite pass; fmt/clippy clean. Still shorts-first, longs gated, Remaining pre-enable gates unchanged: regenerate the constants on CI reference hardware, and run the adversarial trading-games matrix on a mainnet replica before any κ ramp. |
…ctual count Remove MAX_POSITIONS_CEILING (1024). It was a stopgap "until weights are benchmarked / settlement is paginated"; weights are now benchmarked, so the hard compile-time cap is no longer justified and was inconsistent with the chain's existing unbounded per-subnet dereg work (destroy_alpha_in_out_stakes enumerates every staker with no cap). - Setters: Short/LongMaxPositions are now the sole per-subnet open-position limit (enforced at open, default 128, governance-configurable) — no hard ceiling clamp. - Dissolve (root-only) now charges the benchmarked linear settlement weight at the *actual* ShortPositionCount/LongPositionCount(netuid) rather than a flat 1024 — accurate for any count, parity with (and better than) the flat alpha unwind charge. - Fix stale/incorrect comment: per-block decay is O(active subnets) via the aggregate + Omega index, NOT O(positions); the cap never bounded it. - hooks.rs: document the operational invariant that decay WeightInfo is benchmarked over [0,128]; raising subnet count past 128 requires regenerating decay weights (or clamp/paginate) first. 81 derivative tests pass. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Dropped the hard position ceiling (parity with the alpha-stake unwind)Per the design discussion, removed What changed
Operational invariants (documented in
81 derivative tests pass. Unchanged: do not production-enable shorts or ramp κ until the adversarial trading-games matrix is run (EMA-slowness, sustained max-cap suppression, dereg terminal edge cases, cross-subnet flow games). |
…enablement checklist - run_short_decay doc no longer claims decay is "currently unmetered / pre-mainnet benchmarking" — it is now metered in on_initialize via WeightInfo::run_short_decay. - QA_REPORT: correct the weights bullet (settlement charged at actual position count, not a ceiling; decay/[0,128], settlement/[0,1024] ranges). - QA_REPORT §7.1 pre-enablement checklist: trading-games matrix (incl. one-sided accounting validation), pEMA clamp/half-life dependency, high-price-subnet caveat, decay-weight regen if subnet count > 128, CI weight regen. - QA_REPORT §7.2 accepted tradeoffs: terminal transfer failure = log + sweep. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
… fix dissolve doc-comment Follow-ups from an internal adversarial/quality/architecture review pass of the cap-removal delta (all three lenses cleared it; no CRITICAL/HIGH): - dissolve_network/root_dissolve_network: note that settlement weight is benchmarked over [0,1024] and counts above that extrapolate the per-position slope (scaled, not under-charged) — regen if Short/LongMaxPositions > 1024. Mirrors the existing decay [0,128] note in hooks.rs. - Fix stale dissolve_network doc-comment: "caller must be the owner" -> root (both dissolve paths are ensure_root). - QA_REPORT: fold the settlement [0,1024] validation boundary into the decay-vs-128 pre-enablement checklist item. (Note: a "debug_assert/clamp the decay charge at 128" suggestion was considered and rejected — on_initialize charges run_*_decay(TotalNetworks) with the full count, so it extrapolates linearly and does NOT under-charge above 128; clamping would cause the under-charge it aimed to prevent. The boundary is a validation matter, handled by documentation.) Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
… alpha parity) Following the cap analysis: the only on-chain O(positions) operation is terminal dereg settlement, which is exact parity with the uncapped destroy_alpha_in_out_stakes alpha unwind (root-only, one-shot, weight-charged at actual count; alpha is actually heavier — it sorts O(N log N)). Per-block decay is O(active subnets) via the aggregate+Omega index, NOT O(positions). Economic exposure is already bounded by the kappa capacity limit and per-position size by min-input. So the open-time Short/LongMaxPositions count limit was redundant and has no analogue on the staking side — removed for full parity with how alpha is liquidated on dereg. Removed cleanly (no dead code): - open_short/open_long: drop the count-limit check (keep the hotkey-mismatch merge guard). - set_short/long_max_positions setters; ShortMaxPositions/LongMaxPositions storage + DefaultShortMaxPositions; ShortPositionLimit/LongPositionLimit error variants. - admin-utils: sudo_set_short_max_positions (call_index 103) and sudo_set_long_max_positions (109). Indices left as gaps (no renumbering). - Tests: position_count_cap_enforced_and_maintained -> position_count_maintained (keeps the increment/decrement/merge count assertions, which still feed the dereg weight charge); long_merge_mismatch_and_position_cap -> long_merge_mismatch. ShortPositionCount/LongPositionCount are retained — still used for empty/active detection and the dissolve weight charge. Note: the auto-generated open_short/open_long weights still list a (now-removed) ShortMaxPositions read, so they over-charge by one DB read until the next benchmark regen (already a tracked pre-enablement gate). Full pallet suite: 1265 passed / 0 failed; admin-utils compiles. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Follow-up from the internal review of the position-limit removal: the inline dispatches.rs comment was updated but QA_REPORT §7.1(4) still referenced the now-removed Short/LongMaxPositions knob and a "MaxPositions > 1024" condition. Reworded to: settlement weight extrapolates linearly above the benchmarked 1024, there is no per-subnet open-position cap (parity with alpha unwind; bounded by κ capacity + min-input), regen if subnets routinely exceed 1024 positions. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
… explicit Per reviewer: the no-cap model is acceptable because derivative terminal settlement is intentionally the same immediate enumerate-and-settle sweep as native subnet alpha liquidation — but the PR must say so and stop implying a cap exists. - DESIGN.md §3.4: add an explicit "settlement model" paragraph — immediate sweep like destroy_alpha_in_out_stakes, open-position count NOT protocol-capped (no MAX_POSITIONS_CEILING, no Short/LongMaxPositions), work bounded operationally by position-creation economics (floor+escrow, κ capacity, min-input). State that the benchmark p∈[0,1024] is a calibration range, not a consensus cap. - Reword the two remaining stale "Position limit was validated" comments (mod.rs/long.rs) and the count-invariant test comment to say there is no cap and the count is denormalized bookkeeping for the dereg weight charge + active detection. - QA_REPORT pre-enablement checklist: trading-games must include position-count stress (not just economics); add a dereg weight/latency monitoring item at high synthetic position counts; note the stale open_short/open_long weight annotation (over-charges one read, cleared by the mandatory CI regen). Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
…lears stale read) Residual from the position-cap removal: open_short/open_long no longer read the removed ShortMaxPositions/LongMaxPositions storage, so the benchmark over-charged one DB read with a stale storage-proof annotation. Rebuilt the runtime-benchmarks node on the current code and regenerated both extrinsics (20 steps x 5 repeats, compiled wasm); spliced into both the SubstrateWeight<T> (T::DbWeight) and () (RocksDbWeight) impls. open_short reads 17->16, open_long unchanged-count but re-measured. Zero MaxPositions references remain anywhere in the repo. QA_REPORT: the open weights no longer carry the stale annotation; final constants still pending the CI reference-hardware regen. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Covered continuous-unwind shorts/longs — hardening + parity (continuation of opentensor#2764)
Continues @unconst's opentensor#2764 (Pool-borrowing spec, adapted from
shorting.pdf/ Fixed-Liability Covered Continuous-Unwind Model v3.6.1). Same scope and posture: shorts-first, entire feature default-disabled (ShortsEnabled/LongsEnabled = false) and governance-gated — nothing activates without an explicit governance action. This branch builds on opentensor#2764's head and adds security/architecture hardening, long-side read parity, numerical fixes, benchmarked weights, an immediate-sweep terminal-settlement model, and a large invariant/adversarial test campaign.1. Safety / correctness hardening
open_short/open_longtake a caller-signedmax_alpha_liability/max_tao_liabilityand rejectSlippageTooHighbefore any mutation (TaoBalance::MAX/AlphaBalance::MAXopts out). The design specified a bound the original extrinsics didn't carry.TotalStakeaccounting.#[frame_support::transactional]on all eight money functions (open / top-up / close / default, both sides). Dispatchables here are not auto-rolled-back, so a mid-path transfer failure could otherwise partial-commit (burned stake / stranded floor / unbacked reserve credit). Now all-or-nothing, with a forced-failure rollback test.K_D = max(K_spot, K_EMA)computed as a u128 ceiling-rounded CPMM buyback (both legs slippage-aware:K_spoton live reserves,K_EMAon the EMA-implied reserveT_EMA = pEMA·A_live). Fixes anI64F64rao² overflow in the original scalart·qform that silently zeroed the slippage cover; adds a cold-EMA floorK_D ≥ Rso a forced-dereg attacker can't recover the pool-origin buffer; emits only the equity actually paid.Σ kᵢ ≥ K_Σ). Result is order- and split-independent (regression-tested both ways).run_short_decayperforms the custody→pool restoration transfer first and advances Ω / shrinks the Σ aggregates / credits reserves only if it lands; on failure it leaves the aggregate and Ω untouched and retries next block. Per-positionexp(−ΔΩ)decay can never run ahead of TAO still in custody — the custody ≥ obligations solvency invariant holds even on a failed transfer.do_dissolve_networkis#[frame_support::transactional]; terminal settlement runs before the fallibledestroy_alpha_in_out_stakes/clear_protocol_liquiditylegs, all in one storage layer (forced-failure rollback test added). Terminal transfer failures are loud (log::error!on a custody-invariant breach) rather than silent, while a dereg still completes — the emitted equity reflects only what was actually paid.solve_collateraluses the cancellation-stable rootC = 2P / (b + √(b²+4aP));buyback_cost_raoparams renamed to asset-neutral(pay_reserve, recv_reserve, recv_amount)so the long side's swapped-operand call reads correctly. Follows house conventions (rao² products → u128/U256, neverI64F64;safe_div; ceiling/floor discipline).SubnetAlphaOutguard on long open/top-up; materialization factor clamped to ≤ 1 (no inflation).2. Position model — no protocol cap, immediate enumerate-and-settle
The per-subnet open-position cap was removed entirely (first the hard compile-time
MAX_POSITIONS_CEILING, then the governanceShort/LongMaxPositionslimit, setters, storage, errors, and admin extrinsics). Terminal settlement is an immediate enumerate-and-settle sweep on dereg, modeled on native subnet alpha liquidation (destroy_alpha_in_out_stakes, which itself enumerates unbounded stakers): open-position count is not protocol-capped; settlement work is bounded operationally by position-creation economics (floor + escrow, theκcapacity limit, min-input). Decay is O(active subnets) via the per-subnet aggregate + Ω index — not per-position.Short/LongPositionCountis retained for the dereg weight charge and active/empty-set detection. Validated on-chain: 140 positions opened on one subnet (no cap), dissolve swept all in one pass.3. Weights — benchmarked and wired
FRAME v2 benchmarks for all 8 extrinsics plus the O(N) hooks —
run_short_decay/run_long_decay(active-subnet component, benchmarked over[0,128]=DefaultSubnetLimit) andsettle_shorts_on_dereg/settle_longs_on_dereg(position component, over[0,1024]). The dispatches useT::WeightInfo::*;on_initializecharges the per-block decay atTotalNetworks; the dissolve extrinsics charge terminal settlement at the actual per-subnet position count. Benchmarked at worst-case bounds; the[0,1024]range is a calibration range, not a consensus cap. Remaining: regenerate the constants on CI reference hardware before enablement.4. Long-side read/RPC parity (was shorts-only)
quote_open_long,quote_close_long,get_long_position(s),get_subnet_long_state(+LongOpenQuote/LongPositionInfo/LongMarketInfo/CloseLongQuote) wired intoDerivativesRuntimeApiand the runtime. A long'sest_close_cost = D(close repaysDTAO directly — no pool swap, hence no slippage term, deliberately unlike the short buyback).5. Intended spec upgrades (not bugs)
Three implementation choices deliberately upgrade the v3.6.1 spec text and are documented as such: terminal
K_Das a CPMM buyback (vs scalarQ·pEMA, which understates large-Qcost and is gameable via forced-dereg price suppression); lifecycle legs realized as one-sided protocol reserve mutations using the spec closed-forms (vs fee/weightedSwapHandler::swap);sim_swapused as a read-only quote layer only. The fee/weighted-pool divergence is an accepted launch approximation gated by the trading-games suite. The load-bearingpEMAcaveat (SubnetMovingPrice = EMA(min(spot,1.0)), ~30-day half-life, 0 at cold start → guarantees hold for price ≤ ~1.0, true for every mainnet subnet today; half-life is a governance-tuned invariant) and the EMA-slowness invariant are documented inDESIGN.md.Testing & on-chain verification
pallet-subtensorlib suite 1265 passed / 0 failed;pallet-admin-utilsbuilds;cargo fmt+clippyclean. Coverage includes the spec worked-example, full-mixed-lifecycle TAO+alpha conservation, the terminal-settlement matrix (in-the-money / underwater / cold-EMA, both sides), dereg full-path long-equity survival through the stake-wipe, custody ≥ obligations under decay at theDecayMaxextreme (every tick), split-neutral and order-independent settlement, and count == map == active-set sync through churn.fast-runtime, pool/EMA state seeded exactly from a 128-subnet mainnet snapshot viaset_storage): every extrinsic, rejection path, attack vector, and the per-block decay hook exercised on a producing chain across several passes (stress / DoS / chain-bloat, long-side sweeps, op semantics, conservation accounting) — 180+ checks, 0 failures in aggregate; full short + long lifecycle and atomic rollback observed live; cap-removal validated (removed surface absent on-chain, 140-position dissolve swept clean).κ(−3.7%/−8.4%/−13.9% at κ=0.05/0.10/0.20 across pool depths 1k→200k τ), plus the κ×λ interaction matrix and LTV/decay sweeps.Review history (all resolved)
Multiple independent adversarial lenses (security / architect / exploiter / quality, reconfirmed 9/10, exploiter DEFEATED — no profitable extraction across self-short-to-dereg, sandwich, capacity-split, decay-drift mint, EMA manipulation, cross-side, RPC abuse), plus three static-audit rounds, each landed and re-verified on a rebuilt localnet:
5cc99aba7); proven exactly on-chain.Pre-enablement gates (remaining — operational, not code)
--extrinsic '*').κramp orShortsEnabledflip — including the EMA-slowness margin, sustained max-cap suppression, dereg terminal edge cases, position-count stress, and dereg weight/latency monitoring at high synthetic position counts.🤖 Generated with Claude Code