diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index f8b3e6a9b6..b625145410 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -120,7 +120,7 @@ parameter_types! { pub const InitialMaxDifficulty: u64 = u64::MAX; pub const InitialRAORecycledForRegistration: u64 = 0; pub const InitialSenateRequiredStakePercentage: u64 = 2; // 2 percent of total stake - pub const InitialNetworkImmunityPeriod: u64 = 7200 * 7; + pub const InitialNetworkImmunityPeriod: u64 = 1_296_000; pub const InitialNetworkMinAllowedUids: u16 = 128; pub const InitialNetworkMinLockCost: u64 = 100_000_000_000; pub const InitialSubnetOwnerCut: u16 = 0; // 0%. 100% of rewards go to validators + miners. diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 1f3a91b339..dd8206e281 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -21,8 +21,10 @@ use frame_support::storage::IterableStorageDoubleMap; use frame_support::weights::Weight; use safe_math::*; use sp_core::Get; +use sp_runtime::PerThing; +use sp_runtime::Perbill; use sp_std::vec; -use substrate_fixed::types::I64F64; +use substrate_fixed::types::{I64F64, U96F32}; impl Pallet { /// Fetches the total count of root network validators @@ -427,64 +429,28 @@ impl Pallet { .into()) } - /// Facilitates the removal of a user's subnetwork. - /// - /// # Args: - /// * 'origin': ('T::RuntimeOrigin'): The calling origin. Must be signed. - /// * 'netuid': ('u16'): The unique identifier of the network to be removed. - /// - /// # Event: - /// * 'NetworkRemoved': Emitted when a network is successfully removed. - /// - /// # Raises: - /// * 'SubNetworkDoesNotExist': If the specified network does not exist. - /// * 'NotSubnetOwner': If the caller does not own the specified subnet. - /// - pub fn user_remove_network(coldkey: T::AccountId, netuid: u16) -> dispatch::DispatchResult { - // --- 1. Ensure this subnet exists. + pub fn do_dissolve_network(netuid: u16) -> dispatch::DispatchResult { + // --- Perform the dtTao-compatible cleanup before removing the network. + Self::destroy_alpha_in_out_stakes(netuid)?; + + // --- Finally, remove the network entirely. ensure!( Self::if_subnet_exist(netuid), Error::::SubNetworkDoesNotExist ); - - // --- 2. Ensure the caller owns this subnet. - ensure!( - SubnetOwner::::get(netuid) == coldkey, - Error::::NotSubnetOwner - ); - - // --- 4. Remove the subnet identity if it exists. - if SubnetIdentitiesV2::::take(netuid).is_some() { - Self::deposit_event(Event::SubnetIdentityRemoved(netuid)); - } - - // --- 5. Explicitly erase the network and all its parameters. Self::remove_network(netuid); - // --- 6. Emit the NetworkRemoved event. + // --- Emit event. log::debug!("NetworkRemoved( netuid:{:?} )", netuid); Self::deposit_event(Event::NetworkRemoved(netuid)); - // --- 7. Return success. Ok(()) } - /// Removes a network (identified by netuid) and all associated parameters. - /// - /// This function is responsible for cleaning up all the data associated with a network. - /// It ensures that all the storage values related to the network are removed, any - /// reserved balance is returned to the network owner, and the subnet identity is removed if it exists. - /// - /// # Args: - /// * 'netuid': ('u16'): The unique identifier of the network to be removed. - /// - /// # Note: - /// This function does not emit any events, nor does it raise any errors. It silently - /// returns if any internal checks fail. pub fn remove_network(netuid: u16) { - // --- 1. Return balance to subnet owner. + // --- 1. Get the owner and remove from SubnetOwner. let owner_coldkey: T::AccountId = SubnetOwner::::get(netuid); - let reserved_amount: u64 = Self::get_subnet_locked_balance(netuid); + SubnetOwner::::remove(netuid); // --- 2. Remove network count. SubnetworkN::::remove(netuid); @@ -507,28 +473,26 @@ impl Pallet { let _ = Keys::::clear_prefix(netuid, u32::MAX, None); let _ = Bonds::::clear_prefix(netuid, u32::MAX, None); - // --- 8. Removes the weights for this subnet (do not remove). + // --- 8. Remove the weights for this subnet itself. let _ = Weights::::clear_prefix(netuid, u32::MAX, None); - // --- 9. Iterate over stored weights and fill the matrix. + // --- 9. Also zero out any weights *in the root network* that point to this netuid. for (uid_i, weights_i) in as IterableStorageDoubleMap>>::iter_prefix( Self::get_root_netuid(), ) { - // Create a new vector to hold modified weights. let mut modified_weights: Vec<(u16, u16)> = weights_i.clone(); - // Iterate over each weight entry to potentially update it. for (subnet_id, weight) in modified_weights.iter_mut() { + // If the root network had a weight pointing to this netuid, set it to 0 if subnet_id == &netuid { - // If the condition matches, modify the weight - *weight = 0; // Set weight to 0 for the matching subnet_id. + *weight = 0; } } Weights::::insert(Self::get_root_netuid(), uid_i, modified_weights); } - // --- 10. Remove various network-related parameters. + // --- 10. Remove network-related parameters and data. Rank::::remove(netuid); Trust::::remove(netuid); Active::::remove(netuid); @@ -544,8 +508,6 @@ impl Pallet { for (_uid, key) in keys { IsNetworkMember::::remove(key, netuid); } - - // --- 11. Erase network parameters. Tempo::::remove(netuid); Kappa::::remove(netuid); Difficulty::::remove(netuid); @@ -557,17 +519,24 @@ impl Pallet { RegistrationsThisInterval::::remove(netuid); POWRegistrationsThisInterval::::remove(netuid); BurnRegistrationsThisInterval::::remove(netuid); + SubnetTAO::::remove(netuid); + SubnetAlphaInEmission::::remove(netuid); + SubnetAlphaOutEmission::::remove(netuid); + SubnetTaoInEmission::::remove(netuid); + SubnetVolume::::remove(netuid); + SubnetMovingPrice::::remove(netuid); - // --- 12. Add the balance back to the owner. - Self::add_balance_to_coldkey_account(&owner_coldkey, reserved_amount); - Self::set_subnet_locked_balance(netuid, 0); - SubnetOwner::::remove(netuid); - - // --- 13. Remove subnet identity if it exists. if SubnetIdentitiesV2::::contains_key(netuid) { SubnetIdentitiesV2::::remove(netuid); Self::deposit_event(Event::SubnetIdentityRemoved(netuid)); } + + // --- Log final removal. + log::debug!( + "remove_network: netuid={}, owner={:?} removed successfully", + netuid, + owner_coldkey + ); } #[allow(clippy::arithmetic_side_effects)] @@ -671,4 +640,122 @@ impl Pallet { pub fn set_rate_limited_last_block(rate_limit_key: &RateLimitKey, block: u64) { LastRateLimitedBlock::::set(rate_limit_key, block); } + + fn destroy_alpha_in_out_stakes(netuid: u16) -> DispatchResult { + // 1. Ensure the subnet exists. + ensure!( + Self::if_subnet_exist(netuid), + Error::::SubNetworkDoesNotExist + ); + + // 2. Gather relevant info. + let owner_coldkey: T::AccountId = SubnetOwner::::get(netuid); + let lock_cost: u64 = Self::get_subnet_locked_balance(netuid); + + // (Optional) Grab total emission in Tao. + let emission_vec = Emission::::get(netuid); + let total_emission: u64 = emission_vec.iter().sum(); + + // The portion the owner received is total_emission * owner_cut (stored as fraction in U96F32). + let owner_fraction = Self::get_float_subnet_owner_cut(); + let owner_received_emission = (U96F32::from_num(total_emission) * owner_fraction) + .floor() + .saturating_to_num::(); + + // 3. Destroy α stakes and distribute remaining subnet Tao to α-out stakers (pro rata). + let mut total_alpha_out: u128 = 0; + let mut stakers_data = Vec::new(); + + // (A) First pass: sum total alpha-out for this netuid. + for ((hotkey, coldkey, this_netuid), alpha_shares) in Alpha::::iter() { + if this_netuid == netuid { + // alpha_shares is U64F64; convert to u128 for ratio math + let alpha_as_u128 = alpha_shares.saturating_to_num::(); + total_alpha_out = total_alpha_out.saturating_add(alpha_as_u128); + stakers_data.push((hotkey, coldkey, alpha_as_u128)); + } + } + + // (B) Second pass: distribute the subnet’s Tao among those stakers. + let subnet_tao = SubnetTAO::::get(netuid); + + if total_alpha_out > 0 { + let accuracy_as_u128 = u128::from(Perbill::ACCURACY); + + for (hotkey, coldkey, alpha_amount) in stakers_data { + let scaled = alpha_amount + .saturating_mul(accuracy_as_u128) + .checked_div(total_alpha_out) + .unwrap_or(0); + + // Clamp to avoid overflow beyond the Perbill limit (which is a 1.0 fraction). + let clamped = if scaled > accuracy_as_u128 { + Perbill::ACCURACY + } else { + scaled as u32 + }; + + // Construct a Perbill from these parts + let fraction = Perbill::from_parts(clamped); + + // Multiply fraction by subnet_tao to get the staker’s share (u64). + let tao_share = fraction * subnet_tao; + + // Credit the coldkey (or hotkey, depending on your design). + Self::add_balance_to_coldkey_account(&coldkey, tao_share); + + // Remove these alpha shares. + Alpha::::remove((hotkey.clone(), coldkey.clone(), netuid)); + } + } + + // Clear any leftover alpha in/out accumulations. + SubnetAlphaIn::::insert(netuid, 0); + SubnetAlphaOut::::insert(netuid, 0); + + // 4. Calculate partial refund = max(0, lock_cost - owner_received_emission). + let final_refund = lock_cost.saturating_sub(owner_received_emission).max(0); + + // 5. Set the locked balance on this subnet to 0, then credit the final_refund. + Self::set_subnet_locked_balance(netuid, 0); + + if final_refund > 0 { + Self::add_balance_to_coldkey_account(&owner_coldkey, final_refund); + } + + Ok(()) + } + + pub fn get_network_to_prune() -> Option { + let current_block: u64 = Self::get_current_block_as_u64(); + let total_networks: u16 = TotalNetworks::::get(); + + let mut candidate_netuid: Option = None; + let mut candidate_emission = u64::MAX; + let mut candidate_timestamp = u64::MAX; + + for netuid in 1..=total_networks { + let registered_at = NetworkRegisteredAt::::get(netuid); + + // Skip immune networks + if current_block < registered_at.saturating_add(Self::get_network_immunity_period()) { + continue; + } + + // We want total emission across all UIDs in this subnet: + let emission_vec = Emission::::get(netuid); + let total_emission = emission_vec.iter().sum::(); + + // If tie on total_emission, earliest registration wins + if total_emission < candidate_emission + || (total_emission == candidate_emission && registered_at < candidate_timestamp) + { + candidate_netuid = Some(netuid); + candidate_emission = total_emission; + candidate_timestamp = registered_at; + } + } + + candidate_netuid + } } diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 650fb50451..0a0d9083f5 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1231,7 +1231,7 @@ mod dispatches { netuid: u16, ) -> DispatchResult { ensure_root(origin)?; - Self::user_remove_network(coldkey, netuid) + Self::do_dissolve_network(netuid) } /// Set a single child for a given hotkey on a specified network. diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 221d802ccd..dbabfb926f 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -172,7 +172,7 @@ parameter_types! { pub const InitialMaxDifficulty: u64 = u64::MAX; pub const InitialRAORecycledForRegistration: u64 = 0; pub const InitialSenateRequiredStakePercentage: u64 = 2; // 2 percent of total stake - pub const InitialNetworkImmunityPeriod: u64 = 7200 * 7; + pub const InitialNetworkImmunityPeriod: u64 = 1_296_000; pub const InitialNetworkMinAllowedUids: u16 = 128; pub const InitialNetworkMinLockCost: u64 = 100_000_000_000; pub const InitialSubnetOwnerCut: u16 = 0; // 0%. 100% of rewards go to validators + miners. diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 7dda0502c1..c9bb787437 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -32,10 +32,7 @@ fn test_registration_ok() { coldkey_account_id )); - assert_ok!(SubtensorModule::user_remove_network( - coldkey_account_id, - netuid - )); + assert_ok!(SubtensorModule::do_dissolve_network(netuid)); assert!(!SubtensorModule::if_subnet_exist(netuid)) }) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 95b032f9e6..f4af9956be 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1073,7 +1073,7 @@ parameter_types! { pub const SubtensorInitialTxChildKeyTakeRateLimit: u64 = INITIAL_CHILDKEY_TAKE_RATELIMIT; pub const SubtensorInitialRAORecycledForRegistration: u64 = 0; // 0 rao pub const SubtensorInitialSenateRequiredStakePercentage: u64 = 1; // 1 percent of total stake - pub const SubtensorInitialNetworkImmunity: u64 = 7 * 7200; + pub const SubtensorInitialNetworkImmunity: u64 = 1_296_000; pub const SubtensorInitialMinAllowedUids: u16 = 128; pub const SubtensorInitialMinLockCost: u64 = 1_000_000_000_000; // 1000 TAO pub const SubtensorInitialSubnetOwnerCut: u16 = 11_796; // 18 percent