Skip to content

Commit 5acc0cf

Browse files
calm329claude
andcommitted
feat: implement subnet EMA reset mechanism
This commit implements a mechanism for subnet owners to reset negative EMA values that prevent their subnet from receiving emissions. Changes: - Add MaxEmaResetCost storage item (default: 100 TAO) - Add reset_subnet_ema extrinsic (call_index: 125) - Add get_ema_reset_cost helper function - Add EmaNotInitialized, SubnetEmaNotNegative, NotEnoughBalanceToPayEmaResetCost errors - Add SubnetEmaReset event with netuid, who, cost, previous_ema - Add comprehensive tests for the new functionality The reset cost is calculated as |EMA| × (1/α), capped at MaxEmaResetCost. The burned TAO is recycled to reduce total issuance. Closes #2228 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent be47139 commit 5acc0cf

File tree

6 files changed

+439
-0
lines changed

6 files changed

+439
-0
lines changed

pallets/subtensor/src/coinbase/subnet_emissions.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use super::*;
22
use alloc::collections::BTreeMap;
3+
use frame_support::dispatch::DispatchResult;
34
use safe_math::FixedExt;
45
use substrate_fixed::transcendental::{exp, ln};
56
use substrate_fixed::types::{I32F32, I64F64, U64F64, U96F32};
7+
use subtensor_runtime_common::TaoCurrency;
68

79
impl<T: Config> Pallet<T> {
810
pub fn get_subnets_to_emit_to(subnets: &[NetUid]) -> Vec<NetUid> {
@@ -246,4 +248,104 @@ impl<T: Config> Pallet<T> {
246248
})
247249
.collect::<BTreeMap<NetUid, U64F64>>()
248250
}
251+
252+
/// Calculates the cost to reset a subnet's EMA to zero.
253+
///
254+
/// The cost formula is: |EMA| × (1/α), capped at MaxEmaResetCost.
255+
/// Where α is the FlowEmaSmoothingFactor normalized by i64::MAX.
256+
///
257+
/// Returns the cost in RAO (TaoCurrency), or None if EMA is not negative.
258+
pub fn get_ema_reset_cost(netuid: NetUid) -> Option<TaoCurrency> {
259+
// Get the current EMA value
260+
let (_, ema) = SubnetEmaTaoFlow::<T>::get(netuid)?;
261+
262+
// Only allow reset if EMA is negative
263+
if ema >= I64F64::saturating_from_num(0) {
264+
return None;
265+
}
266+
267+
// Get the absolute value of EMA
268+
let abs_ema = ema.saturating_abs();
269+
270+
// Get the smoothing factor (alpha) and normalize it
271+
// FlowEmaSmoothingFactor is stored as u64 normalized by i64::MAX (2^63)
272+
let alpha_normalized = FlowEmaSmoothingFactor::<T>::get();
273+
274+
// Cost = |EMA| × (1/α) = |EMA| × (i64::MAX / alpha_normalized)
275+
// This can overflow, so we need to be careful with the calculation
276+
let i64_max = I64F64::saturating_from_num(i64::MAX);
277+
let alpha = I64F64::saturating_from_num(alpha_normalized).safe_div(i64_max);
278+
279+
// Calculate cost = |EMA| / alpha
280+
let cost_raw = abs_ema.safe_div(alpha);
281+
282+
// Convert to u64 (RAO)
283+
let cost_rao = cost_raw
284+
.checked_to_num::<u64>()
285+
.unwrap_or(u64::MAX);
286+
287+
// Cap at MaxEmaResetCost
288+
let max_cost = MaxEmaResetCost::<T>::get();
289+
let cost = TaoCurrency::from(cost_rao).min(max_cost);
290+
291+
Some(cost)
292+
}
293+
294+
/// Resets the subnet EMA to zero by burning TAO.
295+
///
296+
/// This function allows subnet owners to reset negative EMA values that
297+
/// prevent their subnet from receiving emissions.
298+
pub fn do_reset_subnet_ema(origin: T::RuntimeOrigin, netuid: NetUid) -> DispatchResult {
299+
// Ensure the subnet exists
300+
ensure!(Self::if_subnet_exist(netuid), Error::<T>::SubnetNotExists);
301+
302+
// Ensure the caller is the subnet owner
303+
let who = Self::ensure_subnet_owner(origin, netuid)?;
304+
305+
// Get the current EMA value - check if initialized
306+
let (_, previous_ema) = SubnetEmaTaoFlow::<T>::get(netuid)
307+
.ok_or(Error::<T>::EmaNotInitialized)?;
308+
309+
// Ensure EMA is negative
310+
ensure!(
311+
previous_ema < I64F64::saturating_from_num(0),
312+
Error::<T>::SubnetEmaNotNegative
313+
);
314+
315+
// Get the reset cost
316+
let cost = Self::get_ema_reset_cost(netuid)
317+
.ok_or(Error::<T>::SubnetEmaNotNegative)?;
318+
319+
// Ensure the owner has enough balance
320+
ensure!(
321+
Self::can_remove_balance_from_coldkey_account(&who, cost.into()),
322+
Error::<T>::NotEnoughBalanceToPayEmaResetCost
323+
);
324+
325+
// Remove the balance from the owner's account
326+
let actual_cost = Self::remove_balance_from_coldkey_account(&who, cost.into())?;
327+
328+
// Burn the TAO (reduce total issuance)
329+
Self::recycle_tao(actual_cost);
330+
331+
// Reset the EMA to zero
332+
let current_block = Self::get_current_block_as_u64();
333+
SubnetEmaTaoFlow::<T>::insert(netuid, (current_block, I64F64::saturating_from_num(0)));
334+
335+
// Also reset the accumulated flow
336+
Self::reset_tao_outflow(netuid);
337+
338+
// Convert previous_ema to i128 for the event (I64F64 is 128 bits total)
339+
let previous_ema_bits = previous_ema.to_bits();
340+
341+
// Emit the event
342+
Self::deposit_event(Event::SubnetEmaReset {
343+
netuid,
344+
who,
345+
cost: actual_cost,
346+
previous_ema: previous_ema_bits,
347+
});
348+
349+
Ok(())
350+
}
249351
}

pallets/subtensor/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,6 +1493,16 @@ pub mod pallet {
14931493
pub type FlowEmaSmoothingFactor<T: Config> =
14941494
StorageValue<_, u64, ValueQuery, DefaultFlowEmaSmoothingFactor<T>>;
14951495

1496+
#[pallet::type_value]
1497+
/// Default maximum cost for resetting subnet EMA (100 TAO in RAO).
1498+
pub fn DefaultMaxEmaResetCost<T: Config>() -> TaoCurrency {
1499+
TaoCurrency::from(100_000_000_000u64) // 100 TAO
1500+
}
1501+
#[pallet::storage]
1502+
/// --- ITEM --> Maximum cost for resetting subnet EMA
1503+
pub type MaxEmaResetCost<T: Config> =
1504+
StorageValue<_, TaoCurrency, ValueQuery, DefaultMaxEmaResetCost<T>>;
1505+
14961506
/// ============================
14971507
/// ==== Global Parameters =====
14981508
/// ============================

pallets/subtensor/src/macros/dispatches.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2432,5 +2432,35 @@ mod dispatches {
24322432

24332433
Ok(())
24342434
}
2435+
2436+
/// --- Resets the subnet EMA to zero by burning TAO.
2437+
///
2438+
/// This extrinsic allows subnet owners to reset negative EMA values that
2439+
/// prevent their subnet from receiving emissions. The cost is proportional
2440+
/// to |EMA| × (1/α), capped at MaxEmaResetCost.
2441+
///
2442+
/// # Arguments
2443+
/// * `origin` - The origin of the call, must be the subnet owner.
2444+
/// * `netuid` - The network identifier of the subnet to reset.
2445+
///
2446+
/// # Errors
2447+
/// * `SubnetNotExists` - The subnet does not exist.
2448+
/// * `EmaNotInitialized` - The subnet EMA has not been initialized.
2449+
/// * `SubnetEmaNotNegative` - The subnet EMA is not negative, reset not needed.
2450+
/// * `NotEnoughBalanceToPayEmaResetCost` - Insufficient balance to pay reset cost.
2451+
///
2452+
/// # Events
2453+
/// Emits a `SubnetEmaReset` event on success.
2454+
#[pallet::call_index(125)]
2455+
#[pallet::weight((
2456+
Weight::from_parts(35_000_000, 0)
2457+
.saturating_add(T::DbWeight::get().reads(5_u64))
2458+
.saturating_add(T::DbWeight::get().writes(3_u64)),
2459+
DispatchClass::Normal,
2460+
Pays::Yes
2461+
))]
2462+
pub fn reset_subnet_ema(origin: OriginFor<T>, netuid: NetUid) -> DispatchResult {
2463+
Self::do_reset_subnet_ema(origin, netuid)
2464+
}
24352465
}
24362466
}

pallets/subtensor/src/macros/errors.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,5 +268,11 @@ mod errors {
268268
InvalidSubnetNumber,
269269
/// Unintended precision loss when unstaking alpha
270270
PrecisionLoss,
271+
/// EMA has not been initialized for this subnet.
272+
EmaNotInitialized,
273+
/// Subnet EMA is not negative, reset not needed.
274+
SubnetEmaNotNegative,
275+
/// Not enough balance to pay the EMA reset cost.
276+
NotEnoughBalanceToPayEmaResetCost,
271277
}
272278
}

pallets/subtensor/src/macros/events.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,5 +479,17 @@ mod events {
479479
/// The amount of alpha distributed
480480
alpha: AlphaCurrency,
481481
},
482+
483+
/// Subnet EMA has been reset by the owner.
484+
SubnetEmaReset {
485+
/// The subnet ID
486+
netuid: NetUid,
487+
/// The account that executed the reset
488+
who: T::AccountId,
489+
/// The cost paid for the reset in TAO
490+
cost: TaoCurrency,
491+
/// The previous EMA value before reset (as i128 bits representation of I64F64)
492+
previous_ema: i128,
493+
},
482494
}
483495
}

0 commit comments

Comments
 (0)