code/pallets/subtensor/src/subnets/emission_weights.rs

emission_weights.rs

728 lines · 30,081 bytes · d967d13588RawGitHub
//! 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      | with recycle, `≤ 33%` combined            |
//! | 1    | recycle   | with burn, `≤ 33%` combined               |
//! | 2    | owner     | `0 ≤ owner ≤ 33%`; leased subnets `≥ 18%` |
//! | 3    | validator | `≤ 50%`, and above root's baseline share  |
//! | 4    | miner     | `≥ 33%`                                   |
//!
//! A subnet may also give shares of at most 10% each to the gamma of
//! whitelisted subnets with gamma enabled ([`EmissionSlot::Gamma`], stored in
//! [`SubnetGammaEmission`]); the five slots and the gamma shares together sum
//! to exactly one.
//!
//! Root holders are paid **off the top** of the validator slot. Every block,
//! root's alpha on a subnet is `min(V, r) · alpha_out` where `r` is root's
//! *baseline* share ([`Pallet::baseline_root_emission_share`]): what root
//! earns on this subnet at this block with the default validator share `V0`
//! and the global `TaoWeight`, `r = V0 · x_g / (x_g + A)` with
//! `x_g = root_tao · TaoWeight` and `A` the subnet's alpha issuance. This is
//! exactly what a subnet on the default split pays root, and it is recomputed
//! from live chain state every block, so root's income tracks root stake,
//! alpha issuance and the global `TaoWeight` with no per-subnet state and no
//! further calls. Alpha validators receive the rest of the slot,
//! `V · alpha_out − root_alpha`. The owner and miner slots are untouched.
//!
//! A set is rejected when the new validator share is at or below the live
//! baseline (`V_new ≤ r`): validators would get nothing. If the baseline later
//! grows past `V` (root stake up, issuance down, `TaoWeight` up), root's cut
//! is capped at the whole validator slot and alpha validators get zero until
//! the owner raises `V` or the baseline recedes.
//!
//! Consensus stake weighting, the root/alpha dividend split and child/parent
//! contributions all use the global `TaoWeight`, exactly as before per-subnet
//! emission weights existed.
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);
/// Burn and recycle slots together may be at most 33% of emission.
pub const MAX_BURN_RECYCLE_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);
/// Each gamma share may be at most 10% of emission.
pub const MAX_GAMMA_EMISSION: u64 = emission_fraction(10, 100);
/// Most gamma shares a split has, as many as a subnet's mechanisms.
pub const MAX_GAMMA_SHARES: u32 = 16;
/// Most slots a split has: the five fixed slots and the gamma shares.
pub const MAX_EMISSION_SLOTS: u32 = EMISSION_WEIGHT_SLOTS + MAX_GAMMA_SHARES;

/// A subnet's gamma shares, `(target, share)` in ascending target order.
pub type GammaEmission = BoundedVec<(NetUid, u64), ConstU32<MAX_GAMMA_SHARES>>;

/// A whole emission split, its slots in strictly ascending order.
pub type EmissionSplit = BoundedVec<(EmissionSlot, u64), ConstU32<MAX_EMISSION_SLOTS>>;

/// 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 share in `[0, 1]` as u64 parts of [`EMISSION_WEIGHT_ONE`].
pub fn emission_share_to_parts(share: U96F32) -> u64 {
    share
        .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. With the
/// default validator share and the global `TaoWeight` this is root's
/// baseline, the amount `emit_to_subnets` pays root off the top of the
/// validator slot every 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)
}

/// 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 (still counts toward issuance).
    Burn = 0,
    /// Alpha removed from issuance (SubnetAlphaOut and TotalAlphaIssuance).
    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
    }
}

/// A slot of a subnet's emission split: a fixed slot or a share of a subnet's gamma.
#[derive(
    Copy,
    Clone,
    Debug,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Encode,
    Decode,
    DecodeWithMemTracking,
    TypeInfo,
    MaxEncodedLen,
)]
pub enum EmissionSlot {
    /// [`EmissionIndex::Burn`].
    Burn,
    /// [`EmissionIndex::Recycle`].
    Recycle,
    /// [`EmissionIndex::Owner`].
    Owner,
    /// [`EmissionIndex::Validator`].
    Validator,
    /// [`EmissionIndex::Miner`].
    Miner,
    /// A share of the alpha emission, converted at each epoch into the gamma of subnet `netuid`
    /// for the subnet owner hotkey.
    Gamma { netuid: NetUid },
}

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).
    pub fn get_subnet_burn_emission(netuid: NetUid) -> u64 {
        Self::get_subnet_emission_weight(netuid, EmissionIndex::Burn)
    }

    /// Recycle share (slot 1).
    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
    }

    /// 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 *baseline* share `r` of `netuid`'s emission at this block: what
    /// root earns 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` and `A` the
    /// subnet's alpha issuance. Recomputed from live chain state on every read;
    /// the subnet's own vector plays no part, so it is exactly what a subnet on
    /// the default split pays root.
    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)),
        )
    }

    /// Root's share of `netuid`'s emission this block: the baseline, capped at
    /// the validator slot, `min(V, r)`. Root is paid this off the top of the
    /// validator slot and alpha validators get `V − min(V, r)`; the cap only
    /// binds when the baseline has grown past `V` since the owner set it.
    pub fn get_root_emission_share(netuid: NetUid) -> U96F32 {
        Self::baseline_root_emission_share(netuid)
            .min(Self::get_float_subnet_validator_emission_cut(netuid))
    }

    /// Whether root's baseline share exceeds the validator slot, so alpha
    /// validators on `netuid` receive nothing this block.
    pub fn is_root_emission_share_capped(netuid: NetUid) -> bool {
        Self::baseline_root_emission_share(netuid)
            > Self::get_float_subnet_validator_emission_cut(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
    /// (lease marker, accrued owner cut). 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));
        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 root-baseline check, 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.
    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;
        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,
        shares: &[(NetUid, u64)],
    ) -> DispatchResult {
        let slot = |index: EmissionIndex| weights.get(index.position()).copied().unwrap_or(0);

        let burn = slot(EmissionIndex::Burn);
        let recycle = slot(EmissionIndex::Recycle);
        ensure!(
            (burn as u128).saturating_add(recycle as u128) <= MAX_BURN_RECYCLE_EMISSION as u128,
            Error::<T>::BurnRecycleEmissionAboveMaximum
        );

        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()
            .chain(shares.iter().map(|(_, share)| share))
            .map(|w| *w as u128)
            .sum();
        ensure!(
            total == EMISSION_WEIGHT_ONE as u128,
            Error::<T>::EmissionWeightsDoNotSumToOne
        );
        Ok(())
    }

    /// The validator slot must leave alpha validators something after root is
    /// paid off the top: `V_new > r` where `r` is root's live baseline share
    /// [`Self::baseline_root_emission_share`]. Only `new_validator` and live
    /// chain values go in; the old vector plays no part. Fails with
    /// [`Error::ValidatorEmissionTooLowToPreserveRoot`] when `V_new ≤ r`.
    ///
    /// The comparison is made in u64 parts of [`EMISSION_WEIGHT_ONE`], the
    /// same unit the runtime API reports as `root_baseline_share`, so the
    /// smallest accepted value is exactly `root_baseline_share + 1` and
    /// clients can compute the floor without reproducing fixed-point rounding.
    pub fn ensure_validator_share_above_root_baseline(
        netuid: NetUid,
        new_validator: u64,
    ) -> Result<(), Error<T>> {
        ensure!(
            new_validator > emission_share_to_parts(Self::baseline_root_emission_share(netuid)),
            Error::<T>::ValidatorEmissionTooLowToPreserveRoot
        );
        Ok(())
    }

    /// Merge, validate and store a sparse emission weight update, after
    /// checking the new validator slot clears root's live baseline share, 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,
            &SubnetGammaEmission::<T>::get(netuid),
        )?;

        let new_validator = merged
            .get(EmissionIndex::Validator.position())
            .copied()
            .unwrap_or(0);
        Self::ensure_validator_share_above_root_baseline(netuid, new_validator)?;

        SubnetEmissionWeights::<T>::insert(netuid, Self::emission_weights_to_vec(merged));

        Self::deposit_event(Event::SubnetEmissionWeightsSet {
            netuid,
            weights: merged.to_vec(),
        });
        Ok(())
    }

    /// Replace the subnet's whole emission split with `split`, after checking every rule and
    /// that the new validator slot clears root's live baseline share. A slot left out is zero,
    /// and a gamma slot of zero is no share.
    pub fn do_set_subnet_emission_weights_v2(
        netuid: NetUid,
        split: EmissionSplit,
    ) -> DispatchResult {
        ensure!(Self::if_subnet_exist(netuid), Error::<T>::SubnetNotExists);
        ensure!(
            !netuid.is_root(),
            Error::<T>::EmissionWeightsNotSettableOnRoot
        );
        ensure!(
            split.is_sorted_by(|(slot, _), (next, _)| slot < next),
            Error::<T>::EmissionSlotsNotAscending
        );

        let mut weights = [0u64; EMISSION_WEIGHT_SLOTS as usize];
        let mut shares = GammaEmission::default();
        for (slot, share) in split {
            let index = match slot {
                EmissionSlot::Burn => EmissionIndex::Burn,
                EmissionSlot::Recycle => EmissionIndex::Recycle,
                EmissionSlot::Owner => EmissionIndex::Owner,
                EmissionSlot::Validator => EmissionIndex::Validator,
                EmissionSlot::Miner => EmissionIndex::Miner,
                EmissionSlot::Gamma { netuid: target } => {
                    ensure!(target != netuid, Error::<T>::InvalidValue);
                    ensure!(
                        SubnetGammaEmissionWhitelisted::<T>::get(target),
                        Error::<T>::GammaEmissionTargetNotWhitelisted
                    );
                    ensure!(
                        SubnetGammaEnabled::<T>::get(target),
                        Error::<T>::GammaDisabled
                    );
                    ensure!(
                        share <= MAX_GAMMA_EMISSION,
                        Error::<T>::GammaEmissionAboveMaximum
                    );
                    if share > 0 {
                        shares
                            .try_push((target, share))
                            .map_err(|_| Error::<T>::TooManyGammaShares)?;
                    }
                    continue;
                }
            };
            if let Some(weight) = weights.get_mut(index.position()) {
                *weight = share;
            }
        }
        ensure!(
            shares.is_empty() || !SubnetUidToLeaseId::<T>::contains_key(netuid),
            Error::<T>::GammaEmissionNotSettableOnLeasedSubnet
        );
        Self::validate_subnet_emission_weights(netuid, &weights, &shares)?;
        Self::ensure_validator_share_above_root_baseline(
            netuid,
            weights
                .get(EmissionIndex::Validator.position())
                .copied()
                .unwrap_or(0),
        )?;

        Self::set_subnet_emission_split(netuid, weights, shares);
        Ok(())
    }

    /// Store a subnet's split and emit it, after allotting the gamma emission accrued under the
    /// old split. A gamma share that leaves the split moves its unconverted alpha to the
    /// subnet's pending miner emission.
    fn set_subnet_emission_split(netuid: NetUid, weights: EmissionWeights, shares: GammaEmission) {
        let old = SubnetGammaEmission::<T>::get(netuid);
        Self::allot_gamma_emission(netuid, &old);
        let returned = old
            .iter()
            .filter(|(target, _)| !shares.iter().any(|(kept, _)| kept == target))
            .fold(AlphaBalance::ZERO, |total, (target, _)| {
                total.saturating_add(UnconvertedGammaEmission::<T>::take(netuid, target))
            });
        PendingServerEmission::<T>::mutate(netuid, |total| {
            *total = total.saturating_add(returned);
        });
        SubnetEmissionWeights::<T>::insert(netuid, Self::emission_weights_to_vec(weights));
        if shares.is_empty() {
            SubnetGammaEmission::<T>::remove(netuid);
        } else {
            SubnetGammaEmission::<T>::insert(netuid, &shares);
        }
        Self::deposit_event(Event::SubnetEmissionWeightsSet {
            netuid,
            weights: weights.to_vec(),
        });
        Self::deposit_event(Event::SubnetGammaEmissionSet {
            netuid,
            shares: shares.into_inner(),
        });
    }

    /// Take `target` off the gamma emission whitelist and out of every subnet's split: each
    /// share joins that subnet's miner slot, and its unconverted alpha the pending miner emission.
    pub fn remove_gamma_emission_target(target: NetUid) {
        SubnetGammaEmissionWhitelisted::<T>::remove(target);
        let splits: Vec<(NetUid, GammaEmission)> = SubnetGammaEmission::<T>::iter()
            .filter(|(_, shares)| shares.iter().any(|(listed, _)| *listed == target))
            .collect();
        for (netuid, shares) in splits {
            let (removed, kept): (Vec<_>, Vec<_>) = shares
                .into_iter()
                .partition(|(listed, _)| *listed == target);
            let mut weights = Self::get_subnet_emission_weights(netuid);
            if let Some(miner) = weights.get_mut(EmissionIndex::Miner.position()) {
                *miner = removed
                    .iter()
                    .fold(*miner, |miner, (_, share)| miner.saturating_add(*share));
            }
            Self::set_subnet_emission_split(netuid, weights, GammaEmission::truncate_from(kept));
        }
    }

    /// Worst-case weight of [`Self::remove_gamma_emission_target`]: every subnet's split read,
    /// allotted and rewritten. Only live subnets have splits.
    pub fn remove_gamma_emission_target_weight() -> Weight {
        let subnets = u64::from(TotalNetworks::<T>::get());
        let shares = u64::from(MAX_GAMMA_SHARES);
        T::DbWeight::get().reads_writes(
            subnets.saturating_mul(shares.saturating_add(6)),
            subnets
                .saturating_mul(shares.saturating_add(5))
                .saturating_add(1),
        )
    }
}