Skip to content
Merged
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
69 changes: 68 additions & 1 deletion pallets/admin-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub mod pallet {
utils::rate_limiting::{Hyperparameter, TransactionType},
};
use sp_runtime::BoundedVec;
use substrate_fixed::types::I96F32;
use substrate_fixed::types::{I64F64, I96F32, U64F64};
use subtensor_runtime_common::{MechId, NetUid, TaoCurrency};

/// The main data structure of the module.
Expand Down Expand Up @@ -115,6 +115,8 @@ pub mod pallet {
MaxAllowedUidsLessThanMinAllowedUids,
/// The maximum allowed UIDs must be less than the default maximum allowed UIDs.
MaxAllowedUidsGreaterThanDefaultMaxAllowedUids,
/// Bad parameter value
InvalidValue,
}
/// Enum for specifying the type of precompile operation.
#[derive(
Expand Down Expand Up @@ -2102,6 +2104,71 @@ pub mod pallet {
);
Ok(())
}

/// Sets TAO flow cutoff value (A)
#[pallet::call_index(81)]
#[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_tao_flow_cutoff(
origin: OriginFor<T>,
flow_cutoff: I64F64,
) -> DispatchResult {
ensure_root(origin)?;
pallet_subtensor::Pallet::<T>::set_tao_flow_cutoff(flow_cutoff);
log::debug!("set_tao_flow_cutoff( {flow_cutoff:?} ) ");
Ok(())
}

/// Sets TAO flow normalization exponent (p)
#[pallet::call_index(82)]
#[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_tao_flow_normalization_exponent(
origin: OriginFor<T>,
exponent: U64F64,
) -> DispatchResult {
ensure_root(origin)?;

let one = U64F64::saturating_from_num(1);
let two = U64F64::saturating_from_num(2);
ensure!(
(one <= exponent) && (exponent <= two),
Error::<T>::InvalidValue
);

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

/// Sets TAO flow smoothing factor (alpha)
#[pallet::call_index(83)]
#[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_tao_flow_smoothing_factor(
origin: OriginFor<T>,
smoothing_factor: u64,
) -> DispatchResult {
ensure_root(origin)?;
pallet_subtensor::Pallet::<T>::set_tao_flow_smoothing_factor(smoothing_factor);
log::debug!("set_tao_flow_smoothing_factor( {smoothing_factor:?} ) ");
Ok(())
}
}
}

Expand Down
270 changes: 259 additions & 11 deletions pallets/subtensor/src/coinbase/subnet_emissions.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use super::*;
use crate::alloc::borrow::ToOwned;
use alloc::collections::BTreeMap;
use substrate_fixed::types::U96F32;
use safe_math::FixedExt;
use substrate_fixed::transcendental::{exp, ln};
use substrate_fixed::types::{I32F32, I64F64, U64F64, U96F32};

impl<T: Config> Pallet<T> {
pub fn get_subnet_block_emissions(
Expand All @@ -17,29 +19,275 @@ impl<T: Config> Pallet<T> {
.collect();
log::debug!("Subnets to emit to: {subnets_to_emit_to:?}");

// Get subnet TAO emissions.
let shares = Self::get_shares(&subnets_to_emit_to);
log::debug!("Subnet emission shares = {shares:?}");

shares
.into_iter()
.map(|(netuid, share)| {
let emission = U64F64::saturating_from_num(block_emission).saturating_mul(share);
(netuid, U96F32::saturating_from_num(emission))
})
.collect::<BTreeMap<NetUid, U96F32>>()
}

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

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

pub fn reset_tao_outflow(netuid: NetUid) {
SubnetTaoFlow::<T>::remove(netuid);
}

// Update SubnetEmaTaoFlow if needed and return its value for
// the current block
fn get_ema_flow(netuid: NetUid) -> I64F64 {
let current_block: u64 = Self::get_current_block_as_u64();

// Calculate net ema flow for the next block
let block_flow = I64F64::saturating_from_num(SubnetTaoFlow::<T>::get(netuid));
if let Some((last_block, last_block_ema)) = SubnetEmaTaoFlow::<T>::get(netuid) {
// EMA flow already initialized
if last_block != current_block {
let flow_alpha = I64F64::saturating_from_num(FlowEmaSmoothingFactor::<T>::get())
.safe_div(I64F64::saturating_from_num(u16::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));
SubnetEmaTaoFlow::<T>::insert(netuid, (current_block, ema_flow));

// Drop the accumulated flow in the last block
Self::reset_tao_outflow(netuid);
ema_flow
} else {
last_block_ema
}
} else {
// Initialize EMA flow, set S(current_block) = 0
let ema_flow = I64F64::saturating_from_num(0);
SubnetEmaTaoFlow::<T>::insert(netuid, (current_block, ema_flow));
ema_flow
}
}

// Either the minimal EMA flow L = min{Si}, or an artificial
// cut off at some higher value A (TaoFlowCutoff)
// L = max {A, min{min{S[i], 0}}}
fn get_lower_limit(ema_flows: &BTreeMap<NetUid, I64F64>) -> I64F64 {
let zero = I64F64::saturating_from_num(0);
let min_flow = ema_flows
.values()
.map(|flow| flow.min(&zero))
.min()
.unwrap_or(&zero);
let flow_cutoff = TaoFlowCutoff::<T>::get();
flow_cutoff.max(*min_flow)
}

// Estimate the upper value of pow with hardcoded p = 2
fn pow_estimate(val: U64F64) -> U64F64 {
val.saturating_mul(val)
}

fn safe_pow(val: U64F64, p: U64F64) -> U64F64 {
// If val is too low so that ln(val) doesn't fit I32F32::MIN,
// return 0 from the function
let zero = U64F64::saturating_from_num(0);
let i32f32_max = I32F32::saturating_from_num(i32::MAX);
if let Ok(val_ln) = ln(I32F32::saturating_from_num(val)) {
// If exp doesn't fit, do the best we can - max out on I32F32::MAX
U64F64::saturating_from_num(I32F32::saturating_from_num(
exp(I32F32::saturating_from_num(p).saturating_mul(val_ln)).unwrap_or(i32f32_max),
))
} else {
zero
}
}

fn inplace_scale(offset_flows: &mut BTreeMap<NetUid, U64F64>) {
let zero = U64F64::saturating_from_num(0);
let flow_max = offset_flows.values().copied().max().unwrap_or(zero);

// Calculate scale factor so that max becomes 1.0
let flow_factor = U64F64::saturating_from_num(1).safe_div(flow_max);

// Upscale/downscale in-place
for flow in offset_flows.values_mut() {
*flow = flow_factor.saturating_mul(*flow);
}
}

pub(crate) fn inplace_pow_normalize(offset_flows: &mut BTreeMap<NetUid, U64F64>, p: U64F64) {
// Scale offset flows so that that are no overflows and underflows when we use safe_pow:
// flow_factor * subnet_count * (flow_max ^ p) <= I32F32::MAX
let zero = U64F64::saturating_from_num(0);
let subnet_count = offset_flows.len();

// Pre-scale to max 1.0
Self::inplace_scale(offset_flows);

// Scale to maximize precision
let flow_max = offset_flows.values().copied().max().unwrap_or(zero);
log::debug!("Offset flow max: {flow_max:?}");
let flow_max_pow_est = Self::pow_estimate(flow_max);
log::debug!("flow_max_pow_est: {flow_max_pow_est:?}");

let max_times_count =
U64F64::saturating_from_num(subnet_count).saturating_mul(flow_max_pow_est);
let i32f32_max = U64F64::saturating_from_num(i32::MAX);
let precision_min = i32f32_max.safe_div(U64F64::saturating_from_num(u64::MAX));

// If max_times_count < precision_min, all flow values are too low to fit I32F32.
if max_times_count >= precision_min {
let epsilon =
U64F64::saturating_from_num(1).safe_div(U64F64::saturating_from_num(1_000));
let flow_factor = i32f32_max
.safe_div(max_times_count)
.checked_sqrt(epsilon)
.unwrap_or(zero);

// Calculate sum
let sum = offset_flows
.clone()
.into_values()
.map(|flow| flow_factor.saturating_mul(flow))
.map(|scaled_flow| Self::safe_pow(scaled_flow, p))
.sum();
log::debug!("Scaled offset flow sum: {sum:?}");

// Normalize in-place
for flow in offset_flows.values_mut() {
let scaled_flow = flow_factor.saturating_mul(*flow);
*flow = Self::safe_pow(scaled_flow, p).safe_div(sum);
}
}
}

// Implementation of shares that uses TAO flow
fn get_shares_flow(subnets_to_emit_to: &[NetUid]) -> BTreeMap<NetUid, U64F64> {
// Get raw flows
let ema_flows = subnets_to_emit_to
.iter()
.map(|netuid| (*netuid, Self::get_ema_flow(*netuid)))
.collect();
log::debug!("EMA flows: {ema_flows:?}");

// Clip the EMA flow with lower limit L
// z[i] = max{S[i] − L, 0}
let lower_limit = Self::get_lower_limit(&ema_flows);
log::debug!("Lower flow limit: {lower_limit:?}");
let mut offset_flows = ema_flows
.iter()
.map(|(netuid, flow)| {
(
*netuid,
if *flow > lower_limit {
U64F64::saturating_from_num(flow.saturating_sub(lower_limit))
} else {
U64F64::saturating_from_num(0)
},
)
})
.collect::<BTreeMap<NetUid, U64F64>>();

// Normalize the set {z[i]}, using an exponent parameter (p ≥ 1)
let p = FlowNormExponent::<T>::get();
Self::inplace_pow_normalize(&mut offset_flows, p);
offset_flows
}

// DEPRECATED: Implementation of shares that uses EMA prices will be gradually deprecated
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| Self::get_moving_alpha_price(*netuid))
.fold(U96F32::saturating_from_num(0.0), |acc, ema| {
.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:?}");

// Get subnet TAO emissions.
// Calculate shares.
subnets_to_emit_to
.into_iter()
.iter()
.map(|netuid| {
let moving_price = Self::get_moving_alpha_price(netuid);
let moving_price =
U64F64::saturating_from_num(Self::get_moving_alpha_price(*netuid));
log::debug!("moving_price_i: {moving_price:?}");

let share = block_emission
.saturating_mul(moving_price)
let share = moving_price
.checked_div(total_moving_prices)
.unwrap_or(U96F32::from_num(0));
.unwrap_or(U64F64::saturating_from_num(0));

(netuid, share)
(*netuid, share)
})
.collect::<BTreeMap<NetUid, U96F32>>()
.collect::<BTreeMap<NetUid, U64F64>>()
}

// Combines ema price method and tao flow method linearly over FlowHalfLife blocks
pub(crate) fn get_shares(subnets_to_emit_to: &[NetUid]) -> BTreeMap<NetUid, U64F64> {
let current_block: u64 = Self::get_current_block_as_u64();

// Weight of tao flow method
let period = FlowHalfLife::<T>::get();
let one = U64F64::saturating_from_num(1);
let zero = U64F64::saturating_from_num(0);
let tao_flow_weight = if let Some(start_block) = FlowFirstBlock::<T>::get() {
if (current_block > start_block) && (current_block < start_block.saturating_add(period))
{
// Combination period in progress
let start_fixed = U64F64::saturating_from_num(start_block);
let current_fixed = U64F64::saturating_from_num(current_block);
let period_fixed = U64F64::saturating_from_num(period);
current_fixed
.saturating_sub(start_fixed)
.safe_div(period_fixed)
} else if current_block >= start_block.saturating_add(period) {
// Over combination period
one
} else {
// Not yet in combination period
zero
}
} else {
zero
};

// Get shares for each method as needed
let shares_flow = if tao_flow_weight > zero {
Self::get_shares_flow(subnets_to_emit_to)
} else {
BTreeMap::new()
};

let shares_prices = if tao_flow_weight < one {
Self::get_shares_price_ema(subnets_to_emit_to)
} else {
BTreeMap::new()
};

// Combine
let mut shares_combined = BTreeMap::new();
for netuid in subnets_to_emit_to.iter() {
let share_flow = shares_flow.get(netuid).unwrap_or(&zero);
let share_prices = shares_prices.get(netuid).unwrap_or(&zero);
shares_combined.insert(
*netuid,
share_flow.saturating_mul(tao_flow_weight).saturating_add(
share_prices.saturating_mul(one.saturating_sub(tao_flow_weight)),
),
);
}
shares_combined
}
}
Loading
Loading