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
102 changes: 102 additions & 0 deletions pallets/subtensor/src/coinbase/subnet_emissions.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use super::*;
use alloc::collections::BTreeMap;
use frame_support::dispatch::DispatchResult;
use safe_math::FixedExt;
use substrate_fixed::transcendental::{exp, ln};
use substrate_fixed::types::{I32F32, I64F64, U64F64, U96F32};
use subtensor_runtime_common::TaoCurrency;

impl<T: Config> Pallet<T> {
pub fn get_subnets_to_emit_to(subnets: &[NetUid]) -> Vec<NetUid> {
Expand Down Expand Up @@ -246,4 +248,104 @@ impl<T: Config> Pallet<T> {
})
.collect::<BTreeMap<NetUid, U64F64>>()
}

/// Calculates the cost to reset a subnet's EMA to zero.
///
/// The cost formula is: |EMA| × (1/α), capped at MaxEmaResetCost.
/// Where α is the FlowEmaSmoothingFactor normalized by i64::MAX.
///
/// Returns the cost in RAO (TaoCurrency), or None if EMA is not negative.
pub fn get_ema_reset_cost(netuid: NetUid) -> Option<TaoCurrency> {
// Get the current EMA value
let (_, ema) = SubnetEmaTaoFlow::<T>::get(netuid)?;

// Only allow reset if EMA is negative
if ema >= I64F64::saturating_from_num(0) {
return None;
}

// Get the absolute value of EMA
let abs_ema = ema.saturating_abs();

// Get the smoothing factor (alpha) and normalize it
// FlowEmaSmoothingFactor is stored as u64 normalized by i64::MAX (2^63)
let alpha_normalized = FlowEmaSmoothingFactor::<T>::get();

// Cost = |EMA| × (1/α) = |EMA| × (i64::MAX / alpha_normalized)
// This can overflow, so we need to be careful with the calculation
let i64_max = I64F64::saturating_from_num(i64::MAX);
let alpha = I64F64::saturating_from_num(alpha_normalized).safe_div(i64_max);

// Calculate cost = |EMA| / alpha
let cost_raw = abs_ema.safe_div(alpha);

// Convert to u64 (RAO)
let cost_rao = cost_raw
.checked_to_num::<u64>()
.unwrap_or(u64::MAX);

// Cap at MaxEmaResetCost
let max_cost = MaxEmaResetCost::<T>::get();
let cost = TaoCurrency::from(cost_rao).min(max_cost);

Some(cost)
}

/// Resets the subnet EMA to zero by burning TAO.
///
/// This function allows subnet owners to reset negative EMA values that
/// prevent their subnet from receiving emissions.
pub fn do_reset_subnet_ema(origin: T::RuntimeOrigin, netuid: NetUid) -> DispatchResult {
// Ensure the subnet exists
ensure!(Self::if_subnet_exist(netuid), Error::<T>::SubnetNotExists);

// Ensure the caller is the subnet owner
let who = Self::ensure_subnet_owner(origin, netuid)?;

// Get the current EMA value - check if initialized
let (_, previous_ema) = SubnetEmaTaoFlow::<T>::get(netuid)
.ok_or(Error::<T>::EmaNotInitialized)?;

// Ensure EMA is negative
ensure!(
previous_ema < I64F64::saturating_from_num(0),
Error::<T>::SubnetEmaNotNegative
);

// Get the reset cost
let cost = Self::get_ema_reset_cost(netuid)
.ok_or(Error::<T>::SubnetEmaNotNegative)?;

// Ensure the owner has enough balance
ensure!(
Self::can_remove_balance_from_coldkey_account(&who, cost.into()),
Error::<T>::NotEnoughBalanceToPayEmaResetCost
);

// Remove the balance from the owner's account
let actual_cost = Self::remove_balance_from_coldkey_account(&who, cost.into())?;

// Burn the TAO (reduce total issuance)
Self::recycle_tao(actual_cost);

// Reset the EMA to zero
let current_block = Self::get_current_block_as_u64();
SubnetEmaTaoFlow::<T>::insert(netuid, (current_block, I64F64::saturating_from_num(0)));

// Also reset the accumulated flow
Self::reset_tao_outflow(netuid);

// Convert previous_ema to i128 for the event (I64F64 is 128 bits total)
let previous_ema_bits = previous_ema.to_bits();

// Emit the event
Self::deposit_event(Event::SubnetEmaReset {
netuid,
who,
cost: actual_cost,
previous_ema: previous_ema_bits,
});

Ok(())
}
}
10 changes: 10 additions & 0 deletions pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1493,6 +1493,16 @@ pub mod pallet {
pub type FlowEmaSmoothingFactor<T: Config> =
StorageValue<_, u64, ValueQuery, DefaultFlowEmaSmoothingFactor<T>>;

#[pallet::type_value]
/// Default maximum cost for resetting subnet EMA (100 TAO in RAO).
pub fn DefaultMaxEmaResetCost<T: Config>() -> TaoCurrency {
TaoCurrency::from(100_000_000_000u64) // 100 TAO
}
#[pallet::storage]
/// --- ITEM --> Maximum cost for resetting subnet EMA
pub type MaxEmaResetCost<T: Config> =
StorageValue<_, TaoCurrency, ValueQuery, DefaultMaxEmaResetCost<T>>;

/// ============================
/// ==== Global Parameters =====
/// ============================
Expand Down
30 changes: 30 additions & 0 deletions pallets/subtensor/src/macros/dispatches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2432,5 +2432,35 @@ mod dispatches {

Ok(())
}

/// --- Resets the subnet EMA to zero by burning TAO.
///
/// This extrinsic allows subnet owners to reset negative EMA values that
/// prevent their subnet from receiving emissions. The cost is proportional
/// to |EMA| × (1/α), capped at MaxEmaResetCost.
///
/// # Arguments
/// * `origin` - The origin of the call, must be the subnet owner.
/// * `netuid` - The network identifier of the subnet to reset.
///
/// # Errors
/// * `SubnetNotExists` - The subnet does not exist.
/// * `EmaNotInitialized` - The subnet EMA has not been initialized.
/// * `SubnetEmaNotNegative` - The subnet EMA is not negative, reset not needed.
/// * `NotEnoughBalanceToPayEmaResetCost` - Insufficient balance to pay reset cost.
///
/// # Events
/// Emits a `SubnetEmaReset` event on success.
#[pallet::call_index(125)]
#[pallet::weight((
Weight::from_parts(35_000_000, 0)
.saturating_add(T::DbWeight::get().reads(5_u64))
.saturating_add(T::DbWeight::get().writes(3_u64)),
DispatchClass::Normal,
Pays::Yes
))]
pub fn reset_subnet_ema(origin: OriginFor<T>, netuid: NetUid) -> DispatchResult {
Self::do_reset_subnet_ema(origin, netuid)
}
}
}
6 changes: 6 additions & 0 deletions pallets/subtensor/src/macros/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,5 +268,11 @@ mod errors {
InvalidSubnetNumber,
/// Unintended precision loss when unstaking alpha
PrecisionLoss,
/// EMA has not been initialized for this subnet.
EmaNotInitialized,
/// Subnet EMA is not negative, reset not needed.
SubnetEmaNotNegative,
/// Not enough balance to pay the EMA reset cost.
NotEnoughBalanceToPayEmaResetCost,
}
}
12 changes: 12 additions & 0 deletions pallets/subtensor/src/macros/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,5 +479,17 @@ mod events {
/// The amount of alpha distributed
alpha: AlphaCurrency,
},

/// Subnet EMA has been reset by the owner.
SubnetEmaReset {
/// The subnet ID
netuid: NetUid,
/// The account that executed the reset
who: T::AccountId,
/// The cost paid for the reset in TAO
cost: TaoCurrency,
/// The previous EMA value before reset (as i128 bits representation of I64F64)
previous_ema: i128,
},
}
}
Loading