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
27 changes: 27 additions & 0 deletions pallets/admin-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2057,6 +2057,33 @@ pub mod pallet {
Ok(())
}

/// Sets the emission depth bar (D), in rao of SubnetTAO reserve. Pools below the bar
/// have their emission-relevant EMA price capped toward the network median; the cap
/// relaxes as the pool deepens. Zero disables the cap. (Reuses the gate-exponent
/// weight; a dedicated benchmark should be added before mainnet.)
#[pallet::call_index(103)]
#[pallet::weight(<T as Config>::WeightInfo::sudo_set_emission_gate_exponent())]
pub fn sudo_set_emission_depth_bar(origin: OriginFor<T>, bar: u64) -> DispatchResult {
ensure_root(origin)?;
pallet_subtensor::Pallet::<T>::set_emission_depth_bar(bar);
log::debug!("set_emission_depth_bar( {bar:?} ) ");
Ok(())
}

/// Sets the emission depth exponent (k): sharpness of the depth-cap falloff
#[pallet::call_index(104)]
#[pallet::weight(<T as Config>::WeightInfo::sudo_set_emission_gate_exponent())]
pub fn sudo_set_emission_depth_exponent(
origin: OriginFor<T>,
exponent: u16,
) -> DispatchResult {
ensure_root(origin)?;
ensure!((1..=8).contains(&exponent), Error::<T>::InvalidValue);
pallet_subtensor::Pallet::<T>::set_emission_depth_exponent(exponent);
log::debug!("set_emission_depth_exponent( {exponent:?} ) ");
Ok(())
}

/// Sets TAO flow smoothing factor (alpha)
#[pallet::call_index(83)]
#[pallet::weight(<T as Config>::WeightInfo::sudo_set_tao_flow_smoothing_factor())]
Expand Down
105 changes: 86 additions & 19 deletions pallets/subtensor/src/coinbase/subnet_emissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,31 +490,98 @@ impl<T: Config> Pallet<T> {
}

// Implementation of shares that uses subnet EMA prices (SubnetMovingPrice),
// not the active/spot alpha price.
// not the active/spot alpha price. Each subnet's EMA price is first passed through a
// depth-graduated cap (`emission_price_capped`): prices at or below the network median EMA
// price pass through unchanged, but the portion ABOVE the median is credited only in
// proportion to the pool's depth weight w(SubnetTAO). A shallow pool (w -> 0) is therefore
// capped at the median regardless of how far its price is pumped, while a deep pool
// (w -> 1) keeps its full price. This caps the TAO a small pool can earn without touching
// its alpha emission, and leaves large pools ~unchanged.
fn get_shares_price_ema(subnets_to_emit_to: &[NetUid]) -> BTreeMap<NetUid, U64F64> {
// Get sum of alpha moving prices
let total_moving_prices = subnets_to_emit_to
.iter()
.map(|netuid| U64F64::saturating_from_num(Self::get_moving_alpha_price(*netuid)))
.fold(U64F64::saturating_from_num(0.0), |acc, ema| {
acc.saturating_add(ema)
});
log::debug!("total_moving_prices: {total_moving_prices:?}");
// Median EMA price across the emit set = the cap anchor for shallow pools.
let median_ema = Self::get_median_moving_price(subnets_to_emit_to);

// Calculate shares.
subnets_to_emit_to
// Depth-capped price per subnet.
let capped: BTreeMap<NetUid, U64F64> = subnets_to_emit_to
.iter()
.map(|netuid| {
let moving_price =
U64F64::saturating_from_num(Self::get_moving_alpha_price(*netuid));
log::debug!("moving_price_i: {moving_price:?}");
.map(|netuid| (*netuid, Self::emission_price_capped(*netuid, median_ema)))
.collect();

let share = moving_price
.checked_div(total_moving_prices)
.unwrap_or(U64F64::saturating_from_num(0));
let total_capped = capped
.values()
.copied()
.fold(U64F64::saturating_from_num(0.0), |acc, p| {
acc.saturating_add(p)
});
log::debug!("total_capped_moving_prices: {total_capped:?}");

(*netuid, share)
capped
.into_iter()
.map(|(netuid, price)| {
let share = price
.checked_div(total_capped)
.unwrap_or(U64F64::saturating_from_num(0));
(netuid, share)
})
.collect::<BTreeMap<NetUid, U64F64>>()
}

/// Median of the EMA (moving) prices over the emit set — the anchor at which a shallow
/// pool's emission price is capped. Zero if the set is empty.
pub(crate) fn get_median_moving_price(subnets_to_emit_to: &[NetUid]) -> U64F64 {
let mut prices: Vec<U64F64> = subnets_to_emit_to
.iter()
.map(|netuid| Self::get_moving_alpha_price(*netuid))
.collect();
if prices.is_empty() {
return U64F64::saturating_from_num(0);
}
prices.sort();
let mid = prices.len() / 2;
if prices.len() % 2 == 1 {
prices[mid]
} else {
prices[mid.saturating_sub(1)]
.saturating_add(prices[mid])
.safe_div(U64F64::saturating_from_num(2))
}
}

/// A subnet's emission-relevant price after the depth-graduated cap.
/// - Prices at or below `median_ema` pass through unchanged.
/// - The portion above `median_ema` is credited only at fraction `w = emission_depth_weight`,
/// so a shallow pool (w -> 0) is capped at the median and a deep pool (w -> 1) keeps its
/// full price.
pub(crate) fn emission_price_capped(netuid: NetUid, median_ema: U64F64) -> U64F64 {
let ema = Self::get_moving_alpha_price(netuid);
if ema <= median_ema {
return ema;
}
let w = Self::emission_depth_weight(netuid);
let excess = ema.saturating_sub(median_ema);
median_ema.saturating_add(w.saturating_mul(excess))
}

/// Depth weight w(T) in [0, 1] where T = SubnetTAO reserve. Hill ramp
/// `w = T^k / (T^k + D^k)` with bar D = `EmissionDepthBar` (rao) and exponent
/// k = `EmissionDepthExponent`. w -> 0 for a shallow pool (hard cap at the median),
/// w -> 1 for a deep pool (full price).
pub(crate) fn emission_depth_weight(netuid: NetUid) -> U64F64 {
let d_u64 = EmissionDepthBar::<T>::get();
if d_u64 == 0 {
return U64F64::saturating_from_num(1);
}
let t = U64F64::saturating_from_num(SubnetTAO::<T>::get(netuid).to_u64());
let d = U64F64::saturating_from_num(d_u64);
let r = t.safe_div(d);
let k = u32::from(EmissionDepthExponent::<T>::get().clamp(1, 8));
let mut rk = r;
let mut i = 1u32;
while i < k {
rk = rk.saturating_mul(r);
i = i.saturating_add(1);
}
rk.checked_div(rk.saturating_add(U64F64::saturating_from_num(1)))
.unwrap_or(U64F64::saturating_from_num(0))
}
}
27 changes: 27 additions & 0 deletions pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1875,6 +1875,33 @@ pub mod pallet {
/// same EMA-price shares that drive emission. Zero means "not yet computed"
/// and disables the gate.
pub type EmissionGateBar<T: Config> = StorageValue<_, U64F64, ValueQuery>;

#[pallet::type_value]
/// Default emission depth bar (D): the SubnetTAO reserve, in rao, at which a subnet's
/// depth weight `w(T) = T^k/(T^k+D^k)` reaches exactly 0.5 (the half-credit pool size).
/// With the default exponent k = 3 this lands w ~= 0.89 at 1000 TAO and ~= 1.0 at 10k TAO
/// of reserve. Zero disables the depth cap (every subnet keeps its full EMA price).
pub fn DefaultEmissionDepthBar<T: Config>() -> u64 {
// 500 TAO in rao. Half-credit pool size (w = 0.5 at 500 TAO) at the default exponent k = 3.
500_000_000_000
}
#[pallet::storage]
/// ITEM --> Emission depth bar (D), in rao of SubnetTAO reserve. Small pools have their
/// emission-relevant EMA price capped toward the network median; the cap relaxes as the
/// pool deepens past this bar.
pub type EmissionDepthBar<T: Config> =
StorageValue<_, u64, ValueQuery, DefaultEmissionDepthBar<T>>;

#[pallet::type_value]
/// Default emission depth exponent (k) for the depth-weight Hill curve. Higher = sharper
/// falloff of emission below the bar.
pub fn DefaultEmissionDepthExponent<T: Config>() -> u16 {
3
}
#[pallet::storage]
/// ITEM --> Emission depth exponent (k), clamped to 1..=8 when read.
pub type EmissionDepthExponent<T: Config> =
StorageValue<_, u16, ValueQuery, DefaultEmissionDepthExponent<T>>;
#[pallet::type_value]
/// Default value for flow EMA smoothing.
pub fn DefaultFlowEmaSmoothingFactor<T: Config>() -> u64 {
Expand Down
26 changes: 9 additions & 17 deletions pallets/subtensor/src/subnets/subnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,32 +369,24 @@ impl<T: Config> Pallet<T> {
weight.saturating_accrue(db_weight.reads(networks_added_reads.max(1)));
weight.saturating_accrue(db_weight.writes(1));

// Keep the locked TAO in the pool instead of recycling the excess.
// Size the pool alpha reserve from the total TAO reserve at that same price.
let pool_initial_tao: TaoBalance = Self::get_network_min_lock();
// The pool begins with exactly 1 TAO and 1 alpha regardless of the lock paid.
// The remainder of the lock is recycled below, which makes registration a
// real (non-recoverable) cost rather than a self-deposit into the founder's own pool.
let _ = median_subnet_alpha_price;
let one_unit: u64 = 1_000_000_000; // 1 TAO / 1 alpha (9 decimals)
let total_pool_tao: TaoBalance = one_unit.into();
let total_pool_alpha: AlphaBalance = one_unit.into();
weight.saturating_accrue(db_weight.reads(1));

let total_pool_tao: TaoBalance = if actual_tao_lock_amount >= pool_initial_tao {
actual_tao_lock_amount
} else {
pool_initial_tao
};

let total_pool_alpha: AlphaBalance = U64F64::saturating_from_num(total_pool_tao.to_u64())
.safe_div(median_subnet_alpha_price)
.saturating_floor()
.saturating_to_num::<u64>()
.into();

// // With the full lock retained in the reserve, this will normally be zero.
// Everything above the 1-TAO seed is recycled (removed from circulation).
let tao_recycled_for_registration = actual_tao_lock_amount.saturating_sub(total_pool_tao);

// Core pool + ownership
SubnetTAO::<T>::insert(netuid_to_register, total_pool_tao);
SubnetAlphaIn::<T>::insert(netuid_to_register, total_pool_alpha);
SubnetOwner::<T>::insert(netuid_to_register, coldkey.clone());
Self::set_subnet_owner_hotkey(netuid_to_register, hotkey)?;
SubnetLocked::<T>::insert(netuid_to_register, actual_tao_lock_amount);
SubnetLocked::<T>::insert(netuid_to_register, total_pool_tao);
SubnetAlphaOut::<T>::insert(netuid_to_register, AlphaBalance::ZERO);
SubnetVolume::<T>::insert(netuid_to_register, 0u128);
RAORecycledForRegistration::<T>::insert(netuid_to_register, tao_recycled_for_registration);
Expand Down
10 changes: 10 additions & 0 deletions pallets/subtensor/src/utils/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,16 @@ impl<T: Config> Pallet<T> {
EmissionGateExponent::<T>::set(exponent);
}

/// Sets the emission depth bar (D)
pub fn set_emission_depth_bar(bar: u64) {
EmissionDepthBar::<T>::set(bar);
}

/// Sets the emission depth exponent (k)
pub fn set_emission_depth_exponent(exponent: u16) {
EmissionDepthExponent::<T>::set(exponent);
}

/// Sets TAO flow smoothing factor (alpha)
pub fn set_tao_flow_smoothing_factor(smoothing_factor: u64) {
FlowEmaSmoothingFactor::<T>::set(smoothing_factor);
Expand Down