Skip to content

Replace basket set-weights with validator-directed alpha swaps (swap_basket_alpha) - #3151

Closed
unarbos wants to merge 2 commits into
mainfrom
cursor/basket-swap-alpha-5c25
Closed

Replace basket set-weights with validator-directed alpha swaps (swap_basket_alpha)#3151
unarbos wants to merge 2 commits into
mainfrom
cursor/basket-swap-alpha-5c25

Conversation

@unarbos

@unarbos unarbos commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Description

Feature request from Jacob Steeves: "Remove the set weights command for validator baskets. Instead, allow validators to actively manage — i.e. swap and trade — the basket alphas for each validator basket."

Summary

Root validators no longer declare a target weight vector for their beta basket. Every root dividend now always lands in place on the subnet it arrived on (the previous "uncurated" default), and the validator reshapes the fund directly with one new extrinsic:

SubtensorModule::swap_basket_alpha(origin_netuid, destination_netuid, alpha_amount)   // call index 150, hotkey-signed

It sells alpha_amount of the fund's origin_netuid holding and buys destination_netuid alpha with the proceeds, all inside the fund's escrow. Netuid 0 on either side is the fund's TAO cash slot, so one call covers every trade shape:

origin → destination effect
subnet → subnet swap one alpha for another (two legs through TAO)
subnet → 0 sell alpha, hold the TAO as fund cash
0 → subnet buy alpha with fund cash

Only fund composition changes. BasketShares, BasketRate, and every BasketClaimed watermark are untouched, so no staker entitlement moves; NAV changes only by the trade's slippage, borne pro-rata by all share holders. TotalStake is conserved and the root reserves are credited/debited in lockstep (credit_root_reserves / new debit_root_reserves). Both legs are booked as protocol flow so validator trading is neutral to the TAO-flow emission metric.

Removed (dead code with the old model)

  • set_root_weights (call 146 — index reserved, not reused), do_set_root_weights, the curated deposit flow in basket_flush.rs (try_distribute_root_alpha_batch, BasketFunding::Protocol), get_valid_basket_weights, get_validator_root_weights, and the curated exemption in the dust sweep.
  • Storage RootWeightSettingEnabled, RootWeightsCap; constants MIN_ROOT_BASKET_WEIGHTS, DEFAULT_ROOT_WEIGHTS_CAP.
  • AdminUtils sudo_set_root_weight_setting_enabled (103) and sudo_set_root_weights_cap (105) — indices reserved; their events kept as retired variants.
  • BetaBasketRuntimeApi::get_validator_weights runtime API + betaBasket_getValidatorWeights RPC, and the weights field of BasketSummary (freeze hash updated).
  • Migrations migrate_enable_root_weight_setting and migrate_set_root_min_allowed_weights; migrate_clear_root_basket_weights is bumped to clear_root_basket_weights_v3 and now also deletes the retired gate/cap storage.
  • Errors RootWeightSettingDisabled, RootWeightCapExceeded, BasketHasNoWeights and event RootWeightsSet are kept as retired variants for SCALE index stability (repo convention).
  • Rate-limit dispatch extension arm for set_root_weights; proxy call-group entry swapped for swap_basket_alpha (same SubtensorCommonCalls group).

Downstream

  • Python SDK: SetRootWeights intent, btcli root weights command group and validator_root_weights read removed; new SwapBasketAlpha intent (btcli tx swap-basket-alpha, supports --amount-alpha all); retired-error descriptions updated; codegen/check.py RAW_ONLY list updated.
  • ts-tests: setRootWeights helper replaced with swapBasketAlpha; the two claim-root zombienet suites no longer set weights.
  • clones/js-tests: the (not CI-wired) basket regression script exercises swap_basket_alpha.
  • Docs: docs/guides/root-reborn.mdx validator section rewritten; small wording fixes in staking.mdx / beta-tokens.mdx.
  • Tests: claim_root.rs / stake_into_basket.rs / basket_flush.rs reworked from curated vectors to "accrue in place, then trade"; new tests/swap_basket_alpha.rs (rejections, composition-only invariants, root-slot reserve lockstep, protocol-flow booking, rollback on zero-output buy).

Design decisions

  1. One extrinsic, three shapes. Netuid 0 as the cash slot on either side avoids separate buy/sell/swap calls and reuses the existing root-slot bookkeeping from claims/dissolution.
  2. Hotkey-signed, root-registered. Direct replacement of set_root_weights' authority model (the hotkey already directed how fund assets were deployed). Coldkey alternative listed under open questions.
  3. Fee-free legs. Matches the curated redeploy the extrinsic replaces and keeps the executed sale identical to the fee-free realizable NAV quote (so a trade never marks the fund up).
  4. Protocol-flow booking. Same as the old redeploy: prevents a validator from pumping a subnet's TAO-flow score by trading fund assets into it.
  5. Strict failure. Unlike claims, a terminally shallow origin pool or a zero-alpha buy is an error and the whole trade rolls back — a validator-directed trade must never silently write off fund value.
  6. No concentration cap, no rate limit. The request asks for free active management; Pays::Yes is the anti-spam gate.
  7. Dust sweep now unconditional. Without a vector there is no "curated" exemption; sub-threshold rows are consolidated on the next claim (documented).

Assumptions where the request was ambiguous

  • "Validators" = the root-registered hotkey (as with set_root_weights and set_weights).
  • "Swap and trade" is satisfied by a single generic origin→destination trade including the TAO cash slot; no limit orders / slippage-limit parameter.
  • Existing on-chain weight vectors and the gate/cap storage should be wiped on upgrade (nothing reads them any more).
  • stake_into_basket always mirrors current holdings (the previous uncurated behaviour); an empty fund parks the deposit as cash.
  • The BasketSummary.weights RPC field and get_validator_weights API are removed rather than kept as always-empty.

Open questions for Jacob

  1. Should the trade be signable by the owning coldkey (with hotkey as an argument, possibly via proxy) instead of / in addition to the hotkey? Hotkey compromise now allows arbitrary rebalancing (previously bounded by the 1/16 cap).
  2. Should validator trades charge swap fees (like user stake moves) rather than run fee-free like protocol redeploys?
  3. Do we want any concentration cap or rate limit on trades, or a limit_price / min-out parameter?
  4. Should a validator that is no longer root-registered still be allowed to rebalance a fund that still has share holders? (Currently rejected with HotKeyNotRegisteredInSubNet.)
  5. Should swap_basket_alpha flush pending basket deposits first (it does not; deposits price at NAV so ordering is fair either way)?

Related Issue(s)

  • Feature request from Jacob Steeves (no tracking issue linked).

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Other (please describe):

Breaking Change

  • SubtensorModule::set_root_weights (146), AdminUtils::sudo_set_root_weight_setting_enabled (103) and AdminUtils::sudo_set_root_weights_cap (105) no longer exist; clients must stop submitting them.
  • BetaBasketRuntimeApi::get_validator_weights / RPC betaBasket_getValidatorWeights are removed; BasketSummary (returned by betaBasket_getValidatorSummary / betaBasket_getAllBaskets) drops its weights field.
  • Storage RootWeightSettingEnabled and RootWeightsCap are removed and their values deleted by migration; stored Weights[ROOT] vectors are wiped.
  • Migration path: validators that want a non-default composition call swap_basket_alpha after the upgrade; stakers are unaffected (entitlements do not change).

Checklist

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have run ./scripts/fix_rust.sh to ensure my code is formatted and linted correctly (cargo fmt --check --all run per AGENTS.md; fix_rust.sh creates a commit and was not run)
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings (left to CI — no local build per AGENTS.md)
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes (left to CI per AGENTS.md)
  • Any dependent changes have been merged and published in downstream modules

Additional Notes

Advisory requirements a maintainer / CI must handle (not done here, per AGENTS.md):

  • spec_version: this is a runtime-affecting change; spec_version (currently 455 in runtime/src/lib.rs) must be bumped above mainnet's, or the PR labelled no-spec-version-bump. Not changed here.
  • Benchmark weights: WeightInfo::swap_basket_alpha is wired and a swap_basket_alpha benchmark is added, but the committed weight is an interim copy of the measured swap_stake figures (same shape: one sell, one buy). Apply the run-benchmarks label so CI measures it on the reference machine and produces the bench patch; sudo_set_root_weights_cap was removed from pallet_admin_utils weights.
  • Generated SDK bindings: sdk/python/bittensor/_generated/* (calls, errors, runtime APIs, storage) must be regenerated against a node built from this branch (python -m codegen <endpoint>). Until then codegen.check --coverage and the SDK intent table tests will flag set_root_weights / the two AdminUtils calls as unwrapped and swap_basket_alpha as missing from the builders. tests/fixtures/shape_corpus/corpus.json may also need refreshing.
  • Generated reference docs: docs/tx/, docs/query/, docs/errors/ and website/apps/bittensor-website/public/catalog/ need website/apps/bittensor-website/scripts/generate.py re-run from sdk/python (removes set-root-weights / validator-root-weights pages, adds swap-basket-alpha). Not hand-edited.
  • ts-tests descriptors: @polkadot-api/descriptors must be regenerated for swap_basket_alpha.

Preflight run locally (per AGENTS.md): cargo fmt --check --all (clean), ruff check . / ruff format --check . on sdk/python (clean, using a standalone ruff 0.15.20 matching uv.lock; the locked uv environment was not available so uv run --no-sync was not used), git diff --check (clean). Skipped because their environments were unavailable: generate.py --check, pnpm run fmt for ts-tests, codegen.check, pytest. No lockfile changes.

Open in Web Open in Cursor 

cursoragent and others added 2 commits September 11, 2026 12:53
…swap_basket_alpha)

Root validators no longer declare a target weight vector for their beta
basket. Dividends always accumulate in place on the subnet they arrive on,
and validators actively manage the fund with the new swap_basket_alpha
extrinsic: sell alpha of one holding and buy alpha of another (netuid 0 is
the fund's TAO cash slot on either side), all inside the escrow.

- Remove set_root_weights (call 146 retired), do_set_root_weights, the
  curated deposit flow, get_valid_basket_weights, get_validator_root_weights,
  RootWeightSettingEnabled / RootWeightsCap storage and their constants.
- Add swap_basket_alpha (call 150), BasketAlphaSwapped event, WeightInfo
  entry (interim, seeded from swap_stake) and benchmark.
- Remove AdminUtils sudo_set_root_weight_setting_enabled (103) and
  sudo_set_root_weights_cap (105); retire their events.
- Drop the BetaBasketRuntimeApi.get_validator_weights runtime API / RPC and
  the BasketSummary.weights field.
- Replace the enable/min-allowed-weights migrations with a v3 clear that
  wipes Weights[ROOT] and the retired gate/cap storage.
- Rework basket tests around in-place accrual plus explicit trades.

Co-authored-by: Arbos <unarbos@users.noreply.github.com>
- Python SDK: drop the SetRootWeights intent, the btcli root weights
  command group, and the validator_root_weights read; add the
  SwapBasketAlpha intent (btcli tx swap-basket-alpha); update error
  descriptions for the retired errors and the basket summary shape.
- ts-tests: replace the setRootWeights helper with swapBasketAlpha and
  stop the claim-root suites from setting weights (dividends accumulate in
  place).
- clones js-tests: exercise swap_basket_alpha instead of set_root_weights.
- docs: rewrite the Root Reborn validator section around trading.

Co-authored-by: Arbos <unarbos@users.noreply.github.com>
@vercel

vercel Bot commented Sep 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
subtensor Ready Ready Preview Sep 11, 2026 1:08pm UTC

Request Review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

Comment on lines +521 to +524
let bought = Self::swap_basket_tao_for_alpha_chunks(destination_netuid, tao, true)?;
// A buy that rounds to zero alpha would silently donate the fund's TAO to the
// pool; reject it (mirrors the direct-deposit dust rule).
ensure!(!bought.is_zero(), Error::<T>::AmountTooLow);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Require a signed minimum output for basket trades

pallets/subtensor/src/staking/claim_root.rs:521-524 accepts any positive output. The sell helper uses the minimum possible price and the buy helper uses the maximum, while the extrinsic provides no caller-selected bound. An observer can sandwich a pending validator trade by moving either pool against it and reversing afterward, extracting value from all basket holders while the transaction still succeeds. Unlike the former weight setter, this call immediately trades existing fund assets, potentially the entire holding. Add a signed minimum destination output (including TAO for destination 0), enforce it inside the transaction, and expose it through the SDK.

let held =
Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &escrow, origin_netuid);
ensure!(held >= alpha, Error::<T>::NotEnoughStakeToWithdraw);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Settle queued dividends before changing the root cash slot

pallets/subtensor/src/staking/claim_root.rs:467 begins the trade without flushing pending dividends. Trades involving netuid 0 change escrow_root, but deposit_root_alpha_batch reads that balance later and mint_basket_dividend_shares attributes the queued dividend using total_root / (total_root + escrow_root). For example, moving fund assets into root after a dividend was earned attributes part of that old dividend to cash that did not earn it, diverting value from current root stakers toward existing basket shareholders; moving cash out causes the reverse misallocation. NAV pricing does not correct this attribution change. Flush before mutating the fund, reject the trade if eligible credits remain unsettled, and account for the flush work. Add regression coverage with different root-stake and basket-share owners.

Comment on lines +1937 to +1946
/// Interim: seeded from the measured `swap_stake` figures (same shape — one sell, one
/// buy, stake reads/writes on two subnets) until the reference benchmark run replaces it.
fn swap_basket_alpha() -> Weight {
// Proof Size summary in bytes:
// Measured: `2505`
// Estimated: `10920`
// Minimum execution time: 371_000_000 picoseconds.
Weight::from_parts(388_000_000, 10920)
.saturating_add(T::DbWeight::get().reads(49_u64))
.saturating_add(T::DbWeight::get().writes(24_u64))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Cover multi-chunk swaps in the declared weight and benchmark

pallets/subtensor/src/weights.rs:1937-1946 charges a copied single-sell/single-buy weight, but the new call invokes reserve-bounded chunk loops on both legs. Inputs above a pool's max_swap_input execute additional swaps, each with repeated computation and storage accesses, for the same declared weight. A root validator can select a shallow destination and a large fund holding to exercise this path repeatedly, allowing block execution work to exceed its charged budget. The new benchmark uses a tiny holding against deep reserves, so simply regenerating weights from it does not cover this case. Bound execution chunks and benchmark their worst case, or meter them against a conservatively declared limit, including failed trades.

@github-actions

Copy link
Copy Markdown
Contributor

🛡️ AI Review — Skeptic (security review)

VERDICT: VULNERABLE

LOW contributor risk / baseline scrutiny: author has write permission and substantial merged history; Cursor co-authorship is disclosed; no listed Gittensor association. Branch targets main.

Static review found economic and resource-accounting vulnerabilities in the new trading path. No evidence of malicious intent or changes to the AI-review trust boundary.

Checks: git diff --check passed; git status --short was clean. Builds, tests, formatting tools, and dependency commands were not run under the Skeptic static-only restriction.

Findings

Sev File Finding
HIGH pallets/subtensor/src/staking/claim_root.rs:467 Settle queued dividends before changing the root cash slot inline
HIGH pallets/subtensor/src/staking/claim_root.rs:524 Require a signed minimum output for basket trades inline
HIGH pallets/subtensor/src/weights.rs:1946 Cover multi-chunk swaps in the declared weight and benchmark inline

Conclusion

The new trading path needs slippage protection, settlement of pending dividends before root-cash changes, and weight accounting that covers chunked execution before merge.


# 🔍 AI Review — Auditor (domain review) has not yet run on this PR.

@github-actions

Copy link
Copy Markdown
Contributor

🔄 AI review updated — Skeptic: VULNERABLE

@unarbos unarbos closed this Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants