Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
17 changes: 17 additions & 0 deletions pallets/admin-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1963,6 +1963,23 @@ pub mod pallet {
Ok(())
}

/// Enables or disables net TAO flow (protocol cost deduction from emission shares).
/// When enabled, emission shares use net flow = user flow - protocol cost.
/// When disabled, emission shares use gross user flow only (current behavior).
#[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)))]
pub fn sudo_set_net_tao_flow_enabled(
origin: OriginFor<T>,
enabled: bool,
) -> DispatchResult {
ensure_root(origin)?;
pallet_subtensor::Pallet::<T>::set_net_tao_flow_enabled(enabled);
log::debug!("set_net_tao_flow_enabled( {enabled:?} ) ");
Ok(())
}

/// Sets the global maximum number of mechanisms in a subnet
#[pallet::call_index(88)]
#[pallet::weight(Weight::from_parts(15_000_000, 0)
Expand Down
5 changes: 5 additions & 0 deletions pallets/subtensor/src/coinbase/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,11 @@ impl<T: Config> Pallet<T> {
SubnetMovingPrice::<T>::remove(netuid);
SubnetTaoFlow::<T>::remove(netuid);
SubnetEmaTaoFlow::<T>::remove(netuid);
SubnetEmaSlowTaoFlow::<T>::remove(netuid);
SubnetProtocolFlow::<T>::remove(netuid);
SubnetEmaProtocolFlow::<T>::remove(netuid);
SubnetExcessTao::<T>::remove(netuid);
SubnetRootSellTao::<T>::remove(netuid);
SubnetTaoProvided::<T>::remove(netuid);

// --- 13. Token / mechanism / registration toggles.
Expand Down
14 changes: 14 additions & 0 deletions pallets/subtensor/src/coinbase/run_coinbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ impl<T: Config> Pallet<T> {
log::debug!(
"Running coinbase for block {current_block:?} with block emission: {block_emission:?}"
);

// Reset per-block root sell counters from the previous block.
// Root sells (step 8 in block_step) happen after coinbase, so their
// accumulated values are consumed here at the start of the next block.
let _ = SubnetRootSellTao::<T>::clear(u32::MAX, None);

// --- 1. Get all subnets (excluding root).
let subnets: Vec<NetUid> = Self::get_all_subnet_netuids()
.into_iter()
Expand Down Expand Up @@ -97,6 +103,11 @@ impl<T: Config> Pallet<T> {
let bought_alpha: AlphaBalance =
buy_swap_result_ok.amount_paid_out.into();
Self::recycle_subnet_alpha(*netuid_i, bought_alpha);

// Record actual excess TAO that entered pool.
let actual_excess: TaoBalance = buy_swap_result_ok.amount_paid_in;
SubnetExcessTao::<T>::insert(*netuid_i, actual_excess);
Self::record_protocol_inflow(*netuid_i, actual_excess);
}
}
Err(remainder) => {
Expand Down Expand Up @@ -132,6 +143,9 @@ impl<T: Config> Pallet<T> {
TotalStake::<T>::mutate(|total| {
*total = total.saturating_add(injected_tao);
});

// Record emission injection as protocol inflow.
Self::record_protocol_inflow(*netuid_i, injected_tao);
}
Err(remainder) => {
remaining_credit = remainder;
Expand Down
117 changes: 114 additions & 3 deletions pallets/subtensor/src/coinbase/subnet_emissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,45 @@ impl<T: Config> Pallet<T> {
SubnetTaoFlow::<T>::remove(netuid);
}

pub fn record_protocol_inflow(netuid: NetUid, tao: TaoBalance) {
SubnetProtocolFlow::<T>::mutate(netuid, |flow| {
*flow = flow.saturating_add(u64::from(tao) as i64);
});
}

pub fn record_protocol_outflow(netuid: NetUid, tao: TaoBalance) {
SubnetProtocolFlow::<T>::mutate(netuid, |flow| {
*flow = flow.saturating_sub(u64::from(tao) as i64);
});
}

pub fn reset_protocol_flow(netuid: NetUid) {
SubnetProtocolFlow::<T>::remove(netuid);
}

fn get_ema_protocol_flow(netuid: NetUid) -> I64F64 {
let current_block: u64 = Self::get_current_block_as_u64();

let block_flow = I64F64::saturating_from_num(SubnetProtocolFlow::<T>::get(netuid));
let (last_block, last_block_ema) =
SubnetEmaProtocolFlow::<T>::get(netuid).unwrap_or((0, I64F64::saturating_from_num(0)));

if last_block != current_block {
let flow_alpha = I64F64::saturating_from_num(FlowEmaSmoothingFactor::<T>::get())
.safe_div(I64F64::saturating_from_num(i64::MAX));
let one = I64F64::saturating_from_num(1);
let ema_flow = (one.saturating_sub(flow_alpha))
.saturating_mul(last_block_ema)
.saturating_add(flow_alpha.saturating_mul(block_flow));
SubnetEmaProtocolFlow::<T>::insert(netuid, (current_block, ema_flow));

Self::reset_protocol_flow(netuid);
ema_flow
} else {
last_block_ema
}
}

// Update SubnetEmaTaoFlow if needed and return its value for
// the current block
#[allow(dead_code)]
Expand Down Expand Up @@ -174,13 +213,85 @@ impl<T: Config> Pallet<T> {
}
}

/// Compute slow EMA of the raw user flow EMA (second smoothing layer).
/// Stores EMA(raw), not the clamped min(raw, slow).
/// On first call for a subnet, initializes to the current raw EMA to avoid cliff.
fn get_slow_ema_flow(netuid: NetUid, raw_ema: I64F64) -> I64F64 {
let current_block: u64 = Self::get_current_block_as_u64();

let (last_block, last_slow_ema) = SubnetEmaSlowTaoFlow::<T>::get(netuid)
.unwrap_or((current_block, raw_ema));

if last_block != current_block {
let flow_alpha = I64F64::saturating_from_num(FlowEmaSmoothingFactor::<T>::get())
.safe_div(I64F64::saturating_from_num(i64::MAX));
let one = I64F64::saturating_from_num(1);
let slow_ema = (one.saturating_sub(flow_alpha))
.saturating_mul(last_slow_ema)
.saturating_add(flow_alpha.saturating_mul(raw_ema));
SubnetEmaSlowTaoFlow::<T>::insert(netuid, (current_block, slow_ema));
slow_ema
} else {
last_slow_ema
}
}

// Implementation of shares that uses TAO flow
#[allow(dead_code)]
fn get_shares_flow(subnets_to_emit_to: &[NetUid]) -> BTreeMap<NetUid, U64F64> {
// Get raw flows
let ema_flows = subnets_to_emit_to
let net_flow_enabled = NetTaoFlowEnabled::<T>::get();
let zero = I64F64::saturating_from_num(0);

// Collect raw user EMA, slow EMA (for maturity), and protocol EMA.
// matured = min(raw, slow): buys get delayed credit, sells debit immediately.
let subnet_emas: Vec<(NetUid, I64F64, I64F64)> = subnets_to_emit_to
.iter()
.map(|netuid| (*netuid, Self::get_ema_flow(*netuid)))
.map(|netuid| {
let raw_user_ema = Self::get_ema_flow(*netuid);
let slow_user_ema = Self::get_slow_ema_flow(*netuid, raw_user_ema);
let matured_user_ema = raw_user_ema.min(slow_user_ema);
let protocol_ema = Self::get_ema_protocol_flow(*netuid);
(*netuid, matured_user_ema, protocol_ema)
})
.collect();

// When net flow is enabled, normalize protocol EMA so that
// sum(max(proto, 0)) = sum(max(matured_user, 0)). 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.
// Uses matured user EMA in the numerator so α matches the signal basis.
let norm_factor = if net_flow_enabled {
let sum_pos_user: I64F64 = subnet_emas.iter()
.map(|(_, u, _)| (*u).max(zero))
.fold(zero, |a, b| a.saturating_add(b));
let sum_pos_proto: I64F64 = subnet_emas.iter()
.map(|(_, _, p)| (*p).max(zero))
.fold(zero, |a, b| a.saturating_add(b));
let one = I64F64::saturating_from_num(1);
if sum_pos_proto > zero {
sum_pos_user.safe_div(sum_pos_proto).min(one)
} else {
zero
}
} else {
zero
};
log::debug!("Protocol normalization factor: {norm_factor:?}");

let ema_flows: BTreeMap<NetUid, I64F64> = subnet_emas
.into_iter()
.map(|(netuid, matured_user_ema, protocol_ema)| {
// Only scale positive protocol cost by α. Negative protocol
// (root drain > emissions) is a benefit — kept at full value.
let scaled_proto = if protocol_ema > zero {
norm_factor.saturating_mul(protocol_ema)
} else {
protocol_ema
};
let net = matured_user_ema.saturating_sub(scaled_proto);
(netuid, net)
})
.collect();
log::debug!("EMA flows: {ema_flows:?}");

Expand Down
37 changes: 36 additions & 1 deletion pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1344,6 +1344,16 @@ pub mod pallet {
pub type SubnetTaoInEmission<T: Config> =
StorageMap<_, Identity, NetUid, TaoBalance, ValueQuery, DefaultZeroTao<T>>;

/// --- MAP ( netuid ) --> excess_tao | Returns the excess TAO swapped (chain buys) into this subnet on the last block.
#[pallet::storage]
pub type SubnetExcessTao<T: Config> =
StorageMap<_, Identity, NetUid, TaoBalance, ValueQuery, DefaultZeroTao<T>>;

/// --- MAP ( netuid ) --> root_sell_tao | Returns the TAO received from root dividend sells on this subnet on the last block.
#[pallet::storage]
pub type SubnetRootSellTao<T: Config> =
StorageMap<_, Identity, NetUid, TaoBalance, ValueQuery, DefaultZeroTao<T>>;

/// --- MAP ( netuid ) --> alpha_supply_in_pool | Returns the amount of alpha in the pool.
#[pallet::storage]
pub type SubnetAlphaIn<T: Config> =
Expand Down Expand Up @@ -1571,11 +1581,36 @@ pub mod pallet {
pub type SubnetTaoFlow<T: Config> =
StorageMap<_, Identity, NetUid, i64, ValueQuery, DefaultZeroI64<T>>;

/// --- MAP ( netuid ) --> subnet_ema_tao_flow | Returns the EMA of TAO inflow-outflow balance.
/// --- MAP ( netuid ) --> subnet_ema_tao_flow | Returns the EMA of TAO inflow-outflow balance (raw user flow).
#[pallet::storage]
pub type SubnetEmaTaoFlow<T: Config> =
StorageMap<_, Identity, NetUid, (u64, I64F64), OptionQuery>;

/// --- MAP ( netuid ) --> subnet_ema_slow_tao_flow | Slow EMA of raw user flow EMA (second smoothing layer).
/// Used for maturity clamp: matured = min(raw, slow). Stores EMA(raw), NOT min(raw, slow).
#[pallet::storage]
pub type SubnetEmaSlowTaoFlow<T: Config> =
StorageMap<_, Identity, NetUid, (u64, I64F64), OptionQuery>;

/// --- ITEM --> net_tao_flow_enabled | When true, emission shares use net flow (user - protocol). When false, uses gross user flow only.
#[pallet::type_value]
pub fn DefaultNetTaoFlowEnabled<T: Config>() -> bool {
true
}
#[pallet::storage]
pub type NetTaoFlowEnabled<T: Config> =
StorageValue<_, bool, ValueQuery, DefaultNetTaoFlowEnabled<T>>;

/// --- MAP ( netuid ) --> subnet_protocol_flow | Per-block accumulator for protocol cost (emission + chain buys - root sells).
#[pallet::storage]
pub type SubnetProtocolFlow<T: Config> =
StorageMap<_, Identity, NetUid, i64, ValueQuery, DefaultZeroI64<T>>;

/// --- MAP ( netuid ) --> subnet_ema_protocol_flow | EMA of protocol cost flow, same smoothing as SubnetEmaTaoFlow.
#[pallet::storage]
pub type SubnetEmaProtocolFlow<T: Config> =
StorageMap<_, Identity, NetUid, (u64, I64F64), OptionQuery>;

/// Default value for flow cutoff.
#[pallet::type_value]
pub fn DefaultFlowCutoff<T: Config>() -> I64F64 {
Expand Down
7 changes: 7 additions & 0 deletions pallets/subtensor/src/staking/claim_root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@ impl<T: Config> Pallet<T> {
}
};

// Record root sell as protocol outflow (reduces protocol cost).
let root_sell_tao: TaoBalance = owed_tao.amount_paid_out;
SubnetRootSellTao::<T>::mutate(netuid, |total| {
*total = total.saturating_add(root_sell_tao);
});
Self::record_protocol_outflow(netuid, root_sell_tao);

// Transfer unstaked TAO from subnet account to the root subnet account
// and increase root stake.
if let Some(root_subnet_account_id) = Self::get_subnet_account_id(NetUid::ROOT)
Expand Down
5 changes: 5 additions & 0 deletions pallets/subtensor/src/utils/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,11 @@ impl<T: Config> Pallet<T> {
FlowEmaSmoothingFactor::<T>::set(smoothing_factor);
}

/// Enables or disables net TAO flow (protocol cost deduction from emission shares).
pub fn set_net_tao_flow_enabled(enabled: bool) {
NetTaoFlowEnabled::<T>::set(enabled);
}

/// Multiply an integer `value` by a Q32 fixed-point factor.
///
/// Q32 means:
Expand Down
Loading