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
53 changes: 53 additions & 0 deletions pallets/admin-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ pub mod pallet {
/// The new burn increase multiplier.
burn_increase_mult: U64F64,
},

/// Pool-side subnet emission injections and chain buys were enabled or disabled.
SubnetEmissionEnabledSet {
/// The network identifier.
netuid: NetUid,
/// Whether pool-side emission injections and chain buys are enabled.
enabled: bool,
},
}

// Errors inform users that something went wrong.
Expand Down Expand Up @@ -2141,6 +2149,51 @@ pub mod pallet {

Ok(())
}

/// Enables or disables subnet pool-side emission for a subnet.
///
/// This does not remove the subnet from emission share calculation and does not
/// change `alpha_out`, owner cut, root proportion, pending server emission, or
/// pending validator emission. It only zeros the pool-side `alpha_in`, `tao_in`,
/// and `excess_tao` chain-buy paths.
#[pallet::call_index(92)]
#[pallet::weight((
Weight::from_parts(25_000_000, 0)
.saturating_add(T::DbWeight::get().reads(4))
.saturating_add(T::DbWeight::get().writes(2)),
DispatchClass::Operational,
Pays::Yes,
))]
pub fn sudo_set_subnet_emission_enabled(
origin: OriginFor<T>,
netuid: NetUid,
enabled: bool,
) -> DispatchResult {
let maybe_owner = pallet_subtensor::Pallet::<T>::ensure_sn_owner_or_root_with_limits(
origin,
netuid,
&[Hyperparameter::SubnetEmissionEnabled.into()],
)?;
pallet_subtensor::Pallet::<T>::ensure_admin_window_open(netuid)?;

ensure!(
pallet_subtensor::Pallet::<T>::if_subnet_exist(netuid),
Error::<T>::SubnetDoesNotExist
);
ensure!(!netuid.is_root(), Error::<T>::NotPermittedOnRootSubnet);

pallet_subtensor::SubnetEmissionEnabled::<T>::insert(netuid, enabled);
Self::deposit_event(Event::SubnetEmissionEnabledSet { netuid, enabled });
log::debug!("SubnetEmissionEnabledSet( netuid: {netuid:?}, enabled: {enabled:?} )");

pallet_subtensor::Pallet::<T>::record_owner_rl(
maybe_owner,
netuid,
&[Hyperparameter::SubnetEmissionEnabled.into()],
);

Ok(())
}
}
}

Expand Down
5 changes: 5 additions & 0 deletions pallets/subtensor/src/coinbase/run_coinbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ impl<T: Config> Pallet<T> {
let tao_to_swap_with: TaoBalance =
tou64!(excess_tao.get(netuid_i).unwrap_or(&asfloat!(0))).into();

// Clear per-block pool-side emission counters up front so a subnet
// disabled this block does not display stale values from an earlier block.
SubnetExcessTao::<T>::insert(*netuid_i, TaoBalance::ZERO);
SubnetTaoInEmission::<T>::insert(*netuid_i, TaoBalance::ZERO);

T::SwapInterface::adjust_protocol_liquidity(*netuid_i, tao_in_i, alpha_in_i);

if tao_to_swap_with > TaoBalance::ZERO {
Expand Down
22 changes: 21 additions & 1 deletion pallets/subtensor/src/coinbase/subnet_emissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,33 @@ impl<T: Config> Pallet<T> {
subnets_to_emit_to: &[NetUid],
block_emission: U96F32,
) -> BTreeMap<NetUid, U96F32> {
// Get subnet TAO emissions.
// Get subnet TAO emission shares for all emission-eligible subnets first. This keeps
// disabled subnets in the returned map so they still continue through alpha_out/root-prop
// accounting, while their TAO-side emission is redistributed to enabled subnets.
let shares = Self::get_shares(subnets_to_emit_to);
log::debug!("Subnet emission shares = {shares:?}");

let zero = U64F64::saturating_from_num(0.0);
let has_disabled_subnets = shares
.keys()
.any(|netuid| !SubnetEmissionEnabled::<T>::get(*netuid));
let enabled_share_sum: U64F64 = shares
.iter()
.filter(|(netuid, _)| SubnetEmissionEnabled::<T>::get(**netuid))
.fold(zero, |acc, (_, share)| acc.saturating_add(*share));

shares
.into_iter()
.map(|(netuid, share)| {
let share = if has_disabled_subnets {
if SubnetEmissionEnabled::<T>::get(netuid) && enabled_share_sum > zero {
share.safe_div(enabled_share_sum)
} else {
zero
}
} else {
share
};
let emission = U64F64::saturating_from_num(block_emission).saturating_mul(share);
(netuid, U96F32::saturating_from_num(emission))
})
Expand Down
12 changes: 12 additions & 0 deletions pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,18 @@ pub mod pallet {
pub type SubnetAlphaInEmission<T: Config> =
StorageMap<_, Identity, NetUid, AlphaBalance, ValueQuery, DefaultZeroAlpha<T>>;

/// --- MAP ( netuid ) --> subnet_emission_enabled
///
/// When false, subnet pool-side emission is disabled for this subnet:
/// `alpha_in`, `tao_in`, and `excess_tao` chain buys are all treated as zero.
/// `alpha_out`, owner cut, root proportion, pending server emission, and pending
/// validator emission are intentionally left unchanged.
///
/// Defaults to true so existing subnets keep current behavior.
#[pallet::storage]
pub type SubnetEmissionEnabled<T: Config> =
StorageMap<_, Identity, NetUid, bool, ValueQuery, DefaultTrue<T>>;

/// --- MAP ( netuid ) --> alpha_out_emission | Returns the amount of alpha out emission into the network per block.
#[pallet::storage]
pub type SubnetAlphaOutEmission<T: Config> =
Expand Down
4 changes: 4 additions & 0 deletions pallets/subtensor/src/subnets/subnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,10 @@ impl<T: Config> Pallet<T> {
Self::set_yuma3_enabled(netuid, true);
Self::set_burn(netuid, DefaultNeuronBurnCost::<T>::get());

// New subnets should never inherit a prior subnet owner's disabled state
// when a netuid is reused after pruning/dissolve.
SubnetEmissionEnabled::<T>::insert(netuid, true);

// Make network parameters explicit.
if !Tempo::<T>::contains_key(netuid) {
Tempo::<T>::insert(netuid, Tempo::<T>::get(netuid));
Expand Down
163 changes: 163 additions & 0 deletions pallets/subtensor/src/tests/coinbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,169 @@ fn test_coinbase_tao_issuance_multiple() {
});
}

#[test]
fn test_coinbase_disabled_subnet_emission_redistributes_tao_to_enabled_subnets() {
new_test_ext(1).execute_with(|| {
let netuid1 = NetUid::from(1);
let netuid2 = NetUid::from(2);
let netuid3 = NetUid::from(3);
let emission = TaoBalance::from(3_333_333);

add_network(netuid1, 1, 0);
add_network(netuid2, 1, 0);
add_network(netuid3, 1, 0);

SubnetEmissionEnabled::<Test>::insert(netuid2, false);

SubnetTaoFlow::<Test>::insert(netuid1, 100_000_000_i64);
SubnetTaoFlow::<Test>::insert(netuid2, 100_000_000_i64);
SubnetTaoFlow::<Test>::insert(netuid3, 100_000_000_i64);

let subnet_emissions = SubtensorModule::get_subnet_block_emissions(
&[netuid1, netuid2, netuid3],
U96F32::saturating_from_num(emission.to_u64()),
);

assert_abs_diff_eq!(
subnet_emissions[&netuid1].to_num::<f64>(),
(emission.to_u64() / 2) as f64,
epsilon = 2.0,
);
assert_abs_diff_eq!(
subnet_emissions[&netuid2].to_num::<f64>(),
0.0,
epsilon = 1.0
);
assert_abs_diff_eq!(
subnet_emissions[&netuid3].to_num::<f64>(),
(emission.to_u64() / 2) as f64,
epsilon = 2.0,
);

let (_tao_in, alpha_in, alpha_out, excess_tao) =
SubtensorModule::get_subnet_terms(&subnet_emissions);
assert_eq!(alpha_in[&netuid2], U96F32::from_num(0.0));
assert_eq!(excess_tao[&netuid2], U96F32::from_num(0.0));
assert!(alpha_out[&netuid2] > U96F32::from_num(0.0));

let total_issuance_before = TotalIssuance::<Test>::get();
let total_stake_before = TotalStake::<Test>::get();
let emission_credit = SubtensorModule::mint_tao(emission);
SubtensorModule::run_coinbase(emission_credit);

assert_abs_diff_eq!(
SubnetTAO::<Test>::get(netuid1),
emission / 2.into(),
epsilon = 2.into(),
);
assert_eq!(SubnetTAO::<Test>::get(netuid2), TaoBalance::ZERO);
assert_abs_diff_eq!(
SubnetTAO::<Test>::get(netuid3),
emission / 2.into(),
epsilon = 2.into(),
);
assert_abs_diff_eq!(
TotalIssuance::<Test>::get(),
total_issuance_before + emission,
epsilon = 2.into(),
);
assert_abs_diff_eq!(
TotalStake::<Test>::get(),
total_stake_before + emission,
epsilon = 2.into(),
);
});
}

#[test]
fn test_sudo_set_subnet_emission_enabled_multiple_subnets_multiple_toggles() {
new_test_ext(1).execute_with(|| {
let netuid1 = NetUid::from(1);
let netuid2 = NetUid::from(2);
let netuid3 = NetUid::from(3);
let emission = TaoBalance::from(3_000_000);

add_network(netuid1, 1, 0);
add_network(netuid2, 1, 0);
add_network(netuid3, 1, 0);

SubnetTaoFlow::<Test>::insert(netuid1, 100_000_000_i64);
SubnetTaoFlow::<Test>::insert(netuid2, 100_000_000_i64);
SubnetTaoFlow::<Test>::insert(netuid3, 100_000_000_i64);

let assert_emission_storage = |expected1: u64, expected2: u64, expected3: u64| {
assert_abs_diff_eq!(
SubnetTaoInEmission::<Test>::get(netuid1),
TaoBalance::from(expected1),
epsilon = 2.into(),
);
assert_abs_diff_eq!(
SubnetTaoInEmission::<Test>::get(netuid2),
TaoBalance::from(expected2),
epsilon = 2.into(),
);
assert_abs_diff_eq!(
SubnetTaoInEmission::<Test>::get(netuid3),
TaoBalance::from(expected3),
epsilon = 2.into(),
);

assert_eq!(
SubnetAlphaInEmission::<Test>::get(netuid1) == AlphaBalance::from(0),
expected1 == 0
);
assert_eq!(
SubnetAlphaInEmission::<Test>::get(netuid2) == AlphaBalance::from(0),
expected2 == 0
);
assert_eq!(
SubnetAlphaInEmission::<Test>::get(netuid3) == AlphaBalance::from(0),
expected3 == 0
);

assert!(SubnetAlphaOutEmission::<Test>::get(netuid1) > AlphaBalance::from(0));
assert!(SubnetAlphaOutEmission::<Test>::get(netuid2) > AlphaBalance::from(0));
assert!(SubnetAlphaOutEmission::<Test>::get(netuid3) > AlphaBalance::from(0));
};

let run_coinbase = || {
let emission_credit = SubtensorModule::mint_tao(emission);
SubtensorModule::run_coinbase(emission_credit);
};

// All enabled: split TAO-side emission equally across all three subnets.
run_coinbase();
assert_emission_storage(1_000_000, 1_000_000, 1_000_000);

// Seed stale values and then disable netuid2. The next coinbase run must clear
// netuid2's per-block TAO-side emission storage while preserving alpha_out.
SubnetTaoInEmission::<Test>::insert(netuid2, TaoBalance::from(123));
SubnetAlphaInEmission::<Test>::insert(netuid2, AlphaBalance::from(123));
SubnetExcessTao::<Test>::insert(netuid2, TaoBalance::from(123));
SubnetEmissionEnabled::<Test>::insert(netuid2, false);
run_coinbase();
assert_emission_storage(1_500_000, 0, 1_500_000);
assert_eq!(SubnetExcessTao::<Test>::get(netuid2), TaoBalance::from(0));

// Toggle a different subnet off and netuid2 back on.
SubnetTaoInEmission::<Test>::insert(netuid1, TaoBalance::from(456));
SubnetAlphaInEmission::<Test>::insert(netuid1, AlphaBalance::from(456));
SubnetExcessTao::<Test>::insert(netuid1, TaoBalance::from(456));
SubnetEmissionEnabled::<Test>::insert(netuid1, false);
SubnetEmissionEnabled::<Test>::insert(netuid2, true);
run_coinbase();
assert_emission_storage(0, 1_500_000, 1_500_000);
assert_eq!(SubnetExcessTao::<Test>::get(netuid1), TaoBalance::from(0));

// Toggle everything back on: TAO-side emission should return to an even split.
SubnetEmissionEnabled::<Test>::insert(netuid1, true);
SubnetEmissionEnabled::<Test>::insert(netuid2, true);
SubnetEmissionEnabled::<Test>::insert(netuid3, true);
run_coinbase();
assert_emission_storage(1_000_000, 1_000_000, 1_000_000);
});
}

// Test emission distribution with different subnet prices.
// This test verifies that:
// - Subnets with different prices receive proportional emission shares
Expand Down
1 change: 1 addition & 0 deletions pallets/subtensor/src/utils/rate_limiting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ pub enum Hyperparameter {
MaxAllowedUids = 25,
BurnHalfLife = 26,
BurnIncreaseMult = 27,
SubnetEmissionEnabled = 28,
}

impl<T: Config> Pallet<T> {
Expand Down
5 changes: 4 additions & 1 deletion runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// `spec_version`, and `authoring_version` are the same between Wasm and native.
// This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use
// the compatible custom types.
spec_version: 406,
spec_version: 407,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
Expand Down Expand Up @@ -812,6 +812,9 @@ impl InstanceFilter<RuntimeCall> for ProxyType {
| RuntimeCall::AdminUtils(
pallet_admin_utils::Call::sudo_set_toggle_transfer { .. }
)
| RuntimeCall::AdminUtils(
pallet_admin_utils::Call::sudo_set_subnet_emission_enabled { .. }
)
),
ProxyType::RootClaim => matches!(
c,
Expand Down
Loading