From 923fbcdc8c2b97bf6399b550f85b4608b7c6e68f Mon Sep 17 00:00:00 2001 From: igoraxz Date: Tue, 19 May 2026 18:28:11 +0100 Subject: [PATCH 1/2] feat: normalize protocol cost in net flow EMA Normalizes protocol EMA so that its positive total matches user EMA positive total before computing net flow. This prevents subsidy concentration: as emissions concentrate on fewer subnets their protocol EMA grows, but the normalization factor shrinks to compensate. norm_factor = min(1, sum(max(user_ema_i, 0)) / sum(max(proto_ema_i, 0))) net_flow_i = user_ema_i - norm_factor * protocol_ema_i norm_factor is computed each block from current EMA state. No tunable parameters. Guarantee: non-negative aggregate net signal over gross-positive subnets. Individual subnets can still be filtered when their protocol cost exceeds their user demand. Simulation result: 70 -> 46 subnets (vs 10 without normalization). Also fixes #2667: protocol EMA is now updated unconditionally for all subnets regardless of NetTaoFlowEnabled. The flag only controls whether the normalized deduction is applied. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/coinbase/subnet_emissions.rs | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/coinbase/subnet_emissions.rs b/pallets/subtensor/src/coinbase/subnet_emissions.rs index 400a14a5c8..ac7c42ecec 100644 --- a/pallets/subtensor/src/coinbase/subnet_emissions.rs +++ b/pallets/subtensor/src/coinbase/subnet_emissions.rs @@ -243,19 +243,60 @@ impl Pallet { #[allow(dead_code)] fn get_shares_flow(subnets_to_emit_to: &[NetUid]) -> BTreeMap { let net_flow_enabled = NetTaoFlowEnabled::::get(); + let zero = I64F64::saturating_from_num(0); - // Always update the protocol EMA (keeps it warm for when toggled on) - let ema_flows: BTreeMap = subnets_to_emit_to + // Always update both EMAs (keeps protocol EMA warm for when toggled on). + // Fixes #2667: protocol EMA accumulator was only drained when enabled, + // causing a shock on toggle. + let subnet_emas: Vec<(NetUid, I64F64, I64F64)> = subnets_to_emit_to .iter() .map(|netuid| { let user_ema = Self::get_ema_flow(*netuid); let protocol_ema = Self::update_ema_protocol_flow(*netuid); + (*netuid, user_ema, protocol_ema) + }) + .collect(); + + // When net flow is enabled, normalize protocol EMA so that its + // positive total matches the user EMA positive total. This prevents + // subsidy concentration: as emissions concentrate on fewer subnets, + // their protocol EMA grows, but the normalization factor shrinks to + // compensate, keeping the deduction proportional to user demand. + let norm_factor = if net_flow_enabled { + let (user_positive_ema_sum, protocol_positive_ema_sum) = subnet_emas.iter().fold( + (zero, zero), + |(su, sp), (_, u, p)| { + (su.saturating_add((*u).max(zero)), + sp.saturating_add((*p).max(zero))) + }, + ); + let one = I64F64::saturating_from_num(1); + if protocol_positive_ema_sum > zero { + user_positive_ema_sum.safe_div(protocol_positive_ema_sum).min(one) + } else { + zero + } + } else { + zero + }; + log::debug!("Protocol normalization factor: {norm_factor:?}"); + + let ema_flows: BTreeMap = subnet_emas + .into_iter() + .map(|(netuid, user_ema, protocol_ema)| { let net = if net_flow_enabled { - user_ema.saturating_sub(protocol_ema) + // Only scale positive protocol cost by norm_factor. Negative + // protocol cost (root drain > emissions) is a benefit, kept as-is. + let scaled_protocol = if protocol_ema > zero { + norm_factor.saturating_mul(protocol_ema) + } else { + protocol_ema + }; + user_ema.saturating_sub(scaled_protocol) } else { user_ema }; - (*netuid, net) + (netuid, net) }) .collect(); log::debug!("EMA flows (net_flow_enabled={net_flow_enabled}): {ema_flows:?}"); From f1c6ac89ff56adfa56b6e53ca0caa3a674ea49e6 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 20 May 2026 07:54:24 -0700 Subject: [PATCH 2/2] cargo fmt --- .../src/coinbase/subnet_emissions.rs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/pallets/subtensor/src/coinbase/subnet_emissions.rs b/pallets/subtensor/src/coinbase/subnet_emissions.rs index ac7c42ecec..63dbf36e5a 100644 --- a/pallets/subtensor/src/coinbase/subnet_emissions.rs +++ b/pallets/subtensor/src/coinbase/subnet_emissions.rs @@ -263,16 +263,20 @@ impl Pallet { // their protocol EMA grows, but the normalization factor shrinks to // compensate, keeping the deduction proportional to user demand. let norm_factor = if net_flow_enabled { - let (user_positive_ema_sum, protocol_positive_ema_sum) = subnet_emas.iter().fold( - (zero, zero), - |(su, sp), (_, u, p)| { - (su.saturating_add((*u).max(zero)), - sp.saturating_add((*p).max(zero))) - }, - ); + let (user_positive_ema_sum, protocol_positive_ema_sum) = + subnet_emas + .iter() + .fold((zero, zero), |(su, sp), (_, u, p)| { + ( + su.saturating_add((*u).max(zero)), + sp.saturating_add((*p).max(zero)), + ) + }); let one = I64F64::saturating_from_num(1); if protocol_positive_ema_sum > zero { - user_positive_ema_sum.safe_div(protocol_positive_ema_sum).min(one) + user_positive_ema_sum + .safe_div(protocol_positive_ema_sum) + .min(one) } else { zero }