Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
02e400b
feat: add EffectiveRootProp storage and computation
ppolewicz Jan 31, 2026
d09024c
feat: add EffectiveRootPropEmissionScaling root hyperparameter
ppolewicz Jan 31, 2026
a0e847e
feat: add EmissionTopSubnetProportion root hyperparameter
ppolewicz Jan 31, 2026
48cbd70
feat: add EmissionTopSubnetAbsoluteLimit root hyperparameter
ppolewicz Jan 31, 2026
6f1c024
remove temporary plan.md file
ppolewicz Jan 31, 2026
f3297ec
fix: resolve cargo clippy errors in subnet_emissions
ppolewicz Feb 1, 2026
e1b64c3
fix: change default EmissionTopSubnetProportion to 100% and clean up …
ppolewicz Feb 1, 2026
fced048
refactor: remove emission computation events and fix tests for 100% d…
ppolewicz Feb 1, 2026
215df6a
fix: handle ties at boundary in zero_and_redistribute_bottom_shares
ppolewicz Feb 2, 2026
5bf306d
fix: cap EffectiveRootProp scaling by RootProp to prevent exploitation
ppolewicz Feb 2, 2026
4699232
refactor: address PR review comments for taoflow2
ppolewicz Feb 4, 2026
facff6e
Add wide_scope_dividend test suite for emission distribution
ppolewicz Feb 6, 2026
79e2a47
Merge branch 'wide-scope-dividend-test' into taoflow2
ppolewicz Feb 6, 2026
4afd191
Fix EffectiveRootProp to account for root stake utilization
ppolewicz Feb 6, 2026
1962dcd
Add dividend-efficiency utilization, scaling, and hard cap
ppolewicz Feb 7, 2026
aa60f83
Guard utilization scaling on presence of root dividends
ppolewicz Feb 7, 2026
907d380
Fix conflicting call_index(88) in pallet-admin-utils
ppolewicz Feb 8, 2026
e5fb093
cargo fmt
ppolewicz Feb 8, 2026
9419599
commit Cargo.lock
ppolewicz Feb 8, 2026
e4c0ee2
Add test for root validator abandonment and recovery
ppolewicz Feb 8, 2026
9f5b8f9
Extract get_root_dividend_fraction and apply_utilization_scaling helpers
ppolewicz Feb 8, 2026
0879892
Add utilization analysis Python script
ppolewicz Feb 9, 2026
78ebacc
Remove proportional scaling: binary hard cap at 50% utilization
ppolewicz Feb 9, 2026
4acefda
Address code review: fix safety issues, docs, and add tests
ppolewicz Feb 9, 2026
e524197
Change emission proportion extrinsic to parts-per-million resolution
ppolewicz Feb 9, 2026
4ab2c6d
Fix review findings: storage cleanup, bounds check, and code hygiene
ppolewicz Feb 9, 2026
5723178
commit Cargo.lock
ppolewicz Feb 10, 2026
160db89
commit Cargo.lock
ppolewicz Feb 10, 2026
20ec895
cargo clippy
ppolewicz Feb 10, 2026
5bc5c75
Add 15 comprehensive tests for taoflow2 edge cases and corner coverage
ppolewicz Feb 10, 2026
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
63 changes: 63 additions & 0 deletions pallets/admin-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2279,6 +2279,69 @@ pub mod pallet {
log::trace!("ColdkeySwapReannouncementDelaySet( duration: {duration:?} )");
Ok(())
}

/// Sets EffectiveRootProp emission scaling on/off
#[pallet::call_index(91)]
#[pallet::weight((
Weight::from_parts(7_343_000, 0)
.saturating_add(<T as frame_system::Config>::DbWeight::get().reads(0))
.saturating_add(<T as frame_system::Config>::DbWeight::get().writes(1)),
DispatchClass::Operational,
Pays::Yes
))]
pub fn sudo_set_effective_root_prop_emission_scaling(
origin: OriginFor<T>,
enabled: bool,
) -> DispatchResult {
ensure_root(origin)?;
pallet_subtensor::Pallet::<T>::set_effective_root_prop_emission_scaling(enabled);
Ok(())
}

/// Sets the proportion of top subnets that receive emission.
/// `proportion_ppm` is in parts-per-million: 1_000_000 = 100%, 500_000 = 50%, etc.
/// Must be in range (0, 1_000_000].
#[pallet::call_index(89)]
#[pallet::weight((
Weight::from_parts(7_343_000, 0)
.saturating_add(<T as frame_system::Config>::DbWeight::get().reads(0))
.saturating_add(<T as frame_system::Config>::DbWeight::get().writes(1)),
DispatchClass::Operational,
Pays::Yes
))]
pub fn sudo_set_emission_top_subnet_proportion(
origin: OriginFor<T>,
proportion_ppm: u64,
) -> DispatchResult {
ensure_root(origin)?;
ensure!(
proportion_ppm > 0 && proportion_ppm <= 1_000_000,
Error::<T>::InvalidValue
);
let prop = U64F64::saturating_from_num(proportion_ppm)
.saturating_div(U64F64::saturating_from_num(1_000_000));
pallet_subtensor::Pallet::<T>::set_emission_top_subnet_proportion(prop);
Ok(())
}

/// Sets the absolute-limit cutoff for subnets receiving emission (None = no limit).
/// Ties at the cutoff are included, so the number of nonzero subnets may exceed N.
#[pallet::call_index(90)]
#[pallet::weight((
Weight::from_parts(7_343_000, 0)
.saturating_add(<T as frame_system::Config>::DbWeight::get().reads(0))
.saturating_add(<T as frame_system::Config>::DbWeight::get().writes(1)),
DispatchClass::Operational,
Pays::Yes
))]
pub fn sudo_set_emission_top_subnet_absolute_limit(
origin: OriginFor<T>,
limit: Option<u16>,
) -> DispatchResult {
ensure_root(origin)?;
pallet_subtensor::Pallet::<T>::set_emission_top_subnet_absolute_limit(limit);
Ok(())
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions pallets/subtensor/src/coinbase/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,12 +363,18 @@ impl<T: Config> Pallet<T> {
StakeWeight::<T>::remove(netuid);
LoadedEmission::<T>::remove(netuid);

// --- 18b. Root prop / utilization.
EffectiveRootProp::<T>::remove(netuid);
RootProp::<T>::remove(netuid);
RootClaimableThreshold::<T>::remove(netuid);

// --- 19. DMAPs where netuid is the FIRST key: clear by prefix.
let _ = BlockAtRegistration::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = Axons::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = NeuronCertificates::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = Prometheus::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = AlphaDividendsPerSubnet::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = RootAlphaDividendsPerSubnet::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = PendingChildKeys::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = AssociatedEvmAddress::<T>::clear_prefix(netuid, u32::MAX, None);

Expand Down
195 changes: 194 additions & 1 deletion pallets/subtensor/src/coinbase/run_coinbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,183 @@ impl<T: Config> Pallet<T> {
}
}

/// Computes and stores the EffectiveRootProp for a subnet. Returns the utilization value.
///
/// EffectiveRootProp = raw_root_prop * utilization
///
/// Where:
/// raw_root_prop = sum(root_alpha_dividends) / (sum(alpha_dividends) + sum(root_alpha_dividends))
/// utilization = sum(root_stake_i * efficiency_i) / total_root_stake
/// efficiency_i = min(actual_share_i / expected_share_i, 1.0)
/// expected_share_i = root_stake_i / total_root_stake
/// actual_share_i = root_alpha_dividends[i] / sum(root_alpha_dividends)
///
/// Only root stake of validators with UIDs on this subnet is counted.
/// TotalIssuance, unstaked TAO, and root stake on other subnets are irrelevant.
pub fn compute_and_store_effective_root_prop(
netuid: NetUid,
alpha_dividends: &BTreeMap<T::AccountId, U96F32>,
root_alpha_dividends: &BTreeMap<T::AccountId, U96F32>,
) -> U96F32 {
let zero = U96F32::saturating_from_num(0);
let one = U96F32::saturating_from_num(1);

let total_alpha_divs: U96F32 = alpha_dividends
.values()
.fold(zero, |acc, v| acc.saturating_add(*v));

let total_root_divs: U96F32 = root_alpha_dividends
.values()
.fold(zero, |acc, v| acc.saturating_add(*v));

let total = total_alpha_divs.saturating_add(total_root_divs);

let raw_root_prop = if total > zero {
total_root_divs.checked_div(total).unwrap_or(zero)
} else {
zero
};

// Compute dividend-efficiency-based utilization.
// For each root-staked validator registered on this subnet:
// expected_share = root_stake_i / total_root_stake
// actual_share = root_dividends_i / total_root_divs
// efficiency = min(actual_share / expected_share, 1.0)
// utilization = sum(root_stake_i * efficiency_i) / total_root_stake
let n = SubnetworkN::<T>::get(netuid);
let mut total_root_stake = zero;

// First pass: compute total root stake on this subnet
let mut hotkey_root_stakes: Vec<(T::AccountId, U96F32)> = Vec::new();
for uid in 0..n {
if let Ok(hotkey) = Keys::<T>::try_get(netuid, uid) {
let root_stake = Self::get_stake_for_hotkey_on_subnet(&hotkey, NetUid::ROOT);
let rs = U96F32::saturating_from_num(root_stake.to_u64());
total_root_stake = total_root_stake.saturating_add(rs);
if rs > zero {
hotkey_root_stakes.push((hotkey, rs));
}
}
}

let utilization = if total_root_stake > zero && total_root_divs > zero {
// Second pass: compute weighted efficiency
let mut weighted_efficiency_sum = zero;
for (hotkey, rs) in &hotkey_root_stakes {
let expected_share = rs.checked_div(total_root_stake).unwrap_or(zero);
let actual_div = root_alpha_dividends.get(hotkey).copied().unwrap_or(zero);
let actual_share = actual_div.checked_div(total_root_divs).unwrap_or(zero);
let efficiency = if expected_share > zero {
let raw_eff = actual_share.checked_div(expected_share).unwrap_or(zero);
raw_eff.min(one)
} else {
zero
};
weighted_efficiency_sum =
weighted_efficiency_sum.saturating_add(rs.saturating_mul(efficiency));
}
weighted_efficiency_sum
.checked_div(total_root_stake)
.unwrap_or(zero)
} else if total_root_stake > zero {
// No root dividends at all → utilization = 0
zero
} else {
zero
};

let effective_root_prop = raw_root_prop.saturating_mul(utilization);

log::debug!(
"EffectiveRootProp for netuid {netuid:?}: {effective_root_prop:?} (raw: {raw_root_prop:?}, utilization: {utilization:?}, total_root_stake: {total_root_stake:?})"
);

EffectiveRootProp::<T>::insert(netuid, effective_root_prop);
utilization
}

/// Computes the fraction of a hotkey's dividends attributable to root stake.
///
/// root_fraction = (root_stake * tao_weight) / (alpha_stake + root_stake * tao_weight)
///
/// Returns 0 if the hotkey has no root stake or the total is zero.
pub fn get_root_dividend_fraction(
hotkey: &T::AccountId,
netuid: NetUid,
tao_weight: U96F32,
) -> U96F32 {
let zero = U96F32::saturating_from_num(0);
let root_stake = Self::get_stake_for_hotkey_on_subnet(hotkey, NetUid::ROOT);
let root_stake_f = asfloat!(root_stake.to_u64());
if root_stake_f <= zero {
return zero;
}
let root_alpha_weighted = root_stake_f.saturating_mul(tao_weight);
let alpha_stake = Self::get_stake_for_hotkey_on_subnet(hotkey, netuid);
let alpha_stake_f = asfloat!(alpha_stake.to_u64());
let total_stake = alpha_stake_f.saturating_add(root_alpha_weighted);
if total_stake <= zero {
return zero;
}
root_alpha_weighted.checked_div(total_stake).unwrap_or(zero)
}

/// Applies utilization-based hard cap to root dividend maps.
///
/// - utilization >= 0.5: no scaling, returns 0 recycled
/// - utilization < 0.5: hard cap applied; zeroes all root dividends, recycles everything,
/// and sets EffectiveRootProp to 0
///
/// Also adjusts the root-staked portion of alpha_dividends accordingly.
/// Returns the total amount recycled.
pub fn apply_utilization_scaling(
netuid: NetUid,
utilization: U96F32,
alpha_dividends: &mut BTreeMap<T::AccountId, U96F32>,
root_alpha_dividends: &mut BTreeMap<T::AccountId, U96F32>,
tao_weight: U96F32,
) -> U96F32 {
let half = U96F32::saturating_from_num(0.5);
let zero = U96F32::saturating_from_num(0);

let has_root_dividends =
!root_alpha_dividends.is_empty() && root_alpha_dividends.values().any(|v| *v > zero);

if !has_root_dividends || utilization >= half {
return zero;
}

let mut total_recycled = zero;

// Hard cap: utilization < 0.5 → recycle ALL root alpha dividends
let total_root: U96F32 = root_alpha_dividends
.values()
.fold(zero, |acc, v| acc.saturating_add(*v));
Self::recycle_subnet_alpha(netuid, AlphaCurrency::from(tou64!(total_root)));
total_recycled = total_recycled.saturating_add(total_root);
root_alpha_dividends.clear();

// Zero root-staked portion of alpha_dividends
for (hotkey, alpha_div) in alpha_dividends.iter_mut() {
let root_fraction = Self::get_root_dividend_fraction(hotkey, netuid, tao_weight);
if root_fraction > zero {
let recycle_amount = (*alpha_div).saturating_mul(root_fraction);
*alpha_div = (*alpha_div).saturating_sub(recycle_amount);
Self::recycle_subnet_alpha(netuid, AlphaCurrency::from(tou64!(recycle_amount)));
total_recycled = total_recycled.saturating_add(recycle_amount);
}
}

// Overwrite EffectiveRootProp to 0
EffectiveRootProp::<T>::insert(netuid, U96F32::saturating_from_num(0));

log::debug!(
"Hard cap triggered for netuid {netuid:?}: utilization {utilization:?} < 0.5, all root dividends recycled"
);

total_recycled
}

pub fn get_stake_map(
netuid: NetUid,
hotkeys: Vec<&T::AccountId>,
Expand Down Expand Up @@ -724,7 +901,7 @@ impl<T: Config> Pallet<T> {
let root_alpha = pending_root_alpha;
let owner_cut = pending_owner_cut;

let (incentives, (alpha_dividends, root_alpha_dividends)) =
let (incentives, (mut alpha_dividends, mut root_alpha_dividends)) =
Self::calculate_dividend_and_incentive_distribution(
netuid,
root_alpha,
Expand All @@ -733,6 +910,22 @@ impl<T: Config> Pallet<T> {
tao_weight,
);

// Compute and store EffectiveRootProp, getting back utilization for scaling.
let utilization = Self::compute_and_store_effective_root_prop(
netuid,
&alpha_dividends,
&root_alpha_dividends,
);

// Apply utilization-based scaling or hard cap to root dividends.
Self::apply_utilization_scaling(
netuid,
utilization,
&mut alpha_dividends,
&mut root_alpha_dividends,
tao_weight,
);

Self::distribute_dividends_and_incentives(
netuid,
owner_cut,
Expand Down
Loading
Loading