Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pallets/admin-utils/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,12 @@ mod benchmarks {
_(RawOrigin::Root, U64F64::from_num(0.61));
}

#[benchmark]
fn sudo_set_emission_bar_rank() {
#[extrinsic_call]
_(RawOrigin::Root, 64u16);
}

#[benchmark]
fn sudo_set_emission_gate_exponent() {
#[extrinsic_call]
Expand Down
16 changes: 16 additions & 0 deletions pallets/admin-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2036,6 +2036,22 @@ pub mod pallet {
Ok(())
}

/// Sets the emission bar rank (N): when non-zero, the emission gate bar
/// (theta) is pinned to the Nth-largest demand share instead of the
/// q-mass quantile, so the eligible set tracks rank N as the demand
/// distribution shifts. Setting 0 restores quantile mode. Also forces a
/// bar recompute on the next block so the change takes effect
/// immediately.
#[pallet::call_index(102)]
#[pallet::weight(<T as Config>::WeightInfo::sudo_set_emission_bar_rank())]
pub fn sudo_set_emission_bar_rank(origin: OriginFor<T>, rank: u16) -> DispatchResult {
ensure_root(origin)?;

pallet_subtensor::Pallet::<T>::set_emission_bar_rank(rank);
log::debug!("set_emission_bar_rank( {rank:?} ) ");
Ok(())
}

/// Sets the emission gate Hill exponent (h): cliff sharpness at the bar.
#[pallet::call_index(101)]
#[pallet::weight(<T as Config>::WeightInfo::sudo_set_emission_gate_exponent())]
Expand Down
25 changes: 25 additions & 0 deletions pallets/admin-utils/src/weights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ pub trait WeightInfo {
fn sudo_set_tao_flow_cutoff() -> Weight;
fn sudo_set_tao_flow_normalization_exponent() -> Weight;
fn sudo_set_emission_bar_quantile() -> Weight;
fn sudo_set_emission_bar_rank() -> Weight;
fn sudo_set_emission_gate_exponent() -> Weight;
fn sudo_set_tao_flow_smoothing_factor() -> Weight;
fn sudo_set_net_tao_flow_enabled() -> Weight;
Expand Down Expand Up @@ -1332,6 +1333,18 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
Weight::from_parts(2_000_000, 0)
.saturating_add(T::DbWeight::get().writes(2_u64))
}
/// Storage: `SubtensorModule::EmissionGateBar` (r:0 w:1)
/// Proof: `SubtensorModule::EmissionGateBar` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `SubtensorModule::EmissionBarRank` (r:0 w:1)
/// Proof: `SubtensorModule::EmissionBarRank` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn sudo_set_emission_bar_rank() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 1_000_000 picoseconds.
Weight::from_parts(2_000_000, 0)
.saturating_add(T::DbWeight::get().writes(2_u64))
}
/// Storage: `SubtensorModule::EmissionGateExponent` (r:0 w:1)
/// Proof: `SubtensorModule::EmissionGateExponent` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn sudo_set_emission_gate_exponent() -> Weight {
Expand Down Expand Up @@ -2705,6 +2718,18 @@ impl WeightInfo for () {
Weight::from_parts(2_000_000, 0)
.saturating_add(RocksDbWeight::get().writes(2_u64))
}
/// Storage: `SubtensorModule::EmissionGateBar` (r:0 w:1)
/// Proof: `SubtensorModule::EmissionGateBar` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `SubtensorModule::EmissionBarRank` (r:0 w:1)
/// Proof: `SubtensorModule::EmissionBarRank` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn sudo_set_emission_bar_rank() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 1_000_000 picoseconds.
Weight::from_parts(2_000_000, 0)
.saturating_add(RocksDbWeight::get().writes(2_u64))
}
/// Storage: `SubtensorModule::EmissionGateExponent` (r:0 w:1)
/// Proof: `SubtensorModule::EmissionGateExponent` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn sudo_set_emission_gate_exponent() -> Weight {
Expand Down
49 changes: 34 additions & 15 deletions pallets/subtensor/src/coinbase/subnet_emissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,11 +400,18 @@ impl<T: Config> Pallet<T> {

/// Recomputes the emission gate bar (theta) when due.
///
/// Theta is the q-mass bar: sort demand shares descending and accumulate
/// until the running total crosses `EmissionBarQuantile` (q); the share at
/// the crossing is the bar. Subnets above the bar collectively carry q of
/// demand. Because theta is a property of the demand distribution (not the
/// slot count), registering empty subnets does not move it.
/// Two selection modes, both properties of the demand distribution over
/// *positive* shares, so registering empty subnets does not move the bar:
///
/// * Rank mode (`EmissionBarRank` N > 0): theta is the Nth-largest demand
/// share, so exactly the top N subnets sit at or above the gate midpoint
/// regardless of how the distribution shifts. If fewer than N subnets
/// have demand, theta is the smallest positive share (everyone passes).
///
/// * q-mass mode (N == 0): sort demand shares descending and accumulate
/// until the running total crosses `EmissionBarQuantile` (q); the share
/// at the crossing is the bar. Subnets above the bar collectively carry
/// q of demand.
fn maybe_update_emission_gate_bar(shares: &BTreeMap<NetUid, U64F64>) {
let zero = U64F64::saturating_from_num(0);
let current_bar = EmissionGateBar::<T>::get();
Expand All @@ -417,23 +424,35 @@ impl<T: Config> Pallet<T> {
return;
}

let q = EmissionBarQuantile::<T>::get();
let mut sorted: Vec<U64F64> = shares.values().copied().collect();
sorted.sort_unstable_by(|a, b| b.cmp(a));

let mut cumulative = zero;
let mut theta = zero;
for share in sorted {
cumulative = cumulative.saturating_add(share);
theta = share;
if cumulative >= q {
break;
let rank = EmissionBarRank::<T>::get();
let theta = if rank > 0 {

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] Add tests for the new economic rank path

This introduces a new emission-selection formula without dedicated tests. Existing gate tests only exercise the unchanged quantile path. Add coverage proving the selected theta for rank 1 and an interior rank, the fallback when rank exceeds the number of positive shares, zero-share handling, ties at the boundary, rank 0 compatibility, and that the root setter stores the rank and invalidates EmissionGateBar. Economic logic requires explicit boundary coverage.

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] Add tests for the new economic rank path

This remains untested: the added test changes all explicitly set EmissionBarRank to 0, so none execute this branch even though rank 64 is now the runtime default. Add focused coverage proving selection at rank 1 and an interior rank, fallback when rank exceeds the positive-share count, exclusion of zero shares, and recomputation after sudo_set_emission_bar_rank clears the cached bar. This changes emission allocation at upgrade and requires boundary tests.

sorted
.iter()
.filter(|share| **share > zero)
.nth(usize::from(rank).saturating_sub(1))
.or_else(|| sorted.iter().filter(|share| **share > zero).next_back())
.copied()
.unwrap_or(zero)
} else {
let q = EmissionBarQuantile::<T>::get();
let mut cumulative = zero;
let mut crossing = zero;
for share in sorted {
cumulative = cumulative.saturating_add(share);
crossing = share;
if cumulative >= q {
break;
}
}
}
crossing
};

if theta > zero {
EmissionGateBar::<T>::put(theta);
log::debug!("Emission gate bar updated: theta = {theta:?} (q = {q:?})");
log::debug!("Emission gate bar updated: theta = {theta:?} (rank = {rank:?})");
}
}

Expand Down
13 changes: 13 additions & 0 deletions pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1859,6 +1859,19 @@ pub mod pallet {
pub type EmissionBarQuantile<T: Config> =
StorageValue<_, U64F64, ValueQuery, DefaultEmissionBarQuantile<T>>;

#[pallet::type_value]
/// Default emission bar rank (N). N > 0 pins theta to the Nth-largest
/// demand share, so the eligible set tracks rank N as the distribution
/// shifts instead of drifting with a fixed q. 0 disables rank mode and
/// the bar falls back to the q-mass quantile.
pub fn DefaultEmissionBarRank<T: Config>() -> u16 {
64
}
#[pallet::storage]
/// ITEM --> Emission Bar Rank (N). When non-zero, overrides the quantile.
pub type EmissionBarRank<T: Config> =
StorageValue<_, u16, ValueQuery, DefaultEmissionBarRank<T>>;

#[pallet::type_value]
/// Default emission gate Hill exponent (h). Controls cliff sharpness at the bar.
pub fn DefaultEmissionGateExponent<T: Config>() -> U64F64 {
Expand Down
4 changes: 4 additions & 0 deletions pallets/subtensor/src/tests/coinbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,8 @@ fn test_coinbase_tao_issuance_different_prices() {
// so it should receive twice the TAO emission.
SubnetMovingPrice::<Test>::insert(netuid1, I96F32::from_num(0.1));
SubnetMovingPrice::<Test>::insert(netuid2, I96F32::from_num(0.2));
// Pin to q-mass (quantile) mode; this test asserts quantile gate math.
EmissionBarRank::<Test>::set(0);
// Keep root_proportion ~1 so the injection cap does not bind.
set_full_injection_root_stake();

Expand Down Expand Up @@ -670,6 +672,8 @@ fn test_coinbase_alpha_issuance_different() {
// Price-based shares with prices 1 and 2 (1:2 ratio).
SubnetMovingPrice::<Test>::insert(netuid1, I96F32::from_num(1));
SubnetMovingPrice::<Test>::insert(netuid2, I96F32::from_num(2));
// Pin to q-mass (quantile) mode; this test asserts quantile gate math.
EmissionBarRank::<Test>::set(0);
// Keep root_proportion ~1 so the injection cap does not bind.
set_full_injection_root_stake();
// Run coinbase
Expand Down
6 changes: 6 additions & 0 deletions pallets/subtensor/src/tests/subnet_emissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ fn emission_gate_concentrates_1_to_2_price_split() {
let n1 = add_dynamic_network(&owner_hotkey, &owner_coldkey);
let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey);

// Pin to q-mass (quantile) mode; this test asserts quantile bar selection.
EmissionBarRank::<Test>::set(0);

System::set_block_number(0);
SubnetMovingPrice::<Test>::insert(n1, i96f32(1.0));
SubnetMovingPrice::<Test>::insert(n2, i96f32(2.0));
Expand Down Expand Up @@ -307,6 +310,9 @@ fn emission_gate_bar_update_cadence() {
let n2 = add_dynamic_network(&owner_hotkey, &owner_coldkey);
let n3 = add_dynamic_network(&owner_hotkey, &owner_coldkey);

// Pin to q-mass (quantile) mode; this test asserts quantile bar selection.
EmissionBarRank::<Test>::set(0);

System::set_block_number(0);
// Shares 0.7, 0.2, 0.1. q = 0.61 crosses on the first → theta = 0.7.
SubnetMovingPrice::<Test>::insert(n1, i96f32(7.0));
Expand Down
7 changes: 7 additions & 0 deletions pallets/subtensor/src/utils/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,13 @@ impl<T: Config> Pallet<T> {
EmissionGateBar::<T>::kill();
}

/// Sets the emission bar rank (N). 0 restores q-mass (quantile) mode.
pub fn set_emission_bar_rank(rank: u16) {
EmissionBarRank::<T>::set(rank);
// Force a bar recompute with the new rank on the next block.
EmissionGateBar::<T>::kill();
}

/// Sets the emission gate Hill exponent (h)
pub fn set_emission_gate_exponent(exponent: U64F64) {
EmissionGateExponent::<T>::set(exponent);
Expand Down
2 changes: 1 addition & 1 deletion runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// `spec_version`, and `authoring_version` are the same between Wasm and native.
// This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use
// the compatible custom types.
spec_version: 440,
spec_version: 441,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
Expand Down
Loading