Skip to content

Comments

Emission suppression via root validator voting#2420

Open
ppolewicz wants to merge 18 commits intodevnet-readyfrom
emission_suppression
Open

Emission suppression via root validator voting#2420
ppolewicz wants to merge 18 commits intodevnet-readyfrom
emission_suppression

Conversation

@ppolewicz
Copy link
Collaborator

Summary

Adds a mechanism for root validators to vote to suppress TAO emissions from subnets they believe are not operating in the network's interest. When more than half of the stake-weighted root validators vote to suppress a subnet, its emission share is zeroed and redistributed proportionally to the remaining subnets.

Key features

  • Stake-weighted voting: Each root validator's vote is weighted by their root stake. Suppression activates when >50% of total registered root stake votes to suppress.
  • Root override: A sudo extrinsic (sudo_set_emission_suppression_override) allows root to force-suppress or force-unsuppress any subnet, bypassing the vote outcome.
  • Root sell pressure control: A global flag (KeepRootSellPressureOnSuppressedSubnets, default true) controls whether root validators still receive alpha dividends from suppressed subnets. When disabled, all alpha goes to subnet validators instead — removing root sell pressure on the suppressed subnet's token.
  • Coldkey-keyed votes: Votes are keyed by coldkey (not hotkey) so they can be migrated during coldkey swaps. Swaps fail if the destination coldkey already has votes to prevent silent overwrites.
  • Per-epoch collection: Votes are collected and suppression ratios are recalculated once per epoch per subnet, keeping on-chain computation bounded.

New extrinsics

Call index Name Access
133 sudo_set_emission_suppression_override Root
134 vote_emission_suppression Signed (coldkey)
135 sudo_set_keep_root_sell_pressure_on_suppressed_subnets Root

New storage items

  • EmissionSuppression<NetUid → U64F64> — current suppression ratio per subnet
  • EmissionSuppressionOverride<NetUid → Option<bool>> — root override per subnet
  • EmissionSuppressionVote<(NetUid, AccountId) → Option<bool>> — per-coldkey vote per subnet
  • KeepRootSellPressureOnSuppressedSubnets<bool> — global flag (default true)

Tests

24 test scenarios covering:

  • Share zeroing and renormalization with majority/minority votes
  • Override force-suppress and force-unsuppress
  • Vote eligibility (root registration, minimum stake)
  • Vote clearing and epoch-only collection
  • Coldkey swap migration and conflict detection
  • Subnet dissolution cleanup
  • Root sell pressure flag behavior
  • Root subnet vote rejection
  • Multi-hotkey vote weight aggregation
  • Sudo event emission

Test plan

  • All 24 emission suppression tests pass (cargo test -p pallet-subtensor --lib)
  • Full pallet test suite passes
  • cargo clippy --workspace --all-features --all-targets clean
  • scripts/fix_rust.sh clean

🤖 Generated with Claude Code

ppolewicz and others added 7 commits February 11, 2026 23:14
- Add EmissionSuppression<T> storage map (NetUid -> U64F64) for
  stake-weighted suppression fraction per subnet
- Add normalize_shares() helper to re-normalize shares to sum to 1.0
- Add apply_emission_suppression() to zero shares of suppressed subnets
  (suppression > 0.5) and re-normalize remaining shares
- Call apply_emission_suppression in get_subnet_block_emissions after
  get_shares
- Clean up EmissionSuppression in remove_network

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add EmissionSuppressionOverride<T> storage map (NetUid -> Option<bool>)
  for root override of suppression per subnet
- Modify apply_emission_suppression to check override first:
  Some(true) forces suppression, Some(false) forces unsuppression,
  None falls back to vote-based EmissionSuppression value
- Add sudo_set_emission_suppression_override extrinsic (call_index 133)
- Clean up EmissionSuppressionOverride in remove_network

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add EmissionSuppressionVote<T> storage double map (NetUid, AccountId -> bool)
  for per-coldkey votes on subnet emission suppression
- Add vote_emission_suppression extrinsic (call_index 134):
  requires signed coldkey owning root-registered hotkey with stake >= threshold
- Add collect_emission_suppression_votes() called per-subnet on epoch:
  iterates root validators, accumulates stake-weighted votes, updates
  EmissionSuppression fraction
- Add transfer_emission_suppression_votes in do_swap_coldkey for
  coldkey swap migration
- Clean up EmissionSuppressionVote in remove_network
- New errors: CannotVoteOnRootSubnet, NotEnoughStakeToVote
- New event: EmissionSuppressionVoteCast

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
13 test scenarios covering:
- Share zeroing and renormalization with majority suppression
- No effect when suppression is below 50%
- Root override force suppress/unsuppress
- Override=None falls back to votes
- Vote requires root registration and minimum stake
- Vote clearing removes suppression
- Votes collected only on epoch
- Coldkey swap migrates votes
- Network dissolution clears all suppression state
- Share renormalization across 3 subnets
- Unstaked TAO not counted in suppression denominator

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add KeepRootSellPressureOnSuppressedSubnets<T> storage (default true):
  controls whether root validators receive alpha dividends from suppressed
  subnets
- Add sudo_set_keep_root_sell_pressure_on_suppressed_subnets extrinsic
  (call_index 135)
- Modify emit_to_subnets: when flag is false and subnet is suppressed,
  root_alpha is zeroed and all validator alpha goes to subnet validators
- Add is_subnet_emission_suppressed() helper to deduplicate suppression
  check logic
- Refactor apply_emission_suppression to use the new helper
- Add 3 tests (14-16): root alpha on suppressed subnet with flag on/off,
  and unsuppressed subnet unaffected by flag

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add events for sudo_set_emission_suppression_override and
  sudo_set_keep_root_sell_pressure_on_suppressed_subnets
- Fix missing read in sudo_set_emission_suppression_override weight
- Add early return guard in collect_emission_suppression_votes for root
- Fail coldkey swap if destination already has emission suppression votes
  (new error: DestinationColdkeyHasExistingVotes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- test_vote_on_root_subnet_rejected: CannotVoteOnRootSubnet error
- test_vote_explicit_false: Some(false) stored, produces 0 suppression
- test_all_subnets_suppressed: all shares zeroed, zero total emission
- test_coldkey_swap_blocked_by_existing_votes: swap fails with error
- test_multi_hotkey_coldkey_vote_weight: 3 hotkeys, weight = sum stakes
- test_sudo_override_emits_event: EmissionSuppressionOverrideSet event
- test_sudo_sell_pressure_emits_event: KeepRootSellPressureSet event
- test_collect_votes_skips_root: ROOT no-op guard

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ppolewicz ppolewicz added the skip-cargo-audit This PR fails cargo audit but needs to be merged anyway label Feb 12, 2026
@ppolewicz ppolewicz force-pushed the emission_suppression branch from 2dc0894 to 752f5c3 Compare February 12, 2026 14:48
@ppolewicz ppolewicz force-pushed the emission_suppression branch from c6158be to 11b0d5f Compare February 12, 2026 19:50
ppolewicz and others added 8 commits February 12, 2026 20:14
…hip workflow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nedHotkeys, R-9 root guard, R-11 cache netuids, R-12 call_index order, R-13 saturating fold, R-14 narrow lints, R-16 allow vote cleanup

- R-1: Move DestinationColdkeyHasExistingVotes check to top of do_swap_coldkey
  before any mutations; make transfer_emission_suppression_votes infallible
- R-2: Increase vote_emission_suppression weight from reads(5) to reads(131)
  to account for up to 64 hotkeys at 2 reads each plus 3 base reads
- R-5: Use OwnedHotkeys instead of StakingHotkeys for vote eligibility so
  only hotkeys owned by the coldkey qualify
- R-9: Add root subnet guard to sudo_set_emission_suppression_override
- R-11: Cache get_all_subnet_netuids in transfer_emission_suppression_votes
- R-12: Reorder extrinsics so call indices appear in ascending order (132-135)
- R-13: Replace .sum() with saturating_add fold in test
- R-14: Remove blanket #![allow(unused)] from test file
- R-16: Skip stake threshold check when clearing a vote (suppress=None)
  so deregistered coldkeys can clean up stale entries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix compiler warnings for unused variables by prefixing them with
underscores. All tests pass successfully.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…bnetsMode enum

Introduce a 3-mode enum (Disable/Enable/Recycle) to control how root alpha
dividends are handled on emission-suppressed subnets. Recycle mode (new
default) swaps root alpha to TAO via AMM and burns it, creating sell
pressure without benefiting root validators.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…king

- Use add_dynamic_network to properly initialize AMM for swap tests
- Add record_tao_outflow call after swap_alpha_for_tao in recycle path
- Check SubnetTAO decrease instead of SubnetMovingPrice (needs epoch)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dec indices, R-10 doc fix

- R-1: Add else fallback to recycle alpha when swap_alpha_for_tao fails
  in Recycle mode, preventing orphaned tokens
- R-3: Cache is_subnet_emission_suppressed result to avoid double
  storage read per subnet per block
- R-8: Add explicit #[codec(index)] annotations to enum variants for
  migration safety
- R-10: Fix misleading doc comment on Disable variant

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sam0x17 sam0x17 requested a review from a team February 13, 2026 16:46
ppolewicz and others added 3 commits February 13, 2026 20:31
…ression

* origin/devnet-ready: (81 commits)
  Bump spec version
  Update subtensor pallet weights
  Spec bump
  auto-update benchmark weights
  Bump spec version
  Add precompile integration tests
  Fix subnet_buyback benchmark
  Fix build errors
  Add freeze struct to TickIndex and PositionId
  More user liquidity cleanup
  Cleanup test_claim_root_coinbase_distribution test
  Remove unused user liquidity code
  Add current_alpha_price_all swap RPC, add slippage to sim_swap_tao_for_alpha and sim_swap_alpha_for_tao
  More balancer docs
  Balancer documentation
  Spec bump
  Add get_base_needed_for_quote method to balancer (for alpha fees)
  Add logging for errors in disable_lp
  Fix add_liquidity signature
  Bump spec version
  ...
…add constant, fix weight

R-1: Disable mode now actually recycles root_alpha to PendingValidatorEmission
     instead of zeroing it before computation. Validators get explicitly more.
R-7: Extract EMISSION_SUPPRESSION_THRESHOLD constant (0.5) to avoid magic number.
R-9: Rename suppression_mode → root_sell_pressure_mode for clarity.
R-12: Fix weight for sudo_set_emission_suppression_override: reads(1) → reads(2).

Adds test_disable_mode_recycles_root_alpha_to_validators which verifies that
Disable mode validators receive more than Enable mode by exactly the root
alpha amount.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ppolewicz
Copy link
Collaborator Author

Code Review Fixes (136c1a4)

Addressed findings from automated code review after merging devnet-ready:

R-1: Disable mode now actually recycles root alpha to validators

Previously, Disable mode zeroed root_alpha before computation — alpha "stayed" with validators by never being separated. Now it computes root_alpha normally and explicitly adds it to PendingValidatorEmission, matching the documented "recycled to subnet validators" behavior.

R-7: Extract suppression threshold constant

Replaced magic number 0.5 with EMISSION_SUPPRESSION_THRESHOLD constant in subnet_emissions.rs.

R-9: Clarify variable naming

Renamed suppression_moderoot_sell_pressure_mode in run_coinbase.rs to avoid confusion with the suppression fraction.

R-12: Fix weight estimation

sudo_set_emission_suppression_override now accounts for 2 reads (subnet existence check + storage op) instead of 1.

New test

test_disable_mode_recycles_root_alpha_to_validators — runs emission with Enable mode then Disable mode on the same suppressed subnet and asserts that Disable validators receive more alpha by exactly the root alpha amount from Enable mode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-cargo-audit This PR fails cargo audit but needs to be merged anyway

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant