Replace basket set-weights with validator-directed alpha swaps (swap_basket_alpha) - #3151
Replace basket set-weights with validator-directed alpha swaps (swap_basket_alpha)#3151unarbos wants to merge 2 commits into
Conversation
…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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| 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); |
There was a problem hiding this comment.
[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); | ||
|
|
There was a problem hiding this comment.
[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.
| /// 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)) |
There was a problem hiding this comment.
[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.
🛡️ 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: Findings
ConclusionThe 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. |
|
🔄 AI review updated — Skeptic: VULNERABLE |
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:
It sells
alpha_amountof the fund'sorigin_netuidholding and buysdestination_netuidalpha 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:Only fund composition changes.
BasketShares,BasketRate, and everyBasketClaimedwatermark are untouched, so no staker entitlement moves; NAV changes only by the trade's slippage, borne pro-rata by all share holders.TotalStakeis conserved and the root reserves are credited/debited in lockstep (credit_root_reserves/ newdebit_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 inbasket_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.RootWeightSettingEnabled,RootWeightsCap; constantsMIN_ROOT_BASKET_WEIGHTS,DEFAULT_ROOT_WEIGHTS_CAP.sudo_set_root_weight_setting_enabled(103) andsudo_set_root_weights_cap(105) — indices reserved; their events kept as retired variants.BetaBasketRuntimeApi::get_validator_weightsruntime API +betaBasket_getValidatorWeightsRPC, and theweightsfield ofBasketSummary(freeze hash updated).migrate_enable_root_weight_settingandmigrate_set_root_min_allowed_weights;migrate_clear_root_basket_weightsis bumped toclear_root_basket_weights_v3and now also deletes the retired gate/cap storage.RootWeightSettingDisabled,RootWeightCapExceeded,BasketHasNoWeightsand eventRootWeightsSetare kept as retired variants for SCALE index stability (repo convention).set_root_weights; proxy call-group entry swapped forswap_basket_alpha(sameSubtensorCommonCallsgroup).Downstream
SetRootWeightsintent,btcli root weightscommand group andvalidator_root_weightsread removed; newSwapBasketAlphaintent (btcli tx swap-basket-alpha, supports--amount-alpha all); retired-error descriptions updated;codegen/check.pyRAW_ONLY list updated.setRootWeightshelper replaced withswapBasketAlpha; the two claim-root zombienet suites no longer set weights.swap_basket_alpha.docs/guides/root-reborn.mdxvalidator section rewritten; small wording fixes instaking.mdx/beta-tokens.mdx.claim_root.rs/stake_into_basket.rs/basket_flush.rsreworked from curated vectors to "accrue in place, then trade"; newtests/swap_basket_alpha.rs(rejections, composition-only invariants, root-slot reserve lockstep, protocol-flow booking, rollback on zero-output buy).Design decisions
set_root_weights' authority model (the hotkey already directed how fund assets were deployed). Coldkey alternative listed under open questions.Pays::Yesis the anti-spam gate.Assumptions where the request was ambiguous
set_root_weightsandset_weights).stake_into_basketalways mirrors current holdings (the previous uncurated behaviour); an empty fund parks the deposit as cash.BasketSummary.weightsRPC field andget_validator_weightsAPI are removed rather than kept as always-empty.Open questions for Jacob
hotkeyas 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).limit_price/ min-out parameter?HotKeyNotRegisteredInSubNet.)swap_basket_alphaflush pending basket deposits first (it does not; deposits price at NAV so ordering is fair either way)?Related Issue(s)
Type of Change
Breaking Change
SubtensorModule::set_root_weights(146),AdminUtils::sudo_set_root_weight_setting_enabled(103) andAdminUtils::sudo_set_root_weights_cap(105) no longer exist; clients must stop submitting them.BetaBasketRuntimeApi::get_validator_weights/ RPCbetaBasket_getValidatorWeightsare removed;BasketSummary(returned bybetaBasket_getValidatorSummary/betaBasket_getAllBaskets) drops itsweightsfield.RootWeightSettingEnabledandRootWeightsCapare removed and their values deleted by migration; storedWeights[ROOT]vectors are wiped.swap_basket_alphaafter the upgrade; stakers are unaffected (entitlements do not change).Checklist
./scripts/fix_rust.shto ensure my code is formatted and linted correctly (cargo fmt --check --allrun per AGENTS.md;fix_rust.shcreates a commit and was not run)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 inruntime/src/lib.rs) must be bumped above mainnet's, or the PR labelledno-spec-version-bump. Not changed here.WeightInfo::swap_basket_alphais wired and aswap_basket_alphabenchmark is added, but the committed weight is an interim copy of the measuredswap_stakefigures (same shape: one sell, one buy). Apply therun-benchmarkslabel so CI measures it on the reference machine and produces the bench patch;sudo_set_root_weights_capwas removed frompallet_admin_utilsweights.sdk/python/bittensor/_generated/*(calls, errors, runtime APIs, storage) must be regenerated against a node built from this branch (python -m codegen <endpoint>). Until thencodegen.check --coverageand the SDK intent table tests will flagset_root_weights/ the two AdminUtils calls as unwrapped andswap_basket_alphaas missing from the builders.tests/fixtures/shape_corpus/corpus.jsonmay also need refreshing.docs/tx/,docs/query/,docs/errors/andwebsite/apps/bittensor-website/public/catalog/needwebsite/apps/bittensor-website/scripts/generate.pyre-run fromsdk/python(removesset-root-weights/validator-root-weightspages, addsswap-basket-alpha). Not hand-edited.@polkadot-api/descriptorsmust be regenerated forswap_basket_alpha.Preflight run locally (per
AGENTS.md):cargo fmt --check --all(clean),ruff check ./ruff format --check .onsdk/python(clean, using a standalone ruff 0.15.20 matchinguv.lock; the lockeduvenvironment was not available souv run --no-syncwas not used),git diff --check(clean). Skipped because their environments were unavailable:generate.py --check,pnpm run fmtforts-tests,codegen.check, pytest. No lockfile changes.