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
12 changes: 12 additions & 0 deletions pallets/admin-utils/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -645,5 +645,17 @@ mod benchmarks {
); /* sudo_set_min_non_immune_uids() */
}

#[benchmark]
fn sudo_set_emissions_disabled() {
pallet_subtensor::Pallet::<T>::set_admin_freeze_window(0);
pallet_subtensor::Pallet::<T>::init_new_network(
1u16.into(), /*netuid*/
1u16, /*tempo*/
);

#[extrinsic_call]
_(RawOrigin::Root, 1u16.into()/*netuid*/, true/*disabled*/)/*sudo_set_emissions_disabled*/;
}

//impl_benchmark_test_suite!(AdminUtils, crate::mock::new_test_ext(), crate::mock::Test);
}
34 changes: 34 additions & 0 deletions pallets/admin-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2240,6 +2240,40 @@ pub mod pallet {
pallet_subtensor::Pallet::<T>::set_min_non_immune_uids(netuid, min);
Ok(())
}

/// Enables or disables emissions for a specific subnet.
/// It is only callable by the root account (sudo).
/// When emissions are disabled, the subnet will not receive any TAO emissions.
#[pallet::call_index(85)]
#[pallet::weight((
Weight::from_parts(17_230_000, 0)
.saturating_add(<T as frame_system::Config>::DbWeight::get().reads(2))
.saturating_add(<T as frame_system::Config>::DbWeight::get().writes(1)),
DispatchClass::Operational,
Pays::Yes
))]
pub fn sudo_set_emissions_disabled(
origin: OriginFor<T>,
netuid: NetUid,
disabled: bool,
) -> DispatchResult {
ensure_root(origin)?;
ensure!(
pallet_subtensor::Pallet::<T>::if_subnet_exist(netuid),
Error::<T>::SubnetDoesNotExist
);

let current_value = pallet_subtensor::Pallet::<T>::get_emissions_disabled(netuid);
if current_value == disabled {
return Ok(());
}

pallet_subtensor::Pallet::<T>::set_emissions_disabled(netuid, disabled);
log::debug!(
"sudo_set_emissions_disabled( netuid: {netuid:?}, disabled: {disabled:?} ) "
);
Ok(())
}
}
}

Expand Down
91 changes: 91 additions & 0 deletions pallets/admin-utils/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2887,3 +2887,94 @@ fn test_sudo_set_min_non_immune_uids() {
assert_eq!(SubtensorModule::get_min_non_immune_uids(netuid), to_be_set);
});
}

#[test]
fn test_sudo_set_emissions_disabled() {
new_test_ext().execute_with(|| {
let netuid = NetUid::from(1);
add_network(netuid, 10);

// Check default value is false (emissions enabled)
let init_value: bool = SubtensorModule::get_emissions_disabled(netuid);
assert!(!init_value);

// Non-root cannot call
assert_eq!(
AdminUtils::sudo_set_emissions_disabled(
<<Test as Config>::RuntimeOrigin>::signed(U256::from(1)),
netuid,
true
),
Err(DispatchError::BadOrigin)
);
assert_eq!(SubtensorModule::get_emissions_disabled(netuid), init_value);

// Root can disable emissions
assert_ok!(AdminUtils::sudo_set_emissions_disabled(
<<Test as Config>::RuntimeOrigin>::root(),
netuid,
true
));
assert!(SubtensorModule::get_emissions_disabled(netuid));

// Root can re-enable emissions
assert_ok!(AdminUtils::sudo_set_emissions_disabled(
<<Test as Config>::RuntimeOrigin>::root(),
netuid,
false
));
assert!(!SubtensorModule::get_emissions_disabled(netuid));
});
}

#[test]
fn test_sudo_set_emissions_disabled_subnet_not_exist() {
new_test_ext().execute_with(|| {
let netuid = NetUid::from(99);

// Subnet does not exist
assert_err!(
AdminUtils::sudo_set_emissions_disabled(
<<Test as Config>::RuntimeOrigin>::root(),
netuid,
true
),
Error::<Test>::SubnetDoesNotExist
);
});
}

#[test]
fn test_sudo_set_emissions_disabled_same_value() {
new_test_ext().execute_with(|| {
let netuid = NetUid::from(1);
add_network(netuid, 10);

// Default value is false
assert!(!SubtensorModule::get_emissions_disabled(netuid));

// Setting to same value (false) should succeed without changing anything
assert_ok!(AdminUtils::sudo_set_emissions_disabled(
<<Test as Config>::RuntimeOrigin>::root(),
netuid,
false
));
assert!(!SubtensorModule::get_emissions_disabled(netuid));

// Now disable emissions
assert_ok!(AdminUtils::sudo_set_emissions_disabled(
<<Test as Config>::RuntimeOrigin>::root(),
netuid,
true
));
assert!(SubtensorModule::get_emissions_disabled(netuid));

// Setting to same value (true) should succeed without changing anything
assert_ok!(AdminUtils::sudo_set_emissions_disabled(
<<Test as Config>::RuntimeOrigin>::root(),
netuid,
true
));
assert!(SubtensorModule::get_emissions_disabled(netuid));
});
}
1 change: 1 addition & 0 deletions pallets/subtensor/src/coinbase/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ impl<T: Config> Pallet<T> {

// --- 15. Mechanism step / emissions bookkeeping.
FirstEmissionBlockNumber::<T>::remove(netuid);
EmissionsDisabled::<T>::remove(netuid);
PendingValidatorEmission::<T>::remove(netuid);
PendingServerEmission::<T>::remove(netuid);
PendingRootAlphaDivs::<T>::remove(netuid);
Expand Down
2 changes: 2 additions & 0 deletions pallets/subtensor/src/coinbase/subnet_emissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ impl<T: Config> Pallet<T> {
pub fn get_subnets_to_emit_to(subnets: &[NetUid]) -> Vec<NetUid> {
// Filter out root subnet.
// Filter out subnets with no first emission block number.
// Filter out subnets with emissions disabled.
subnets
.iter()
.filter(|netuid| !netuid.is_root())
.filter(|netuid| FirstEmissionBlockNumber::<T>::get(*netuid).is_some())
.filter(|netuid| SubtokenEnabled::<T>::get(*netuid))
.filter(|netuid| !EmissionsDisabled::<T>::get(*netuid))
.filter(|&netuid| {
// Only emit TAO if the subnetwork allows registration.
Self::get_network_registration_allowed(*netuid)
Expand Down
5 changes: 5 additions & 0 deletions pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1587,6 +1587,11 @@ pub mod pallet {
pub type FirstEmissionBlockNumber<T: Config> =
StorageMap<_, Identity, NetUid, u64, OptionQuery>;

/// --- MAP ( netuid ) --> emissions_disabled | Whether emissions are disabled for this subnet
#[pallet::storage]
pub type EmissionsDisabled<T: Config> =
StorageMap<_, Identity, NetUid, bool, ValueQuery, DefaultFalse<T>>;

/// --- MAP ( netuid ) --> subnet mechanism
#[pallet::storage]
pub type SubnetMechanism<T: Config> =
Expand Down
2 changes: 2 additions & 0 deletions pallets/subtensor/src/macros/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ mod events {
RegistrationAllowed(NetUid, bool),
/// POW registration is allowed/disallowed for a subnet.
PowRegistrationAllowed(NetUid, bool),
/// emissions are enabled/disabled for a subnet.
EmissionsDisabledSet(NetUid, bool),
/// setting tempo on a network
TempoSet(NetUid, u16),
/// setting the RAO recycled for registration.
Expand Down
135 changes: 135 additions & 0 deletions pallets/subtensor/src/tests/coinbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4016,3 +4016,138 @@ fn test_get_subnet_terms_alpha_emissions_cap() {
assert_eq!(alpha_in.get(&netuid).copied().unwrap(), tao_block_emission);
});
}

#[test]
fn test_get_subnets_to_emit_to_filters_emissions_disabled() {
new_test_ext(1).execute_with(|| {
let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2));
let netuid1 = add_dynamic_network(&U256::from(3), &U256::from(4));

// Both subnets should be in the list initially
let subnets_to_emit_to_0 = SubtensorModule::get_subnets_to_emit_to(&[netuid0, netuid1]);
assert_eq!(subnets_to_emit_to_0.len(), 2);
assert!(subnets_to_emit_to_0.contains(&netuid0));
assert!(subnets_to_emit_to_0.contains(&netuid1));

// Disable emissions for netuid0
EmissionsDisabled::<Test>::insert(netuid0, true);

// Check that netuid0 is not in the list
let subnets_to_emit_to_1 = SubtensorModule::get_subnets_to_emit_to(&[netuid0, netuid1]);
assert_eq!(subnets_to_emit_to_1.len(), 1);
assert!(!subnets_to_emit_to_1.contains(&netuid0));
assert!(subnets_to_emit_to_1.contains(&netuid1));

// Re-enable emissions for netuid0
EmissionsDisabled::<Test>::insert(netuid0, false);

// Check that netuid0 is back in the list
let subnets_to_emit_to_2 = SubtensorModule::get_subnets_to_emit_to(&[netuid0, netuid1]);
assert_eq!(subnets_to_emit_to_2.len(), 2);
assert!(subnets_to_emit_to_2.contains(&netuid0));
assert!(subnets_to_emit_to_2.contains(&netuid1));
});
}

/// Test that disabled emissions do NOT accrue retroactively when re-enabled.
/// This is critical to ensure that turning emissions back on doesn't magically
/// give the subnet all the emissions it would have received while disabled.
// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_emissions_disabled_no_retroactive_accrual --exact --show-output --nocapture
#[test]
fn test_emissions_disabled_no_retroactive_accrual() {
new_test_ext(1).execute_with(|| {
let netuid1 = NetUid::from(1);
let netuid2 = NetUid::from(2);
let emission_per_block: u64 = 1_000_000;

// Set up two networks
add_network(netuid1, 1, 0);
add_network(netuid2, 1, 0);

// Make subnets dynamic
SubnetMechanism::<Test>::insert(netuid1, 1);
SubnetMechanism::<Test>::insert(netuid2, 1);

// Set up equal initial state for both subnets
let initial: u64 = 1_000_000;
SubnetTAO::<Test>::insert(netuid1, TaoCurrency::from(initial));
SubnetAlphaIn::<Test>::insert(netuid1, AlphaCurrency::from(initial));
SubnetTAO::<Test>::insert(netuid2, TaoCurrency::from(initial));
SubnetAlphaIn::<Test>::insert(netuid2, AlphaCurrency::from(initial));

// Set equal TAO flows so emissions are split equally
SubnetTaoFlow::<Test>::insert(netuid1, 100_000_000_i64);
SubnetTaoFlow::<Test>::insert(netuid2, 100_000_000_i64);

// Run coinbase once - both subnets should receive emissions
SubtensorModule::run_coinbase(U96F32::from_num(emission_per_block));

let alpha_after_first_emission_netuid1 = SubnetAlphaIn::<Test>::get(netuid1);
let alpha_after_first_emission_netuid2 = SubnetAlphaIn::<Test>::get(netuid2);

// Both should have received equal emissions
assert_eq!(alpha_after_first_emission_netuid1, alpha_after_first_emission_netuid2);
assert!(u64::from(alpha_after_first_emission_netuid1) > initial);

// Now disable emissions for netuid1
EmissionsDisabled::<Test>::insert(netuid1, true);

// Run coinbase multiple times while netuid1 is disabled
// netuid2 should continue to receive emissions, netuid1 should NOT
for _ in 0..5 {
SubtensorModule::run_coinbase(U96F32::from_num(emission_per_block));
}

let alpha_after_disabled_period_netuid1 = SubnetAlphaIn::<Test>::get(netuid1);
let alpha_after_disabled_period_netuid2 = SubnetAlphaIn::<Test>::get(netuid2);

// netuid1 should NOT have received any additional emissions while disabled
assert_eq!(
alpha_after_disabled_period_netuid1,
alpha_after_first_emission_netuid1,
"netuid1 should not have received emissions while disabled"
);

// netuid2 should have received 5 more blocks of emissions
assert!(
u64::from(alpha_after_disabled_period_netuid2) > u64::from(alpha_after_first_emission_netuid2),
"netuid2 should have continued receiving emissions"
);

// Record the state before re-enabling
let alpha_before_reenable_netuid1 = SubnetAlphaIn::<Test>::get(netuid1);

// Re-enable emissions for netuid1
EmissionsDisabled::<Test>::insert(netuid1, false);

// Run coinbase once after re-enabling
SubtensorModule::run_coinbase(U96F32::from_num(emission_per_block));

let alpha_after_reenable_netuid1 = SubnetAlphaIn::<Test>::get(netuid1);

// netuid1 should have received ONLY the current block's emission share,
// NOT any retroactive emissions from the 5 blocks it was disabled
let emission_received_after_reenable =
u64::from(alpha_after_reenable_netuid1) - u64::from(alpha_before_reenable_netuid1);

// The emission received should be roughly equal to one block's worth of emissions
// (accounting for the emission split between subnets)
// It should definitely NOT be 5x or 6x the normal emission
let one_block_emission_estimate = emission_per_block / 2; // Split between 2 subnets

// Allow some tolerance for rounding, but ensure it's not retroactive
// If retroactive, it would be ~5x the normal emission
assert!(
emission_received_after_reenable < one_block_emission_estimate * 2,
"Emission after re-enable ({}) should be roughly one block's worth ({}), not retroactive",
emission_received_after_reenable,
one_block_emission_estimate
);

// Also verify it's not zero - the subnet should receive current emissions
assert!(
emission_received_after_reenable > 0,
"netuid1 should receive emissions after being re-enabled"
);
});
}
9 changes: 9 additions & 0 deletions pallets/subtensor/src/utils/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,15 @@ impl<T: Config> Pallet<T> {
Self::deposit_event(Event::PowRegistrationAllowed(netuid, registration_allowed));
}

// Emissions Disabled utils
pub fn get_emissions_disabled(netuid: NetUid) -> bool {
EmissionsDisabled::<T>::get(netuid)
}
pub fn set_emissions_disabled(netuid: NetUid, disabled: bool) {
EmissionsDisabled::<T>::insert(netuid, disabled);
Self::deposit_event(Event::EmissionsDisabledSet(netuid, disabled));
}

pub fn get_target_registrations_per_interval(netuid: NetUid) -> u16 {
TargetRegistrationsPerInterval::<T>::get(netuid)
}
Expand Down