code/pallets/subtensor/src/subnets/emission_weights.rs
//! Per-subnet emission weight vector ("etao").
//!
//! Every subnet carries a five-slot vector of `u64` parts out of
//! [`EMISSION_WEIGHT_ONE`] that sums to exactly one and says how each block's
//! alpha emission is divided:
//!
//! | slot | role | v1 rule |
//! |-----:|-----------|-------------------------------------------|
//! | 0 | burn | must be 0 (not yet enabled) |
//! | 1 | recycle | must be 0 (not yet enabled) |
//! | 2 | owner | `0 ≤ owner ≤ 33%`; leased subnets `≥ 18%` |
//! | 3 | validator | `≤ 50%`, and must keep root's share |
//! | 4 | miner | `≥ 33%` |
//!
//! Root holders are paid out of the validator slot in proportion to their
//! TAO-weighted stake. So that a subnet cannot cut root's income by shrinking
//! the validator slot, every subnet carries its own effective TAO weight
//! `w_s` ([`SubnetTaoWeight`]). A subnet with no entry follows the global
//! `TaoWeight`. Every accepted set re-solves `w_s` **once**, at set time, so
//! that root's share of the subnet's emission equals a *fresh baseline*: what
//! root would earn on this subnet at this block with the default validator
//! share `V0` and the global `TaoWeight` (see
//! [`Pallet::baseline_root_emission_share`]). The baseline never looks at the
//! old vector or the old `w_s`, so an owner cannot ratchet root's share up or
//! down by changing the validator slot in steps. Afterwards `w_s` is left
//! alone until the next set. Setting the validator share back to `V0` clears
//! the entry so the subnet follows the global knob again. The call is rejected
//! when no `w_s ≤ 1` can reach the baseline.
use super::*;
use frame_support::{BoundedVec, traits::ConstU32};
use safe_math::*;
use substrate_fixed::types::U96F32;
use subtensor_runtime_common::{AlphaBalance, NetUid};
/// Number of slots in the emission weight vector.
pub const EMISSION_WEIGHT_SLOTS: u32 = 5;
/// Denominator of every emission weight: weights are `u64` parts out of `u64::MAX`.
pub const EMISSION_WEIGHT_ONE: u64 = u64::MAX;
/// Storage shape of a subnet's emission weight vector.
pub type EmissionWeightsVec = BoundedVec<u64, ConstU32<EMISSION_WEIGHT_SLOTS>>;
/// In-memory shape of a subnet's emission weight vector.
pub type EmissionWeights = [u64; EMISSION_WEIGHT_SLOTS as usize];
/// `num / den` expressed in parts of [`EMISSION_WEIGHT_ONE`].
pub const fn emission_fraction(num: u64, den: u64) -> u64 {
match (EMISSION_WEIGHT_ONE as u128)
.saturating_mul(num as u128)
.checked_div(den as u128)
{
Some(parts) => parts as u64,
None => 0,
}
}
/// Owner slot may be at most 33% of emission.
pub const MAX_OWNER_EMISSION: u64 = emission_fraction(33, 100);
/// Validator slot may be at most 50% of emission.
pub const MAX_VALIDATOR_EMISSION: u64 = emission_fraction(50, 100);
/// Miner slot must be at least 33% of emission.
pub const MIN_MINER_EMISSION: u64 = emission_fraction(33, 100);
/// Distinct storage items [`Pallet::read_subnet_emission_weights_view`] reads:
/// `SubnetEmissionWeights` and `SubnetCreatedViaLease`.
pub const EMISSION_WEIGHTS_VIEW_READS: u64 = 2;
/// A TAO weight in u64 parts of [`EMISSION_WEIGHT_ONE`] as a fraction in `[0, 1]`.
pub fn normalize_tao_weight(parts: u64) -> U96F32 {
U96F32::saturating_from_num(parts).safe_div(U96F32::saturating_from_num(u64::MAX))
}
/// A TAO weight in `[0, 1]` as u64 parts of [`EMISSION_WEIGHT_ONE`].
pub fn tao_weight_to_parts(weight: U96F32) -> u64 {
weight
.saturating_mul(U96F32::saturating_from_num(EMISSION_WEIGHT_ONE))
.saturating_to_num::<u64>()
}
/// Root's share of a subnet's emission: `V · x / (x + A)` with
/// `x = root_tao · tao_weight` and `A` the subnet's alpha issuance. This is
/// the shape `consensus_root_proportion` applies to the validator slot each
/// block.
pub fn root_emission_share(
validator: U96F32,
tao_weight: U96F32,
root_tao: U96F32,
alpha_issuance: U96F32,
) -> U96F32 {
let weighted_root_tao = root_tao.saturating_mul(tao_weight);
weighted_root_tao
.checked_div(weighted_root_tao.saturating_add(alpha_issuance))
.unwrap_or(U96F32::saturating_from_num(0))
.saturating_mul(validator)
}
/// Solve the TAO weight that gives root exactly the share `root_share` of
/// the emission once the validator slot is `validator`:
/// `x = r · A / (V − r)`, `w = x / root_tao`. `None` when no `w ≤ 1` exists,
/// i.e. `V ≤ r` or root TAO would have to count more than 1:1.
pub fn solve_tao_weight_for_root_share(
root_share: U96F32,
validator: U96F32,
root_tao: U96F32,
alpha_issuance: U96F32,
) -> Option<U96F32> {
if validator <= root_share {
return None;
}
let headroom = validator.saturating_sub(root_share);
let weighted_root_tao = root_share
.saturating_mul(alpha_issuance)
.checked_div(headroom)?;
let tao_weight = weighted_root_tao.checked_div(root_tao)?;
(tao_weight <= U96F32::saturating_from_num(1)).then_some(tao_weight)
}
/// What an accepted set does to a subnet's [`SubnetTaoWeight`] entry.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum SubnetTaoWeightUpdate {
/// Leave the stored entry (or its absence) as it is: there is nothing to
/// preserve (no root TAO or no alpha issuance).
Keep,
/// Remove the entry so the subnet follows the global `TaoWeight` again:
/// the validator share is back at the default `V0`.
FollowGlobal,
/// Freeze this weight (u64 parts) so root's share equals the baseline.
Freeze(u64),
}
/// Snapshot of the storage items behind a subnet's emission weight view, each
/// read once. A caller that has paid for [`EMISSION_WEIGHTS_VIEW_READS`]
/// reads owes nothing more.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EmissionWeightsView {
/// `[burn, recycle, owner, validator, miner]`.
pub weights: EmissionWeights,
/// Whether the subnet was registered through a lease.
pub created_via_lease: bool,
}
/// Named slot of the emission weight vector.
#[derive(
Copy,
Clone,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Encode,
Decode,
DecodeWithMemTracking,
TypeInfo,
MaxEncodedLen,
)]
#[repr(u16)]
pub enum EmissionIndex {
/// Alpha destroyed outright. Reserved: must be zero in v1.
Burn = 0,
/// Alpha burned and un-minted from issuance. Reserved: must be zero in v1.
Recycle = 1,
/// Subnet owner cut.
Owner = 2,
/// Validator dividends (root alpha is carved out of this slot).
Validator = 3,
/// Miner incentive (split across mechanisms by `MechanismEmissionSplit`).
Miner = 4,
}
impl EmissionIndex {
/// Every slot, in vector order.
pub const ALL: [EmissionIndex; EMISSION_WEIGHT_SLOTS as usize] = [
EmissionIndex::Burn,
EmissionIndex::Recycle,
EmissionIndex::Owner,
EmissionIndex::Validator,
EmissionIndex::Miner,
];
/// Decode a wire index; `None` for reserved / unknown slots.
pub fn from_u16(value: u16) -> Option<Self> {
match value {
0 => Some(Self::Burn),
1 => Some(Self::Recycle),
2 => Some(Self::Owner),
3 => Some(Self::Validator),
4 => Some(Self::Miner),
_ => None,
}
}
/// Position of this slot in the vector.
pub const fn position(self) -> usize {
self as u16 as usize
}
}
impl<T: Config> Pallet<T> {
// ---------------------------------------------------------------------
// Defaults
// ---------------------------------------------------------------------
/// Scale a legacy `u16 / u16::MAX` fraction to `u64 / u64::MAX` parts.
pub fn scale_u16_fraction_to_emission_weight(value: u16) -> u64 {
(value as u128)
.saturating_mul(EMISSION_WEIGHT_ONE as u128)
.safe_div(u16::MAX as u128) as u64
}
/// Canonical owner share for subnets that never set a vector: the legacy
/// `InitialSubnetOwnerCut` (18% on mainnet) scaled to u64 parts.
pub fn default_owner_emission() -> u64 {
Self::scale_u16_fraction_to_emission_weight(T::InitialSubnetOwnerCut::get())
}
/// Canonical validator share: half of what remains after the owner cut
/// (41% on mainnet), matching the pre-etao 50/50 split.
pub fn default_validator_emission() -> u64 {
EMISSION_WEIGHT_ONE.saturating_sub(Self::default_owner_emission()) / 2
}
/// Lowest owner share a lease-created subnet may set: the canonical
/// default, so lease dividends can never be squeezed below launch terms.
pub fn leased_subnet_min_owner_emission() -> u64 {
Self::default_owner_emission()
}
/// Build the canonical vector `[0, 0, owner, validator, miner]`; the miner
/// slot absorbs any rounding so the vector sums to exactly one.
pub fn default_emission_weights() -> EmissionWeights {
let owner = Self::default_owner_emission();
let validator = Self::default_validator_emission();
let miner = EMISSION_WEIGHT_ONE
.saturating_sub(owner)
.saturating_sub(validator);
[0, 0, owner, validator, miner]
}
/// `[0, 0, owner, validator, miner]` with the non-owner remainder split
/// evenly between validators and miners (miner absorbs rounding).
pub fn emission_weights_with_owner(owner: u64) -> EmissionWeights {
let validator = EMISSION_WEIGHT_ONE.saturating_sub(owner) / 2;
let miner = EMISSION_WEIGHT_ONE
.saturating_sub(owner)
.saturating_sub(validator);
[0, 0, owner, validator, miner]
}
pub(crate) fn emission_weights_to_vec(weights: EmissionWeights) -> EmissionWeightsVec {
EmissionWeightsVec::truncate_from(weights.to_vec())
}
// ---------------------------------------------------------------------
// Reads
// ---------------------------------------------------------------------
/// The subnet's full emission weight vector.
pub fn get_subnet_emission_weights(netuid: NetUid) -> EmissionWeights {
let stored = SubnetEmissionWeights::<T>::get(netuid);
let mut weights = [0u64; EMISSION_WEIGHT_SLOTS as usize];
for (slot, value) in weights.iter_mut().zip(stored.iter()) {
*slot = *value;
}
weights
}
/// One slot of the subnet's emission weight vector.
pub fn get_subnet_emission_weight(netuid: NetUid, index: EmissionIndex) -> u64 {
Self::get_subnet_emission_weights(netuid)
.get(index.position())
.copied()
.unwrap_or_default()
}
/// Burn share (slot 0). Always zero in v1.
pub fn get_subnet_burn_emission(netuid: NetUid) -> u64 {
Self::get_subnet_emission_weight(netuid, EmissionIndex::Burn)
}
/// Recycle share (slot 1). Always zero in v1.
pub fn get_subnet_recycle_emission(netuid: NetUid) -> u64 {
Self::get_subnet_emission_weight(netuid, EmissionIndex::Recycle)
}
/// Owner cut (slot 2) in u64 parts.
pub fn get_subnet_owner_emission_cut(netuid: NetUid) -> u64 {
Self::get_subnet_emission_weight(netuid, EmissionIndex::Owner)
}
/// Validator share (slot 3) in u64 parts.
pub fn get_subnet_validator_emission_cut(netuid: NetUid) -> u64 {
Self::get_subnet_emission_weight(netuid, EmissionIndex::Validator)
}
/// Miner share (slot 4) in u64 parts.
pub fn get_subnet_miner_emission_cut(netuid: NetUid) -> u64 {
Self::get_subnet_emission_weight(netuid, EmissionIndex::Miner)
}
/// Convert u64 parts to a `[0, 1]` fixed-point fraction.
pub fn emission_weight_to_fixed(weight: u64) -> U96F32 {
U96F32::saturating_from_num(weight)
.safe_div(U96F32::saturating_from_num(EMISSION_WEIGHT_ONE))
}
/// Owner cut as a `[0, 1]` fraction.
pub fn get_float_subnet_owner_emission_cut(netuid: NetUid) -> U96F32 {
Self::emission_weight_to_fixed(Self::get_subnet_owner_emission_cut(netuid))
}
/// Validator share as a `[0, 1]` fraction.
pub fn get_float_subnet_validator_emission_cut(netuid: NetUid) -> U96F32 {
Self::emission_weight_to_fixed(Self::get_subnet_validator_emission_cut(netuid))
}
/// Miner share as a `[0, 1]` fraction.
pub fn get_float_subnet_miner_emission_cut(netuid: NetUid) -> U96F32 {
Self::emission_weight_to_fixed(Self::get_subnet_miner_emission_cut(netuid))
}
/// Whether the owner receives any emission cut (legacy `OwnerCutEnabled`
/// view: true iff the owner slot is non-zero).
pub fn get_owner_cut_enabled(netuid: NetUid) -> bool {
Self::get_subnet_owner_emission_cut(netuid) > 0
}
/// Effective TAO weight `w_s` on `netuid` as a `[0, 1]` fraction. Consensus-side
/// only: it feeds the root carve-out of the validator slot, dividend and
/// child/parent contribution splits, and the stake weighting in Yuma
/// Consensus. The pool side (`tao_in`, `alpha_in`, the injection cap,
/// `RootProp`) uses the global [`Pallet::get_tao_weight`] and must never
/// read this. A subnet that has never changed its validator share reads
/// the global `TaoWeight`.
pub fn get_subnet_tao_weight(netuid: NetUid) -> U96F32 {
normalize_tao_weight(Self::get_subnet_tao_weight_parts(netuid))
}
/// [`Self::get_subnet_tao_weight`] in u64 parts, for RPC. Falls back to the
/// global `TaoWeight` while the subnet has no frozen weight of its own.
pub fn get_subnet_tao_weight_parts(netuid: NetUid) -> u64 {
SubnetTaoWeight::<T>::get(netuid).unwrap_or_else(TaoWeight::<T>::get)
}
/// Whether the subnet was registered through a lease (persists after the
/// lease ends).
pub fn get_subnet_created_via_lease(netuid: NetUid) -> bool {
SubnetCreatedViaLease::<T>::get(netuid)
}
/// Every storage item behind the emission weight view, each read exactly
/// once. Views that meter storage (the EVM precompile) charge
/// [`EMISSION_WEIGHTS_VIEW_READS`] reads and derive the rest from this.
pub fn read_subnet_emission_weights_view(netuid: NetUid) -> EmissionWeightsView {
EmissionWeightsView {
weights: Self::get_subnet_emission_weights(netuid),
created_via_lease: Self::get_subnet_created_via_lease(netuid),
}
}
/// Root's current share of `netuid`'s emission under its stored state:
/// `V · x / (x + A)`, see [`root_emission_share`].
pub fn get_root_emission_share(netuid: NetUid) -> U96F32 {
root_emission_share(
Self::get_float_subnet_validator_emission_cut(netuid),
Self::get_subnet_tao_weight(netuid),
U96F32::saturating_from_num(Self::get_subnet_tao(NetUid::ROOT)),
U96F32::saturating_from_num(Self::get_alpha_issuance(netuid)),
)
}
/// Root's *baseline* share of `netuid`'s emission at this block: what root
/// would earn with the default validator share `V0`
/// ([`Self::default_validator_emission`]) and the global `TaoWeight`,
/// `V0 · x_g / (x_g + A)` with `x_g = root_tao · TaoWeight`. This is the
/// target every set re-solves `w_s` for. It reads neither the subnet's
/// current vector nor its frozen weight, so a sequence of sets cannot move
/// root's share away from what an untouched subnet would pay.
pub fn baseline_root_emission_share(netuid: NetUid) -> U96F32 {
root_emission_share(
Self::emission_weight_to_fixed(Self::default_validator_emission()),
Self::get_tao_weight(),
U96F32::saturating_from_num(Self::get_subnet_tao(NetUid::ROOT)),
U96F32::saturating_from_num(Self::get_alpha_issuance(netuid)),
)
}
// ---------------------------------------------------------------------
// Writes
// ---------------------------------------------------------------------
/// Seed a freshly registered subnet with the canonical vector and reset any
/// per-subnet state a previous occupant of this netuid may have left
/// (effective TAO weight, lease marker, accrued owner cut). With no
/// `SubnetTaoWeight` entry the subnet follows the global `TaoWeight`. The
/// accrued owner cut is written as an explicit zero so that an *absent*
/// entry always means "pre-dates the seed migration" (see
/// [`Self::owner_cut_alpha_for_lock_refund`]). Lease registration sets the
/// marker after this runs.
pub fn seed_subnet_emission_weights(netuid: NetUid) {
if netuid.is_root() {
return;
}
let weights = Self::default_emission_weights();
SubnetEmissionWeights::<T>::insert(netuid, Self::emission_weights_to_vec(weights));
SubnetTaoWeight::<T>::remove(netuid);
SubnetCreatedViaLease::<T>::remove(netuid);
SubnetOwnerCutAccrued::<T>::insert(netuid, AlphaBalance::ZERO);
}
/// The owner α the pre-emission-weights lock refund deducted:
/// `alpha_issuance × legacy_owner_cut / u16::MAX`, floored. `legacy_owner_cut`
/// is the frozen global `SubnetOwnerCut` (its setter is deprecated).
pub fn legacy_owner_cut_accrued(netuid: NetUid, legacy_owner_cut: u16) -> AlphaBalance {
let issuance: u64 = Self::get_alpha_issuance(netuid).to_u64();
let fraction = U96F32::saturating_from_num(legacy_owner_cut)
.safe_div(U96F32::saturating_from_num(u16::MAX));
U96F32::saturating_from_num(issuance)
.saturating_mul(fraction)
.floor()
.saturating_to_num::<u64>()
.into()
}
/// Owner α to deduct from a legacy lock refund. The accrued history
/// (`SubnetOwnerCutAccrued`) when the subnet has an entry; otherwise the
/// pre-upgrade formula. Every subnet registered or migrated after the seed
/// migration has an entry (possibly zero), so the fallback only fires for a
/// subnet that was already dissolving when the migration ran and was not
/// seeded. It can never make the deduction silently zero.
pub fn owner_cut_alpha_for_lock_refund(netuid: NetUid) -> AlphaBalance {
match SubnetOwnerCutAccrued::<T>::try_get(netuid) {
Ok(accrued) => accrued,
Err(()) => Self::legacy_owner_cut_accrued(netuid, Self::get_subnet_owner_cut()),
}
}
/// Write a vector verbatim: no rule checks, no TAO-weight solve, no
/// event. Migration and test plumbing only; extrinsics must go through
/// [`Self::do_set_subnet_emission_weights`].
pub fn set_subnet_emission_weights_unchecked(netuid: NetUid, weights: EmissionWeights) {
SubnetEmissionWeights::<T>::insert(netuid, Self::emission_weights_to_vec(weights));
}
/// Apply a sparse `(uids, values)` update on top of `current`. Omitted
/// slots keep their current value; burn and recycle are always treated as
/// zero when omitted.
pub fn merge_emission_weight_updates(
current: EmissionWeights,
uids: &[u16],
values: &[u64],
) -> Result<EmissionWeights, Error<T>> {
ensure!(
uids.len() == values.len(),
Error::<T>::EmissionWeightsLengthMismatch
);
let mut merged = current;
for reserved in [EmissionIndex::Burn, EmissionIndex::Recycle] {
if let Some(target) = merged.get_mut(reserved.position()) {
*target = 0;
}
}
let mut seen = [false; EMISSION_WEIGHT_SLOTS as usize];
for (uid, value) in uids.iter().zip(values.iter()) {
let index =
EmissionIndex::from_u16(*uid).ok_or(Error::<T>::EmissionWeightIndexOutOfRange)?;
let slot = index.position();
ensure!(
!seen.get(slot).copied().unwrap_or(true),
Error::<T>::EmissionWeightIndexDuplicated
);
if let Some(flag) = seen.get_mut(slot) {
*flag = true;
}
if let Some(target) = merged.get_mut(slot) {
*target = *value;
}
}
Ok(merged)
}
/// Check every v1 rule against a full vector. Each failure names the rule
/// so callers learn the constraints from the error alone.
pub fn validate_subnet_emission_weights(
netuid: NetUid,
weights: &EmissionWeights,
) -> DispatchResult {
let slot = |index: EmissionIndex| weights.get(index.position()).copied().unwrap_or(0);
ensure!(
slot(EmissionIndex::Burn) == 0,
Error::<T>::BurnEmissionMustBeZero
);
ensure!(
slot(EmissionIndex::Recycle) == 0,
Error::<T>::RecycleEmissionMustBeZero
);
let owner = slot(EmissionIndex::Owner);
ensure!(
owner <= MAX_OWNER_EMISSION,
Error::<T>::OwnerEmissionAboveMaximum
);
if Self::get_subnet_created_via_lease(netuid) {
ensure!(
owner >= Self::leased_subnet_min_owner_emission(),
Error::<T>::LeasedSubnetOwnerCutBelowMinimum
);
}
let validator = slot(EmissionIndex::Validator);
ensure!(
validator <= MAX_VALIDATOR_EMISSION,
Error::<T>::ValidatorEmissionAboveMaximum
);
ensure!(
slot(EmissionIndex::Miner) >= MIN_MINER_EMISSION,
Error::<T>::MinerEmissionBelowMinimum
);
let total: u128 = weights.iter().map(|w| *w as u128).sum();
ensure!(
total == EMISSION_WEIGHT_ONE as u128,
Error::<T>::EmissionWeightsDoNotSumToOne
);
Ok(())
}
/// What `netuid`'s effective TAO weight must become so that, with the
/// validator slot at `new_validator`, root's share of emission equals the
/// fresh baseline [`Self::baseline_root_emission_share`] at this block.
/// Only `new_validator` and live chain values go in: the old vector and
/// the old weight are never consulted, so repeated sets cannot ratchet
/// root's share in either direction. `new_validator == V0` clears the
/// entry (the solve would land on the global weight anyway); no root TAO
/// or no alpha issuance keeps whatever is stored. Fails with
/// [`Error::ValidatorEmissionTooLowToPreserveRoot`] when `V_new ≤ r` or the
/// solved weight would exceed one (root TAO never counts more than 1:1).
pub fn tao_weight_preserving_root(
netuid: NetUid,
new_validator: u64,
) -> Result<SubnetTaoWeightUpdate, Error<T>> {
let root_tao = U96F32::saturating_from_num(Self::get_subnet_tao(NetUid::ROOT));
let alpha_issuance = U96F32::saturating_from_num(Self::get_alpha_issuance(netuid));
let zero = U96F32::saturating_from_num(0);
if root_tao == zero || alpha_issuance == zero {
return Ok(SubnetTaoWeightUpdate::Keep);
}
if new_validator == Self::default_validator_emission() {
return Ok(SubnetTaoWeightUpdate::FollowGlobal);
}
let tao_weight = solve_tao_weight_for_root_share(
Self::baseline_root_emission_share(netuid),
Self::emission_weight_to_fixed(new_validator),
root_tao,
alpha_issuance,
)
.ok_or(Error::<T>::ValidatorEmissionTooLowToPreserveRoot)?;
Ok(SubnetTaoWeightUpdate::Freeze(tao_weight_to_parts(
tao_weight,
)))
}
/// Merge, validate and store a sparse emission weight update, re-anchor
/// the subnet's effective TAO weight to the baseline for the new validator
/// slot, and emit [`Event::SubnetEmissionWeightsSet`].
pub fn do_set_subnet_emission_weights(
netuid: NetUid,
uids: Vec<u16>,
values: Vec<u64>,
) -> DispatchResult {
ensure!(Self::if_subnet_exist(netuid), Error::<T>::SubnetNotExists);
ensure!(
!netuid.is_root(),
Error::<T>::EmissionWeightsNotSettableOnRoot
);
let current = Self::get_subnet_emission_weights(netuid);
let merged = Self::merge_emission_weight_updates(current, &uids, &values)?;
Self::validate_subnet_emission_weights(netuid, &merged)?;
let new_validator = merged
.get(EmissionIndex::Validator.position())
.copied()
.unwrap_or(0);
let tao_weight = Self::tao_weight_preserving_root(netuid, new_validator)?;
SubnetEmissionWeights::<T>::insert(netuid, Self::emission_weights_to_vec(merged));
match tao_weight {
SubnetTaoWeightUpdate::Keep => {}
SubnetTaoWeightUpdate::FollowGlobal => SubnetTaoWeight::<T>::remove(netuid),
SubnetTaoWeightUpdate::Freeze(parts) => SubnetTaoWeight::<T>::insert(netuid, parts),
}
Self::deposit_event(Event::SubnetEmissionWeightsSet {
netuid,
weights: merged.to_vec(),
});
Ok(())
}
}